Home | History | Annotate | Download | only in icon
      1 /*
      2  * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
      3  * Copyright (C) 2007 Justin Haygood (jhaygood (at) reaktix.com)
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #include "config.h"
     28 #include "IconDatabase.h"
     29 
     30 #if ENABLE(ICONDATABASE)
     31 
     32 #include "AutodrainedPool.h"
     33 #include "DocumentLoader.h"
     34 #include "FileSystem.h"
     35 #include "IconDatabaseClient.h"
     36 #include "IconRecord.h"
     37 #include "IntSize.h"
     38 #include "Logging.h"
     39 #include "ScriptController.h"
     40 #include "SQLiteStatement.h"
     41 #include "SQLiteTransaction.h"
     42 #include "SuddenTermination.h"
     43 #include <wtf/CurrentTime.h>
     44 #include <wtf/MainThread.h>
     45 #include <wtf/StdLibExtras.h>
     46 
     47 // For methods that are meant to support API from the main thread - should not be called internally
     48 #define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
     49 
     50 // For methods that are meant to support the sync thread ONLY
     51 #define IS_ICON_SYNC_THREAD() (m_syncThread == currentThread())
     52 #define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
     53 
     54 #if PLATFORM(QT) || PLATFORM(GTK)
     55 #define CAN_THEME_URL_ICON
     56 #endif
     57 
     58 namespace WebCore {
     59 
     60 static IconDatabase* sharedIconDatabase = 0;
     61 static int databaseCleanupCounter = 0;
     62 
     63 // This version number is in the DB and marks the current generation of the schema
     64 // Currently, a mismatched schema causes the DB to be wiped and reset.  This isn't
     65 // so bad during development but in the future, we would need to write a conversion
     66 // function to advance older released schemas to "current"
     67 static const int currentDatabaseVersion = 6;
     68 
     69 // Icons expire once every 4 days
     70 static const int iconExpirationTime = 60*60*24*4;
     71 
     72 static const int updateTimerDelay = 5;
     73 
     74 static bool checkIntegrityOnOpen = false;
     75 
     76 #ifndef NDEBUG
     77 static String urlForLogging(const String& url)
     78 {
     79     static unsigned urlTruncationLength = 120;
     80 
     81     if (url.length() < urlTruncationLength)
     82         return url;
     83     return url.substring(0, urlTruncationLength) + "...";
     84 }
     85 #endif
     86 
     87 static IconDatabaseClient* defaultClient()
     88 {
     89     static IconDatabaseClient* defaultClient = new IconDatabaseClient();
     90     return defaultClient;
     91 }
     92 
     93 IconDatabase* iconDatabase()
     94 {
     95     if (!sharedIconDatabase) {
     96         ScriptController::initializeThreading();
     97         sharedIconDatabase = new IconDatabase;
     98     }
     99     return sharedIconDatabase;
    100 }
    101 
    102 // ************************
    103 // *** Main Thread Only ***
    104 // ************************
    105 
    106 void IconDatabase::setClient(IconDatabaseClient* client)
    107 {
    108     // We don't allow a null client, because we never null check it anywhere in this code
    109     // Also don't allow a client change after the thread has already began
    110     // (setting the client should occur before the database is opened)
    111     ASSERT(client);
    112     ASSERT(!m_syncThreadRunning);
    113     if (!client || m_syncThreadRunning)
    114         return;
    115 
    116     m_client = client;
    117 }
    118 
    119 bool IconDatabase::open(const String& databasePath)
    120 {
    121     ASSERT_NOT_SYNC_THREAD();
    122 
    123     if (!m_isEnabled)
    124         return false;
    125 
    126     if (isOpen()) {
    127         LOG_ERROR("Attempt to reopen the IconDatabase which is already open.  Must close it first.");
    128         return false;
    129     }
    130 
    131     m_databaseDirectory = databasePath.crossThreadString();
    132 
    133     // Formulate the full path for the database file
    134     m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename());
    135 
    136     // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call
    137     // completes and m_syncThreadRunning is properly set
    138     m_syncLock.lock();
    139     m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this, "WebCore: IconDatabase");
    140     m_syncThreadRunning = m_syncThread;
    141     m_syncLock.unlock();
    142     if (!m_syncThread)
    143         return false;
    144     return true;
    145 }
    146 
    147 void IconDatabase::close()
    148 {
    149 #ifdef ANDROID
    150     // Since we close and reopen the database within the same process, reset
    151     // this flag
    152     m_initialPruningComplete = false;
    153 #endif
    154     ASSERT_NOT_SYNC_THREAD();
    155 
    156     if (m_syncThreadRunning) {
    157         // Set the flag to tell the sync thread to wrap it up
    158         m_threadTerminationRequested = true;
    159 
    160         // Wake up the sync thread if it's waiting
    161         wakeSyncThread();
    162 
    163         // Wait for the sync thread to terminate
    164         waitForThreadCompletion(m_syncThread, 0);
    165     }
    166 
    167     m_syncThreadRunning = false;
    168     m_threadTerminationRequested = false;
    169     m_removeIconsRequested = false;
    170 
    171     m_syncDB.close();
    172     ASSERT(!isOpen());
    173 }
    174 
    175 void IconDatabase::removeAllIcons()
    176 {
    177     ASSERT_NOT_SYNC_THREAD();
    178 
    179     if (!isOpen())
    180         return;
    181 
    182     LOG(IconDatabase, "Requesting background thread to remove all icons");
    183 
    184     // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk
    185     {
    186         MutexLocker locker(m_urlAndIconLock);
    187 
    188         // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted
    189         // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of
    190         HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin();
    191         HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end();
    192         for (; iter != end; ++iter)
    193             (*iter).second->setIconRecord(0);
    194 
    195         // Clear the iconURL -> IconRecord map
    196         m_iconURLToRecordMap.clear();
    197 
    198         // Clear all in-memory records of things that need to be synced out to disk
    199         {
    200             MutexLocker locker(m_pendingSyncLock);
    201             m_pageURLsPendingSync.clear();
    202             m_iconsPendingSync.clear();
    203         }
    204 
    205         // Clear all in-memory records of things that need to be read in from disk
    206         {
    207             MutexLocker locker(m_pendingReadingLock);
    208             m_pageURLsPendingImport.clear();
    209             m_pageURLsInterestedInIcons.clear();
    210             m_iconsPendingReading.clear();
    211             m_loadersPendingDecision.clear();
    212         }
    213     }
    214 
    215     m_removeIconsRequested = true;
    216     wakeSyncThread();
    217 }
    218 
    219 Image* IconDatabase::iconForPageURL(const String& pageURLOriginal, const IntSize& size)
    220 {
    221     ASSERT_NOT_SYNC_THREAD();
    222 
    223     // pageURLOriginal cannot be stored without being deep copied first.
    224     // We should go our of our way to only copy it if we have to store it
    225 
    226     if (!isOpen() || pageURLOriginal.isEmpty())
    227         return defaultIcon(size);
    228 
    229     MutexLocker locker(m_urlAndIconLock);
    230 
    231     String pageURLCopy; // Creates a null string for easy testing
    232 
    233     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
    234     if (!pageRecord) {
    235         pageURLCopy = pageURLOriginal.crossThreadString();
    236         pageRecord = getOrCreatePageURLRecord(pageURLCopy);
    237     }
    238 
    239     // If pageRecord is NULL, one of two things is true -
    240     // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists
    241     // 2 - The initial url import IS complete and this pageURL has no icon
    242     if (!pageRecord) {
    243         MutexLocker locker(m_pendingReadingLock);
    244 
    245         // Import is ongoing, there might be an icon.  In this case, register to be notified when the icon comes in
    246         // If we ever reach this condition, we know we've already made the pageURL copy
    247         if (!m_iconURLImportComplete)
    248             m_pageURLsInterestedInIcons.add(pageURLCopy);
    249 
    250         return 0;
    251     }
    252 
    253     IconRecord* iconRecord = pageRecord->iconRecord();
    254 
    255     // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon
    256     // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so
    257     // we can just bail now
    258     if (!m_iconURLImportComplete && !iconRecord)
    259         return 0;
    260 
    261     // The only way we should *not* have an icon record is if this pageURL is retained but has no icon yet - make sure of that
    262     ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal));
    263 
    264     if (!iconRecord)
    265         return 0;
    266 
    267     // If it's a new IconRecord object that doesn't have its imageData set yet,
    268     // mark it to be read by the background thread
    269     if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) {
    270         if (pageURLCopy.isNull())
    271             pageURLCopy = pageURLOriginal.crossThreadString();
    272 
    273         MutexLocker locker(m_pendingReadingLock);
    274         m_pageURLsInterestedInIcons.add(pageURLCopy);
    275         m_iconsPendingReading.add(iconRecord);
    276         wakeSyncThread();
    277         return 0;
    278     }
    279 
    280     // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off
    281     // and isn't actually interested in the image return value
    282     if (size == IntSize(0, 0))
    283         return 0;
    284 
    285     // PARANOID DISCUSSION: This method makes some assumptions.  It returns a WebCore::image which the icon database might dispose of at anytime in the future,
    286     // and Images aren't ref counted.  So there is no way for the client to guarantee continued existence of the image.
    287     // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image
    288     // and drop the raw Image*.  On Mac an NSImage, and on windows drawing into an HBITMAP.
    289     // The async aspect adds a huge question - what if the image is deleted before the platform specific API has a chance to create its own
    290     // representation out of it?
    291     // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache.
    292     // This is because we make the assumption that anything in memory is newer than whatever is in the database.
    293     // So the only time the data will be set from the second thread is when it is INITIALLY being read in from the database, but we would never
    294     // delete the image on the secondary thread if the image already exists.
    295     return iconRecord->image(size);
    296 }
    297 
    298 void IconDatabase::readIconForPageURLFromDisk(const String& pageURL)
    299 {
    300     // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk
    301     // if it hasn't already been set in memory.  The special IntSize (0, 0) is a special way of telling
    302     // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk.
    303     iconForPageURL(pageURL, IntSize(0,0));
    304 }
    305 
    306 String IconDatabase::iconURLForPageURL(const String& pageURLOriginal)
    307 {
    308     ASSERT_NOT_SYNC_THREAD();
    309 
    310     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
    311     // Also, in the case we have a real answer for the caller, we must deep copy that as well
    312 
    313     if (!isOpen() || pageURLOriginal.isEmpty())
    314         return String();
    315 
    316     MutexLocker locker(m_urlAndIconLock);
    317 
    318     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
    319     if (!pageRecord)
    320         pageRecord = getOrCreatePageURLRecord(pageURLOriginal.crossThreadString());
    321 
    322     // If pageRecord is NULL, one of two things is true -
    323     // 1 - The initial url import is incomplete and this pageURL has already been marked to be notified once it is complete if an iconURL exists
    324     // 2 - The initial url import IS complete and this pageURL has no icon
    325     if (!pageRecord)
    326         return String();
    327 
    328     // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check
    329     return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().threadsafeCopy() : String();
    330 }
    331 
    332 #ifdef CAN_THEME_URL_ICON
    333 static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
    334 {
    335      defaultIconRecord->loadImageFromResource("urlIcon");
    336 }
    337 #else
    338 static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
    339 {
    340     static const unsigned char defaultIconData[] = { 0x4D, 0x4D, 0x00, 0x2A, 0x00, 0x00, 0x03, 0x32, 0x80, 0x00, 0x20, 0x50, 0x38, 0x24, 0x16, 0x0D, 0x07, 0x84, 0x42, 0x61, 0x50, 0xB8,
    341         0x64, 0x08, 0x18, 0x0D, 0x0A, 0x0B, 0x84, 0xA2, 0xA1, 0xE2, 0x08, 0x5E, 0x39, 0x28, 0xAF, 0x48, 0x24, 0xD3, 0x53, 0x9A, 0x37, 0x1D, 0x18, 0x0E, 0x8A, 0x4B, 0xD1, 0x38,
    342         0xB0, 0x7C, 0x82, 0x07, 0x03, 0x82, 0xA2, 0xE8, 0x6C, 0x2C, 0x03, 0x2F, 0x02, 0x82, 0x41, 0xA1, 0xE2, 0xF8, 0xC8, 0x84, 0x68, 0x6D, 0x1C, 0x11, 0x0A, 0xB7, 0xFA, 0x91,
    343         0x6E, 0xD1, 0x7F, 0xAF, 0x9A, 0x4E, 0x87, 0xFB, 0x19, 0xB0, 0xEA, 0x7F, 0xA4, 0x95, 0x8C, 0xB7, 0xF9, 0xA9, 0x0A, 0xA9, 0x7F, 0x8C, 0x88, 0x66, 0x96, 0xD4, 0xCA, 0x69,
    344         0x2F, 0x00, 0x81, 0x65, 0xB0, 0x29, 0x90, 0x7C, 0xBA, 0x2B, 0x21, 0x1E, 0x5C, 0xE6, 0xB4, 0xBD, 0x31, 0xB6, 0xE7, 0x7A, 0xBF, 0xDD, 0x6F, 0x37, 0xD3, 0xFD, 0xD8, 0xF2,
    345         0xB6, 0xDB, 0xED, 0xAC, 0xF7, 0x03, 0xC5, 0xFE, 0x77, 0x53, 0xB6, 0x1F, 0xE6, 0x24, 0x8B, 0x1D, 0xFE, 0x26, 0x20, 0x9E, 0x1C, 0xE0, 0x80, 0x65, 0x7A, 0x18, 0x02, 0x01,
    346         0x82, 0xC5, 0xA0, 0xC0, 0xF1, 0x89, 0xBA, 0x23, 0x30, 0xAD, 0x1F, 0xE7, 0xE5, 0x5B, 0x6D, 0xFE, 0xE7, 0x78, 0x3E, 0x1F, 0xEE, 0x97, 0x8B, 0xE7, 0x37, 0x9D, 0xCF, 0xE7,
    347         0x92, 0x8B, 0x87, 0x0B, 0xFC, 0xA0, 0x8E, 0x68, 0x3F, 0xC6, 0x27, 0xA6, 0x33, 0xFC, 0x36, 0x5B, 0x59, 0x3F, 0xC1, 0x02, 0x63, 0x3B, 0x74, 0x00, 0x03, 0x07, 0x0B, 0x61,
    348         0x00, 0x20, 0x60, 0xC9, 0x08, 0x00, 0x1C, 0x25, 0x9F, 0xE0, 0x12, 0x8A, 0xD5, 0xFE, 0x6B, 0x4F, 0x35, 0x9F, 0xED, 0xD7, 0x4B, 0xD9, 0xFE, 0x8A, 0x59, 0xB8, 0x1F, 0xEC,
    349         0x56, 0xD3, 0xC1, 0xFE, 0x63, 0x4D, 0xF2, 0x83, 0xC6, 0xB6, 0x1B, 0xFC, 0x34, 0x68, 0x61, 0x3F, 0xC1, 0xA6, 0x25, 0xEB, 0xFC, 0x06, 0x58, 0x5C, 0x3F, 0xC0, 0x03, 0xE4,
    350         0xC3, 0xFC, 0x04, 0x0F, 0x1A, 0x6F, 0xE0, 0xE0, 0x20, 0xF9, 0x61, 0x7A, 0x02, 0x28, 0x2B, 0xBC, 0x46, 0x25, 0xF3, 0xFC, 0x66, 0x3D, 0x99, 0x27, 0xF9, 0x7E, 0x6B, 0x1D,
    351         0xC7, 0xF9, 0x2C, 0x5E, 0x1C, 0x87, 0xF8, 0xC0, 0x4D, 0x9A, 0xE7, 0xF8, 0xDA, 0x51, 0xB2, 0xC1, 0x68, 0xF2, 0x64, 0x1F, 0xE1, 0x50, 0xED, 0x0A, 0x04, 0x23, 0x79, 0x8A,
    352         0x7F, 0x82, 0xA3, 0x39, 0x80, 0x7F, 0x80, 0xC2, 0xB1, 0x5E, 0xF7, 0x04, 0x2F, 0xB2, 0x10, 0x02, 0x86, 0x63, 0xC9, 0xCC, 0x07, 0xBF, 0x87, 0xF8, 0x4A, 0x38, 0xAF, 0xC1,
    353         0x88, 0xF8, 0x66, 0x1F, 0xE1, 0xD9, 0x08, 0xD4, 0x8F, 0x25, 0x5B, 0x4A, 0x49, 0x97, 0x87, 0x39, 0xFE, 0x25, 0x12, 0x10, 0x68, 0xAA, 0x4A, 0x2F, 0x42, 0x29, 0x12, 0x69,
    354         0x9F, 0xE1, 0xC1, 0x00, 0x67, 0x1F, 0xE1, 0x58, 0xED, 0x00, 0x83, 0x23, 0x49, 0x82, 0x7F, 0x81, 0x21, 0xE0, 0xFC, 0x73, 0x21, 0x00, 0x50, 0x7D, 0x2B, 0x84, 0x03, 0x83,
    355         0xC2, 0x1B, 0x90, 0x06, 0x69, 0xFE, 0x23, 0x91, 0xAE, 0x50, 0x9A, 0x49, 0x32, 0xC2, 0x89, 0x30, 0xE9, 0x0A, 0xC4, 0xD9, 0xC4, 0x7F, 0x94, 0xA6, 0x51, 0xDE, 0x7F, 0x9D,
    356         0x07, 0x89, 0xF6, 0x7F, 0x91, 0x85, 0xCA, 0x88, 0x25, 0x11, 0xEE, 0x50, 0x7C, 0x43, 0x35, 0x21, 0x60, 0xF1, 0x0D, 0x82, 0x62, 0x39, 0x07, 0x2C, 0x20, 0xE0, 0x80, 0x72,
    357         0x34, 0x17, 0xA1, 0x80, 0xEE, 0xF0, 0x89, 0x24, 0x74, 0x1A, 0x2C, 0x93, 0xB3, 0x78, 0xCC, 0x52, 0x9D, 0x6A, 0x69, 0x56, 0xBB, 0x0D, 0x85, 0x69, 0xE6, 0x7F, 0x9E, 0x27,
    358         0xB9, 0xFD, 0x50, 0x54, 0x47, 0xF9, 0xCC, 0x78, 0x9F, 0x87, 0xF9, 0x98, 0x70, 0xB9, 0xC2, 0x91, 0x2C, 0x6D, 0x1F, 0xE1, 0xE1, 0x00, 0xBF, 0x02, 0xC1, 0xF5, 0x18, 0x84,
    359         0x01, 0xE1, 0x48, 0x8C, 0x42, 0x07, 0x43, 0xC9, 0x76, 0x7F, 0x8B, 0x04, 0xE4, 0xDE, 0x35, 0x95, 0xAB, 0xB0, 0xF0, 0x5C, 0x55, 0x23, 0xF9, 0x7E, 0x7E, 0x9F, 0xE4, 0x0C,
    360         0xA7, 0x55, 0x47, 0xC7, 0xF9, 0xE6, 0xCF, 0x1F, 0xE7, 0x93, 0x35, 0x52, 0x54, 0x63, 0x19, 0x46, 0x73, 0x1F, 0xE2, 0x61, 0x08, 0xF0, 0x82, 0xE1, 0x80, 0x92, 0xF9, 0x20,
    361         0xC0, 0x28, 0x18, 0x0A, 0x05, 0xA1, 0xA2, 0xF8, 0x6E, 0xDB, 0x47, 0x49, 0xFE, 0x3E, 0x17, 0xB6, 0x61, 0x13, 0x1A, 0x29, 0x26, 0xA9, 0xFE, 0x7F, 0x92, 0x70, 0x69, 0xFE,
    362         0x4C, 0x2F, 0x55, 0x01, 0xF1, 0x54, 0xD4, 0x35, 0x49, 0x4A, 0x69, 0x59, 0x83, 0x81, 0x58, 0x76, 0x9F, 0xE2, 0x20, 0xD6, 0x4C, 0x9B, 0xA0, 0x48, 0x1E, 0x0B, 0xB7, 0x48,
    363         0x58, 0x26, 0x11, 0x06, 0x42, 0xE8, 0xA4, 0x40, 0x17, 0x27, 0x39, 0x00, 0x60, 0x2D, 0xA4, 0xC3, 0x2C, 0x7F, 0x94, 0x56, 0xE4, 0xE1, 0x77, 0x1F, 0xE5, 0xB9, 0xD7, 0x66,
    364         0x1E, 0x07, 0xB3, 0x3C, 0x63, 0x1D, 0x35, 0x49, 0x0E, 0x63, 0x2D, 0xA2, 0xF1, 0x12, 0x60, 0x1C, 0xE0, 0xE0, 0x52, 0x1B, 0x8B, 0xAC, 0x38, 0x0E, 0x07, 0x03, 0x60, 0x28,
    365         0x1C, 0x0E, 0x87, 0x00, 0xF0, 0x66, 0x27, 0x11, 0xA2, 0xC1, 0x02, 0x5A, 0x1C, 0xE4, 0x21, 0x83, 0x1F, 0x13, 0x86, 0xFA, 0xD2, 0x55, 0x1D, 0xD6, 0x61, 0xBC, 0x77, 0xD3,
    366         0xE6, 0x91, 0xCB, 0x4C, 0x90, 0xA6, 0x25, 0xB8, 0x2F, 0x90, 0xC5, 0xA9, 0xCE, 0x12, 0x07, 0x02, 0x91, 0x1B, 0x9F, 0x68, 0x00, 0x16, 0x76, 0x0D, 0xA1, 0x00, 0x08, 0x06,
    367         0x03, 0x81, 0xA0, 0x20, 0x1A, 0x0D, 0x06, 0x80, 0x30, 0x24, 0x12, 0x89, 0x20, 0x98, 0x4A, 0x1F, 0x0F, 0x21, 0xA0, 0x9E, 0x36, 0x16, 0xC2, 0x88, 0xE6, 0x48, 0x9B, 0x83,
    368         0x31, 0x1C, 0x55, 0x1E, 0x43, 0x59, 0x1A, 0x56, 0x1E, 0x42, 0xF0, 0xFA, 0x4D, 0x1B, 0x9B, 0x08, 0xDC, 0x5B, 0x02, 0xA1, 0x30, 0x7E, 0x3C, 0xEE, 0x5B, 0xA6, 0xDD, 0xB8,
    369         0x6D, 0x5B, 0x62, 0xB7, 0xCD, 0xF3, 0x9C, 0xEA, 0x04, 0x80, 0x80, 0x00, 0x00, 0x0E, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x01,
    370         0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00, 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0xE0, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00,
    371         0x00, 0x01, 0x00, 0x05, 0x00, 0x00, 0x01, 0x06, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x01, 0x11, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
    372         0x00, 0x08, 0x01, 0x15, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x01, 0x16, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00, 0x01, 0x17,
    373         0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0x29, 0x01, 0x1A, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x03, 0xE8, 0x01, 0x1B, 0x00, 0x05, 0x00, 0x00,
    374         0x00, 0x01, 0x00, 0x00, 0x03, 0xF0, 0x01, 0x1C, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x28, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02,
    375         0x00, 0x00, 0x01, 0x52, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x0A,
    376         0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 };
    377 
    378     DEFINE_STATIC_LOCAL(RefPtr<SharedBuffer>, defaultIconBuffer, (SharedBuffer::create(defaultIconData, sizeof(defaultIconData))));
    379     defaultIconRecord->setImageData(defaultIconBuffer);
    380 }
    381 #endif
    382 
    383 Image* IconDatabase::defaultIcon(const IntSize& size)
    384 {
    385     ASSERT_NOT_SYNC_THREAD();
    386 
    387 
    388     if (!m_defaultIconRecord) {
    389         m_defaultIconRecord = IconRecord::create("urlIcon");
    390         loadDefaultIconRecord(m_defaultIconRecord.get());
    391     }
    392 
    393     return m_defaultIconRecord->image(size);
    394 }
    395 
    396 
    397 void IconDatabase::retainIconForPageURL(const String& pageURLOriginal)
    398 {
    399     ASSERT_NOT_SYNC_THREAD();
    400 
    401     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
    402 
    403     if (!isEnabled() || pageURLOriginal.isEmpty())
    404         return;
    405 
    406     MutexLocker locker(m_urlAndIconLock);
    407 
    408     PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal);
    409 
    410     String pageURL;
    411 
    412     if (!record) {
    413         pageURL = pageURLOriginal.crossThreadString();
    414 
    415         record = new PageURLRecord(pageURL);
    416         m_pageURLToRecordMap.set(pageURL, record);
    417     }
    418 
    419     if (!record->retain()) {
    420         if (pageURL.isNull())
    421             pageURL = pageURLOriginal.crossThreadString();
    422 
    423         // This page just had its retain count bumped from 0 to 1 - Record that fact
    424         m_retainedPageURLs.add(pageURL);
    425 
    426         // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
    427         // so we bail here and skip those steps
    428         if (!m_iconURLImportComplete)
    429             return;
    430 
    431         MutexLocker locker(m_pendingSyncLock);
    432         // If this pageURL waiting to be sync'ed, update the sync record
    433         // This saves us in the case where a page was ready to be deleted from the database but was just retained - so theres no need to delete it!
    434         if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) {
    435             LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data());
    436             m_pageURLsPendingSync.set(pageURL, record->snapshot());
    437         }
    438     }
    439 }
    440 
    441 void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal)
    442 {
    443     ASSERT_NOT_SYNC_THREAD();
    444 
    445     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
    446 
    447     if (!isEnabled() || pageURLOriginal.isEmpty())
    448         return;
    449 
    450     MutexLocker locker(m_urlAndIconLock);
    451 
    452     // Check if this pageURL is actually retained
    453     if (!m_retainedPageURLs.contains(pageURLOriginal)) {
    454         LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data());
    455         return;
    456     }
    457 
    458     // Get its retain count - if it's retained, we'd better have a PageURLRecord for it
    459     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
    460     ASSERT(pageRecord);
    461     LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1);
    462     ASSERT(pageRecord->retainCount() > 0);
    463 
    464     // If it still has a positive retain count, store the new count and bail
    465     if (pageRecord->release())
    466         return;
    467 
    468     // This pageRecord has now been fully released.  Do the appropriate cleanup
    469     LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data());
    470     m_pageURLToRecordMap.remove(pageURLOriginal);
    471     m_retainedPageURLs.remove(pageURLOriginal);
    472 
    473     // Grab the iconRecord for later use (and do a sanity check on it for kicks)
    474     IconRecord* iconRecord = pageRecord->iconRecord();
    475 
    476     ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord));
    477 
    478     {
    479         MutexLocker locker(m_pendingReadingLock);
    480 
    481         // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results
    482         if (!m_iconURLImportComplete)
    483             m_pageURLsPendingImport.remove(pageURLOriginal);
    484         m_pageURLsInterestedInIcons.remove(pageURLOriginal);
    485 
    486         // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore
    487         if (iconRecord && iconRecord->hasOneRef()) {
    488             m_iconURLToRecordMap.remove(iconRecord->iconURL());
    489             m_iconsPendingReading.remove(iconRecord);
    490         }
    491     }
    492 
    493     // Mark stuff for deletion from the database only if we're not in private browsing
    494     if (!m_privateBrowsingEnabled) {
    495         MutexLocker locker(m_pendingSyncLock);
    496         m_pageURLsPendingSync.set(pageURLOriginal.crossThreadString(), pageRecord->snapshot(true));
    497 
    498         // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to
    499         // be marked for deletion
    500         if (iconRecord && iconRecord->hasOneRef())
    501             m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
    502     }
    503 
    504     delete pageRecord;
    505 
    506     if (isOpen())
    507         scheduleOrDeferSyncTimer();
    508 }
    509 
    510 void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal)
    511 {
    512     ASSERT_NOT_SYNC_THREAD();
    513 
    514     // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first
    515 
    516     if (!isOpen() || iconURLOriginal.isEmpty())
    517         return;
    518 
    519     RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : 0;
    520     String iconURL = iconURLOriginal.crossThreadString();
    521 
    522     Vector<String> pageURLs;
    523     {
    524         MutexLocker locker(m_urlAndIconLock);
    525 
    526         // If this icon was pending a read, remove it from that set because this new data should override what is on disk
    527         RefPtr<IconRecord> icon = m_iconURLToRecordMap.get(iconURL);
    528         if (icon) {
    529             MutexLocker locker(m_pendingReadingLock);
    530             m_iconsPendingReading.remove(icon.get());
    531         } else
    532             icon = getOrCreateIconRecord(iconURL);
    533 
    534         // Update the data and set the time stamp
    535         icon->setImageData(data);
    536         icon->setTimestamp((int)currentTime());
    537 
    538         // Copy the current retaining pageURLs - if any - to notify them of the change
    539         pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end());
    540 
    541         // Mark the IconRecord as requiring an update to the database only if private browsing is disabled
    542         if (!m_privateBrowsingEnabled) {
    543             MutexLocker locker(m_pendingSyncLock);
    544             m_iconsPendingSync.set(iconURL, icon->snapshot());
    545         }
    546 
    547         if (icon->hasOneRef()) {
    548             ASSERT(icon->retainingPageURLs().isEmpty());
    549             LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(icon->iconURL()).ascii().data());
    550             m_iconURLToRecordMap.remove(icon->iconURL());
    551         }
    552     }
    553 
    554     // Send notification out regarding all PageURLs that retain this icon
    555     // But not if we're on the sync thread because that implies this mapping
    556     // comes from the initial import which we don't want notifications for
    557     if (!IS_ICON_SYNC_THREAD()) {
    558         // Start the timer to commit this change - or further delay the timer if it was already started
    559         scheduleOrDeferSyncTimer();
    560 
    561         // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
    562         // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
    563         AutodrainedPool pool(25);
    564 
    565         for (unsigned i = 0; i < pageURLs.size(); ++i) {
    566             LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data());
    567             m_client->dispatchDidAddIconForPageURL(pageURLs[i]);
    568 
    569             pool.cycle();
    570         }
    571     }
    572 }
    573 
    574 void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal)
    575 {
    576     ASSERT_NOT_SYNC_THREAD();
    577 
    578     // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first
    579 
    580     ASSERT(!iconURLOriginal.isEmpty());
    581 
    582     if (!isOpen() || pageURLOriginal.isEmpty())
    583         return;
    584 
    585     String iconURL, pageURL;
    586 
    587     {
    588         MutexLocker locker(m_urlAndIconLock);
    589 
    590         PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
    591 
    592         // If the urls already map to each other, bail.
    593         // This happens surprisingly often, and seems to cream iBench performance
    594         if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal)
    595             return;
    596 
    597         pageURL = pageURLOriginal.crossThreadString();
    598         iconURL = iconURLOriginal.crossThreadString();
    599 
    600         if (!pageRecord) {
    601             pageRecord = new PageURLRecord(pageURL);
    602             m_pageURLToRecordMap.set(pageURL, pageRecord);
    603         }
    604 
    605         RefPtr<IconRecord> iconRecord = pageRecord->iconRecord();
    606 
    607         // Otherwise, set the new icon record for this page
    608         pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
    609 
    610         // If the current icon has only a single ref left, it is about to get wiped out.
    611         // Remove it from the in-memory records and don't bother reading it in from disk anymore
    612         if (iconRecord && iconRecord->hasOneRef()) {
    613             ASSERT(iconRecord->retainingPageURLs().size() == 0);
    614             LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data());
    615             m_iconURLToRecordMap.remove(iconRecord->iconURL());
    616             MutexLocker locker(m_pendingReadingLock);
    617             m_iconsPendingReading.remove(iconRecord.get());
    618         }
    619 
    620         // And mark this mapping to be added to the database
    621         if (!m_privateBrowsingEnabled) {
    622             MutexLocker locker(m_pendingSyncLock);
    623             m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot());
    624 
    625             // If the icon is on its last ref, mark it for deletion
    626             if (iconRecord && iconRecord->hasOneRef())
    627                 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
    628         }
    629     }
    630 
    631     // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping
    632     // comes from the initial import which we don't want notifications for
    633     if (!IS_ICON_SYNC_THREAD()) {
    634         // Start the timer to commit this change - or further delay the timer if it was already started
    635         scheduleOrDeferSyncTimer();
    636 
    637         LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data());
    638         AutodrainedPool pool;
    639         m_client->dispatchDidAddIconForPageURL(pageURL);
    640     }
    641 }
    642 
    643 IconLoadDecision IconDatabase::loadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader)
    644 {
    645     ASSERT_NOT_SYNC_THREAD();
    646 
    647     if (!isOpen() || iconURL.isEmpty())
    648         return IconLoadNo;
    649 
    650     // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord:
    651     // 1 - When we read the icon urls from disk, getting the timeStamp at the same time
    652     // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time
    653     {
    654         MutexLocker locker(m_urlAndIconLock);
    655         if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) {
    656             LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord");
    657             return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo;
    658         }
    659     }
    660 
    661     // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now
    662     MutexLocker readingLocker(m_pendingReadingLock);
    663     if (m_iconURLImportComplete)
    664         return IconLoadYes;
    665 
    666     // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says
    667     // "You might be asked to load this later, so flag that"
    668     LOG(IconDatabase, "Don't know if we should load %s or not - adding %p to the set of document loaders waiting on a decision", iconURL.ascii().data(), notificationDocumentLoader);
    669     m_loadersPendingDecision.add(notificationDocumentLoader);
    670 
    671     return IconLoadUnknown;
    672 }
    673 
    674 bool IconDatabase::iconDataKnownForIconURL(const String& iconURL)
    675 {
    676     ASSERT_NOT_SYNC_THREAD();
    677 
    678     MutexLocker locker(m_urlAndIconLock);
    679     if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
    680         return icon->imageDataStatus() != ImageDataStatusUnknown;
    681 
    682     return false;
    683 }
    684 
    685 void IconDatabase::setEnabled(bool enabled)
    686 {
    687     ASSERT_NOT_SYNC_THREAD();
    688 
    689     if (!enabled && isOpen())
    690         close();
    691     m_isEnabled = enabled;
    692 }
    693 
    694 bool IconDatabase::isEnabled() const
    695 {
    696     ASSERT_NOT_SYNC_THREAD();
    697 
    698      return m_isEnabled;
    699 }
    700 
    701 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
    702 {
    703     m_privateBrowsingEnabled = flag;
    704 }
    705 
    706 bool IconDatabase::isPrivateBrowsingEnabled() const
    707 {
    708     return m_privateBrowsingEnabled;
    709 }
    710 
    711 void IconDatabase::delayDatabaseCleanup()
    712 {
    713     ++databaseCleanupCounter;
    714     if (databaseCleanupCounter == 1)
    715         LOG(IconDatabase, "Database cleanup is now DISABLED");
    716 }
    717 
    718 void IconDatabase::allowDatabaseCleanup()
    719 {
    720     if (--databaseCleanupCounter < 0)
    721         databaseCleanupCounter = 0;
    722     if (databaseCleanupCounter == 0)
    723         LOG(IconDatabase, "Database cleanup is now ENABLED");
    724 }
    725 
    726 void IconDatabase::checkIntegrityBeforeOpening()
    727 {
    728     checkIntegrityOnOpen = true;
    729 }
    730 
    731 size_t IconDatabase::pageURLMappingCount()
    732 {
    733     MutexLocker locker(m_urlAndIconLock);
    734     return m_pageURLToRecordMap.size();
    735 }
    736 
    737 size_t IconDatabase::retainedPageURLCount()
    738 {
    739     MutexLocker locker(m_urlAndIconLock);
    740     return m_retainedPageURLs.size();
    741 }
    742 
    743 size_t IconDatabase::iconRecordCount()
    744 {
    745     MutexLocker locker(m_urlAndIconLock);
    746     return m_iconURLToRecordMap.size();
    747 }
    748 
    749 size_t IconDatabase::iconRecordCountWithData()
    750 {
    751     MutexLocker locker(m_urlAndIconLock);
    752     size_t result = 0;
    753 
    754     HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
    755     HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
    756 
    757     for (; i != end; ++i)
    758         result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
    759 
    760     return result;
    761 }
    762 
    763 IconDatabase::IconDatabase()
    764     : m_syncTimer(this, &IconDatabase::syncTimerFired)
    765     , m_syncThreadRunning(false)
    766     , m_isEnabled(false)
    767     , m_privateBrowsingEnabled(false)
    768     , m_threadTerminationRequested(false)
    769     , m_removeIconsRequested(false)
    770     , m_iconURLImportComplete(false)
    771     , m_initialPruningComplete(false)
    772     , m_client(defaultClient())
    773     , m_imported(false)
    774     , m_isImportedSet(false)
    775 {
    776     ASSERT(isMainThread());
    777 }
    778 
    779 IconDatabase::~IconDatabase()
    780 {
    781     ASSERT_NOT_REACHED();
    782 }
    783 
    784 void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context)
    785 {
    786     static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions();
    787 }
    788 
    789 void IconDatabase::notifyPendingLoadDecisions()
    790 {
    791     ASSERT_NOT_SYNC_THREAD();
    792 
    793     // This method should only be called upon completion of the initial url import from the database
    794     ASSERT(m_iconURLImportComplete);
    795     LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
    796 
    797     HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
    798     HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
    799 
    800     for (; i != end; ++i)
    801         if ((*i)->refCount() > 1)
    802             (*i)->iconLoadDecisionAvailable();
    803 
    804     m_loadersPendingDecision.clear();
    805 }
    806 
    807 void IconDatabase::wakeSyncThread()
    808 {
    809     // The following is balanced by the call to enableSuddenTermination in the
    810     // syncThreadMainLoop function.
    811     // FIXME: It would be better to only disable sudden termination if we have
    812     // something to write, not just if we have something to read.
    813     disableSuddenTermination();
    814 
    815     MutexLocker locker(m_syncLock);
    816     m_syncCondition.signal();
    817 }
    818 
    819 void IconDatabase::scheduleOrDeferSyncTimer()
    820 {
    821     ASSERT_NOT_SYNC_THREAD();
    822 
    823     if (!m_syncTimer.isActive()) {
    824         // The following is balanced by the call to enableSuddenTermination in the
    825         // syncTimerFired function.
    826         disableSuddenTermination();
    827     }
    828 
    829     m_syncTimer.startOneShot(updateTimerDelay);
    830 }
    831 
    832 void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
    833 {
    834     ASSERT_NOT_SYNC_THREAD();
    835     wakeSyncThread();
    836 
    837     // The following is balanced by the call to disableSuddenTermination in the
    838     // scheduleOrDeferSyncTimer function.
    839     enableSuddenTermination();
    840 }
    841 
    842 // ******************
    843 // *** Any Thread ***
    844 // ******************
    845 
    846 bool IconDatabase::isOpen() const
    847 {
    848     MutexLocker locker(m_syncLock);
    849     return m_syncDB.isOpen();
    850 }
    851 
    852 String IconDatabase::databasePath() const
    853 {
    854     MutexLocker locker(m_syncLock);
    855     return m_completeDatabasePath.threadsafeCopy();
    856 }
    857 
    858 String IconDatabase::defaultDatabaseFilename()
    859 {
    860     DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db"));
    861     return defaultDatabaseFilename.threadsafeCopy();
    862 }
    863 
    864 // Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import"
    865 PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL)
    866 {
    867     // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method
    868     ASSERT(!m_urlAndIconLock.tryLock());
    869 
    870     if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
    871         return icon;
    872 
    873     RefPtr<IconRecord> newIcon = IconRecord::create(iconURL);
    874     m_iconURLToRecordMap.set(iconURL, newIcon.get());
    875 
    876     return newIcon.release();
    877 }
    878 
    879 // This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification
    880 PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL)
    881 {
    882     // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method
    883     ASSERT(!m_urlAndIconLock.tryLock());
    884 
    885     if (pageURL.isEmpty())
    886         return 0;
    887 
    888     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
    889 
    890     MutexLocker locker(m_pendingReadingLock);
    891     if (!m_iconURLImportComplete) {
    892         // If the initial import of all URLs hasn't completed and we have no page record, we assume we *might* know about this later and create a record for it
    893         if (!pageRecord) {
    894             LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data());
    895             pageRecord = new PageURLRecord(pageURL);
    896             m_pageURLToRecordMap.set(pageURL, pageRecord);
    897         }
    898 
    899         // If the pageRecord for this page does not have an iconRecord attached to it, then it is a new pageRecord still awaiting the initial import
    900         // Mark the URL as "interested in the result of the import" then bail
    901         if (!pageRecord->iconRecord()) {
    902             m_pageURLsPendingImport.add(pageURL);
    903             return 0;
    904         }
    905     }
    906 
    907     // We've done the initial import of all URLs known in the database.  If this record doesn't exist now, it never will
    908      return pageRecord;
    909 }
    910 
    911 
    912 // ************************
    913 // *** Sync Thread Only ***
    914 // ************************
    915 
    916 void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL)
    917 {
    918     ASSERT_ICON_SYNC_THREAD();
    919 
    920     // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty
    921     ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty());
    922 
    923     setIconURLForPageURLInSQLDatabase(iconURL, pageURL);
    924 }
    925 
    926 void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL)
    927 {
    928     ASSERT_ICON_SYNC_THREAD();
    929 
    930     ASSERT(!iconURL.isEmpty());
    931 
    932     writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get()));
    933 }
    934 
    935 bool IconDatabase::shouldStopThreadActivity() const
    936 {
    937     ASSERT_ICON_SYNC_THREAD();
    938 
    939     return m_threadTerminationRequested || m_removeIconsRequested;
    940 }
    941 
    942 void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase)
    943 {
    944     IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase);
    945 
    946     return iconDB->iconDatabaseSyncThread();
    947 }
    948 
    949 void* IconDatabase::iconDatabaseSyncThread()
    950 {
    951     // The call to create this thread might not complete before the thread actually starts, so we might fail this ASSERT_ICON_SYNC_THREAD() because the pointer
    952     // to our thread structure hasn't been filled in yet.
    953     // To fix this, the main thread acquires this lock before creating us, then releases the lock after creation is complete.  A quick lock/unlock cycle here will
    954     // prevent us from running before that call completes
    955     m_syncLock.lock();
    956     m_syncLock.unlock();
    957 
    958     ASSERT_ICON_SYNC_THREAD();
    959 
    960     LOG(IconDatabase, "(THREAD) IconDatabase sync thread started");
    961 
    962 #ifndef NDEBUG
    963     double startTime = currentTime();
    964 #endif
    965 
    966     // Need to create the database path if it doesn't already exist
    967     makeAllDirectories(m_databaseDirectory);
    968 
    969     // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies
    970     // us to do an integrity check
    971     String journalFilename = m_completeDatabasePath + "-journal";
    972     if (!checkIntegrityOnOpen) {
    973         AutodrainedPool pool;
    974         checkIntegrityOnOpen = fileExists(journalFilename);
    975     }
    976 
    977     {
    978         MutexLocker locker(m_syncLock);
    979         if (!m_syncDB.open(m_completeDatabasePath)) {
    980             LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
    981             return 0;
    982         }
    983     }
    984 
    985     if (shouldStopThreadActivity())
    986         return syncThreadMainLoop();
    987 
    988 #ifndef NDEBUG
    989     double timeStamp = currentTime();
    990     LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime);
    991 #endif
    992 
    993     performOpenInitialization();
    994     if (shouldStopThreadActivity())
    995         return syncThreadMainLoop();
    996 
    997 #ifndef NDEBUG
    998     double newStamp = currentTime();
    999     LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
   1000     timeStamp = newStamp;
   1001 #endif
   1002 
   1003     if (!imported()) {
   1004         LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure");
   1005         SQLiteTransaction importTransaction(m_syncDB);
   1006         importTransaction.begin();
   1007 
   1008         // Commit the transaction only if the import completes (the import should be atomic)
   1009         if (m_client->performImport()) {
   1010             setImported(true);
   1011             importTransaction.commit();
   1012         } else {
   1013             LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled");
   1014             importTransaction.rollback();
   1015         }
   1016 
   1017         if (shouldStopThreadActivity())
   1018             return syncThreadMainLoop();
   1019 
   1020 #ifndef NDEBUG
   1021         newStamp = currentTime();
   1022         LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
   1023         timeStamp = newStamp;
   1024 #endif
   1025     }
   1026 
   1027     // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories)
   1028     // while (currentTime() - timeStamp < 10);
   1029 
   1030     // Read in URL mappings from the database
   1031     LOG(IconDatabase, "(THREAD) Starting iconURL import");
   1032     performURLImport();
   1033 
   1034     if (shouldStopThreadActivity())
   1035         return syncThreadMainLoop();
   1036 
   1037 #ifndef NDEBUG
   1038     newStamp = currentTime();
   1039     LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds.  Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
   1040 #endif
   1041 
   1042     LOG(IconDatabase, "(THREAD) Beginning sync");
   1043     return syncThreadMainLoop();
   1044 }
   1045 
   1046 static int databaseVersionNumber(SQLiteDatabase& db)
   1047 {
   1048     return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
   1049 }
   1050 
   1051 static bool isValidDatabase(SQLiteDatabase& db)
   1052 {
   1053 
   1054     // These four tables should always exist in a valid db
   1055     if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
   1056         return false;
   1057 
   1058     if (databaseVersionNumber(db) < currentDatabaseVersion) {
   1059         LOG(IconDatabase, "DB version is not found or below expected valid version");
   1060         return false;
   1061     }
   1062 
   1063     return true;
   1064 }
   1065 
   1066 static void createDatabaseTables(SQLiteDatabase& db)
   1067 {
   1068     if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
   1069         LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1070         db.close();
   1071         return;
   1072     }
   1073     if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
   1074         LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1075         db.close();
   1076         return;
   1077     }
   1078     if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) {
   1079         LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1080         db.close();
   1081         return;
   1082     }
   1083     if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
   1084         LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1085         db.close();
   1086         return;
   1087     }
   1088     if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
   1089         LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1090         db.close();
   1091         return;
   1092     }
   1093     if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
   1094         LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1095         db.close();
   1096         return;
   1097     }
   1098     if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
   1099         LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
   1100         db.close();
   1101         return;
   1102     }
   1103     if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
   1104         LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
   1105         db.close();
   1106         return;
   1107     }
   1108 }
   1109 
   1110 void IconDatabase::performOpenInitialization()
   1111 {
   1112     ASSERT_ICON_SYNC_THREAD();
   1113 
   1114     if (!isOpen())
   1115         return;
   1116 
   1117     if (checkIntegrityOnOpen) {
   1118         checkIntegrityOnOpen = false;
   1119         if (!checkIntegrity()) {
   1120             LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
   1121 
   1122             m_syncDB.close();
   1123 
   1124             {
   1125                 MutexLocker locker(m_syncLock);
   1126                 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
   1127                 deleteFile(m_completeDatabasePath + "-journal");
   1128                 deleteFile(m_completeDatabasePath);
   1129             }
   1130 
   1131             // Reopen the write database, creating it from scratch
   1132             if (!m_syncDB.open(m_completeDatabasePath)) {
   1133                 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
   1134                 return;
   1135             }
   1136         }
   1137     }
   1138 
   1139     int version = databaseVersionNumber(m_syncDB);
   1140 
   1141     if (version > currentDatabaseVersion) {
   1142         LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
   1143         m_syncDB.close();
   1144         m_threadTerminationRequested = true;
   1145         return;
   1146     }
   1147 
   1148     if (!isValidDatabase(m_syncDB)) {
   1149         LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data());
   1150         m_syncDB.clearAllTables();
   1151         createDatabaseTables(m_syncDB);
   1152     }
   1153 
   1154     // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
   1155     if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
   1156         LOG_ERROR("SQLite database could not set cache_size");
   1157 }
   1158 
   1159 bool IconDatabase::checkIntegrity()
   1160 {
   1161     ASSERT_ICON_SYNC_THREAD();
   1162 
   1163     SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
   1164     if (integrity.prepare() != SQLResultOk) {
   1165         LOG_ERROR("checkIntegrity failed to execute");
   1166         return false;
   1167     }
   1168 
   1169     int resultCode = integrity.step();
   1170     if (resultCode == SQLResultOk)
   1171         return true;
   1172 
   1173     if (resultCode != SQLResultRow)
   1174         return false;
   1175 
   1176     int columns = integrity.columnCount();
   1177     if (columns != 1) {
   1178         LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
   1179         return false;
   1180     }
   1181 
   1182     String resultText = integrity.getColumnText(0);
   1183 
   1184     // A successful, no-error integrity check will be "ok" - all other strings imply failure
   1185     if (resultText == "ok")
   1186         return true;
   1187 
   1188     LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
   1189     return false;
   1190 }
   1191 
   1192 void IconDatabase::performURLImport()
   1193 {
   1194     ASSERT_ICON_SYNC_THREAD();
   1195 
   1196     SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
   1197 
   1198     if (query.prepare() != SQLResultOk) {
   1199         LOG_ERROR("Unable to prepare icon url import query");
   1200         return;
   1201     }
   1202 
   1203     // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
   1204     // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
   1205     AutodrainedPool pool(25);
   1206 
   1207     int result = query.step();
   1208     while (result == SQLResultRow) {
   1209         String pageURL = query.getColumnText(0);
   1210         String iconURL = query.getColumnText(1);
   1211 
   1212         {
   1213             MutexLocker locker(m_urlAndIconLock);
   1214 
   1215             PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
   1216 
   1217             // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
   1218             // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
   1219             // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
   1220             // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
   1221             // in - we'll prune it later instead!
   1222             if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) {
   1223                 pageRecord = new PageURLRecord(pageURL);
   1224                 m_pageURLToRecordMap.set(pageURL, pageRecord);
   1225             }
   1226 
   1227             if (pageRecord) {
   1228                 IconRecord* currentIcon = pageRecord->iconRecord();
   1229 
   1230                 if (!currentIcon || currentIcon->iconURL() != iconURL) {
   1231                     pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
   1232                     currentIcon = pageRecord->iconRecord();
   1233                 }
   1234 
   1235                 // Regardless, the time stamp from disk still takes precedence.  Until we read this icon from disk, we didn't think we'd seen it before
   1236                 // so we marked the timestamp as "now", but it's really much older
   1237                 currentIcon->setTimestamp(query.getColumnInt(2));
   1238             }
   1239         }
   1240 
   1241         // FIXME: Currently the WebKit API supports 1 type of notification that is sent whenever we get an Icon URL for a Page URL.  We might want to re-purpose it to work for
   1242         // getting the actually icon itself also (so each pageurl would get this notification twice) or we might want to add a second type of notification -
   1243         // one for the URL and one for the Image itself
   1244         // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
   1245         {
   1246             MutexLocker locker(m_pendingReadingLock);
   1247             if (m_pageURLsPendingImport.contains(pageURL)) {
   1248                 m_client->dispatchDidAddIconForPageURL(pageURL);
   1249                 m_pageURLsPendingImport.remove(pageURL);
   1250 
   1251                 pool.cycle();
   1252             }
   1253         }
   1254 
   1255         // Stop the import at any time of the thread has been asked to shutdown
   1256         if (shouldStopThreadActivity()) {
   1257             LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
   1258             return;
   1259         }
   1260 
   1261         result = query.step();
   1262     }
   1263 
   1264     if (result != SQLResultDone)
   1265         LOG(IconDatabase, "Error reading page->icon url mappings from database");
   1266 
   1267     // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
   1268     // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
   1269     Vector<String> urls;
   1270     {
   1271         MutexLocker locker(m_pendingReadingLock);
   1272 
   1273         urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
   1274         m_pageURLsPendingImport.clear();
   1275         m_iconURLImportComplete = true;
   1276     }
   1277 
   1278     Vector<String> urlsToNotify;
   1279 
   1280     // Loop through the urls pending import
   1281     // Remove unretained ones if database cleanup is allowed
   1282     // Keep a set of ones that are retained and pending notification
   1283 
   1284     {
   1285         MutexLocker locker(m_urlAndIconLock);
   1286 
   1287         for (unsigned i = 0; i < urls.size(); ++i) {
   1288             if (!m_retainedPageURLs.contains(urls[i])) {
   1289                 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
   1290                 if (record && !databaseCleanupCounter) {
   1291                     m_pageURLToRecordMap.remove(urls[i]);
   1292                     IconRecord* iconRecord = record->iconRecord();
   1293 
   1294                     // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
   1295                     // reading anything related to it
   1296                     if (iconRecord && iconRecord->hasOneRef()) {
   1297                         m_iconURLToRecordMap.remove(iconRecord->iconURL());
   1298 
   1299                         {
   1300                             MutexLocker locker(m_pendingReadingLock);
   1301                             m_pageURLsInterestedInIcons.remove(urls[i]);
   1302                             m_iconsPendingReading.remove(iconRecord);
   1303                         }
   1304                         {
   1305                             MutexLocker locker(m_pendingSyncLock);
   1306                             m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
   1307                         }
   1308                     }
   1309 
   1310                     delete record;
   1311                 }
   1312             } else {
   1313                 urlsToNotify.append(urls[i]);
   1314             }
   1315         }
   1316     }
   1317 
   1318     LOG(IconDatabase, "Notifying %zu interested page URLs that their icon URL is known due to the import", urlsToNotify.size());
   1319     // Now that we don't hold any locks, perform the actual notifications
   1320     for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
   1321         LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
   1322         m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]);
   1323         if (shouldStopThreadActivity())
   1324             return;
   1325 
   1326         pool.cycle();
   1327     }
   1328 
   1329     // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
   1330     callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this);
   1331 }
   1332 
   1333 void* IconDatabase::syncThreadMainLoop()
   1334 {
   1335     ASSERT_ICON_SYNC_THREAD();
   1336 
   1337     bool shouldReenableSuddenTermination = false;
   1338 
   1339     m_syncLock.lock();
   1340 
   1341     // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
   1342     while (!m_threadTerminationRequested) {
   1343         m_syncLock.unlock();
   1344 
   1345 #ifndef NDEBUG
   1346         double timeStamp = currentTime();
   1347 #endif
   1348         LOG(IconDatabase, "(THREAD) Main work loop starting");
   1349 
   1350         // If we should remove all icons, do it now.  This is an uninteruptible procedure that we will always do before quitting if it is requested
   1351         if (m_removeIconsRequested) {
   1352             removeAllIconsOnThread();
   1353             m_removeIconsRequested = false;
   1354         }
   1355 
   1356         // Then, if the thread should be quitting, quit now!
   1357         if (m_threadTerminationRequested)
   1358             break;
   1359 
   1360         bool didAnyWork = true;
   1361         while (didAnyWork) {
   1362             bool didWrite = writeToDatabase();
   1363             if (shouldStopThreadActivity())
   1364                 break;
   1365 
   1366             didAnyWork = readFromDatabase();
   1367             if (shouldStopThreadActivity())
   1368                 break;
   1369 
   1370             // Prune unretained icons after the first time we sync anything out to the database
   1371             // This way, pruning won't be the only operation we perform to the database by itself
   1372             // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
   1373             // or if private browsing is enabled
   1374             // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
   1375             // has asked to delay pruning
   1376             static bool prunedUnretainedIcons = false;
   1377             if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
   1378 #ifndef NDEBUG
   1379                 double time = currentTime();
   1380 #endif
   1381                 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
   1382 
   1383                 pruneUnretainedIcons();
   1384 
   1385                 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
   1386 
   1387                 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
   1388                 // to mark prunedUnretainedIcons true because we're about to terminate anyway
   1389                 prunedUnretainedIcons = true;
   1390             }
   1391 
   1392             didAnyWork = didAnyWork || didWrite;
   1393             if (shouldStopThreadActivity())
   1394                 break;
   1395         }
   1396 
   1397 #ifndef NDEBUG
   1398         double newstamp = currentTime();
   1399         LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
   1400 #endif
   1401 
   1402         m_syncLock.lock();
   1403 
   1404         // There is some condition that is asking us to stop what we're doing now and handle a special case
   1405         // This is either removing all icons, or shutting down the thread to quit the app
   1406         // We handle those at the top of this main loop so continue to jump back up there
   1407         if (shouldStopThreadActivity())
   1408             continue;
   1409 
   1410         if (shouldReenableSuddenTermination) {
   1411             // The following is balanced by the call to disableSuddenTermination in the
   1412             // wakeSyncThread function. Any time we wait on the condition, we also have
   1413             // to enableSuddenTermation, after doing the next batch of work.
   1414             enableSuddenTermination();
   1415         }
   1416 
   1417         m_syncCondition.wait(m_syncLock);
   1418 
   1419         shouldReenableSuddenTermination = true;
   1420     }
   1421 
   1422     m_syncLock.unlock();
   1423 
   1424     // Thread is terminating at this point
   1425     cleanupSyncThread();
   1426 
   1427     if (shouldReenableSuddenTermination) {
   1428         // The following is balanced by the call to disableSuddenTermination in the
   1429         // wakeSyncThread function. Any time we wait on the condition, we also have
   1430         // to enableSuddenTermation, after doing the next batch of work.
   1431         enableSuddenTermination();
   1432     }
   1433 
   1434     return 0;
   1435 }
   1436 
   1437 bool IconDatabase::readFromDatabase()
   1438 {
   1439     ASSERT_ICON_SYNC_THREAD();
   1440 
   1441 #ifndef NDEBUG
   1442     double timeStamp = currentTime();
   1443 #endif
   1444 
   1445     bool didAnyWork = false;
   1446 
   1447     // We'll make a copy of the sets of things that need to be read.  Then we'll verify at the time of updating the record that it still wants to be updated
   1448     // This way we won't hold the lock for a long period of time
   1449     Vector<IconRecord*> icons;
   1450     {
   1451         MutexLocker locker(m_pendingReadingLock);
   1452         icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
   1453     }
   1454 
   1455     // Keep track of icons we actually read to notify them of the new icon
   1456     HashSet<String> urlsToNotify;
   1457 
   1458     for (unsigned i = 0; i < icons.size(); ++i) {
   1459         didAnyWork = true;
   1460         RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
   1461 
   1462         // Verify this icon still wants to be read from disk
   1463         {
   1464             MutexLocker urlLocker(m_urlAndIconLock);
   1465             {
   1466                 MutexLocker readLocker(m_pendingReadingLock);
   1467 
   1468                 if (m_iconsPendingReading.contains(icons[i])) {
   1469                     // Set the new data
   1470                     icons[i]->setImageData(imageData.get());
   1471 
   1472                     // Remove this icon from the set that needs to be read
   1473                     m_iconsPendingReading.remove(icons[i]);
   1474 
   1475                     // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
   1476                     // We want to find the intersection of these two sets to notify them
   1477                     // Check the sizes of these two sets to minimize the number of iterations
   1478                     const HashSet<String>* outerHash;
   1479                     const HashSet<String>* innerHash;
   1480 
   1481                     if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
   1482                         outerHash = &m_pageURLsInterestedInIcons;
   1483                         innerHash = &(icons[i]->retainingPageURLs());
   1484                     } else {
   1485                         innerHash = &m_pageURLsInterestedInIcons;
   1486                         outerHash = &(icons[i]->retainingPageURLs());
   1487                     }
   1488 
   1489                     HashSet<String>::const_iterator iter = outerHash->begin();
   1490                     HashSet<String>::const_iterator end = outerHash->end();
   1491                     for (; iter != end; ++iter) {
   1492                         if (innerHash->contains(*iter)) {
   1493                             LOG(IconDatabase, "%s is interesting in the icon we just read.  Adding it to the list and removing it from the interested set", urlForLogging(*iter).ascii().data());
   1494                             urlsToNotify.add(*iter);
   1495                         }
   1496 
   1497                         // If we ever get to the point were we've seen every url interested in this icon, break early
   1498                         if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
   1499                             break;
   1500                     }
   1501 
   1502                     // We don't need to notify a PageURL twice, so all the ones we're about to notify can be removed from the interested set
   1503                     if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
   1504                         m_pageURLsInterestedInIcons.clear();
   1505                     else {
   1506                         iter = urlsToNotify.begin();
   1507                         end = urlsToNotify.end();
   1508                         for (; iter != end; ++iter)
   1509                             m_pageURLsInterestedInIcons.remove(*iter);
   1510                     }
   1511                 }
   1512             }
   1513         }
   1514 
   1515         if (shouldStopThreadActivity())
   1516             return didAnyWork;
   1517 
   1518         // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
   1519         // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
   1520         AutodrainedPool pool(25);
   1521 
   1522         // Now that we don't hold any locks, perform the actual notifications
   1523         HashSet<String>::iterator iter = urlsToNotify.begin();
   1524         HashSet<String>::iterator end = urlsToNotify.end();
   1525         for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
   1526             LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
   1527             m_client->dispatchDidAddIconForPageURL(*iter);
   1528             if (shouldStopThreadActivity())
   1529                 return didAnyWork;
   1530 
   1531             pool.cycle();
   1532         }
   1533 
   1534         LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
   1535         urlsToNotify.clear();
   1536 
   1537         if (shouldStopThreadActivity())
   1538             return didAnyWork;
   1539     }
   1540 
   1541     LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
   1542 
   1543     return didAnyWork;
   1544 }
   1545 
   1546 bool IconDatabase::writeToDatabase()
   1547 {
   1548     ASSERT_ICON_SYNC_THREAD();
   1549 
   1550 #ifndef NDEBUG
   1551     double timeStamp = currentTime();
   1552 #endif
   1553 
   1554     bool didAnyWork = false;
   1555 
   1556     // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
   1557     // we'll pick it up on the next pass.  This greatly simplifies the locking strategy for this method and remains cohesive with changes
   1558     // asked for by the database on the main thread
   1559     Vector<IconSnapshot> iconSnapshots;
   1560     Vector<PageURLSnapshot> pageSnapshots;
   1561     {
   1562         MutexLocker locker(m_pendingSyncLock);
   1563 
   1564         iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
   1565         m_iconsPendingSync.clear();
   1566 
   1567         pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
   1568         m_pageURLsPendingSync.clear();
   1569     }
   1570 
   1571     if (iconSnapshots.size() || pageSnapshots.size())
   1572         didAnyWork = true;
   1573 
   1574     SQLiteTransaction syncTransaction(m_syncDB);
   1575     syncTransaction.begin();
   1576 
   1577     for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
   1578         writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
   1579         LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp);
   1580     }
   1581 
   1582     for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
   1583         // If the icon URL is empty, this page is meant to be deleted
   1584         // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
   1585         if (pageSnapshots[i].iconURL.isEmpty())
   1586             removePageURLFromSQLDatabase(pageSnapshots[i].pageURL);
   1587         else
   1588             setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL);
   1589         LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data());
   1590     }
   1591 
   1592     syncTransaction.commit();
   1593 
   1594     // Check to make sure there are no dangling PageURLs - If there are, we want to output one log message but not spam the console potentially every few seconds
   1595     if (didAnyWork)
   1596         checkForDanglingPageURLs(false);
   1597 
   1598     LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
   1599 
   1600     return didAnyWork;
   1601 }
   1602 
   1603 void IconDatabase::pruneUnretainedIcons()
   1604 {
   1605     ASSERT_ICON_SYNC_THREAD();
   1606 
   1607     if (!isOpen())
   1608         return;
   1609 
   1610     // This method should only be called once per run
   1611     ASSERT(!m_initialPruningComplete);
   1612 
   1613     // This method relies on having read in all page URLs from the database earlier.
   1614     ASSERT(m_iconURLImportComplete);
   1615 
   1616     // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
   1617     Vector<int64_t> pageIDsToDelete;
   1618 
   1619     SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
   1620     pageSQL.prepare();
   1621 
   1622     int result;
   1623     while ((result = pageSQL.step()) == SQLResultRow) {
   1624         MutexLocker locker(m_urlAndIconLock);
   1625         if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1)))
   1626             pageIDsToDelete.append(pageSQL.getColumnInt64(0));
   1627     }
   1628 
   1629     if (result != SQLResultDone)
   1630         LOG_ERROR("Error reading PageURL table from on-disk DB");
   1631     pageSQL.finalize();
   1632 
   1633     // Delete page URLs that were in the table, but not in our retain count set.
   1634     size_t numToDelete = pageIDsToDelete.size();
   1635     if (numToDelete) {
   1636         SQLiteTransaction pruningTransaction(m_syncDB);
   1637         pruningTransaction.begin();
   1638 
   1639         SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
   1640         pageDeleteSQL.prepare();
   1641         for (size_t i = 0; i < numToDelete; ++i) {
   1642             LOG(IconDatabase, "Pruning page with rowid %lli from disk", pageIDsToDelete[i]);
   1643             pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
   1644             int result = pageDeleteSQL.step();
   1645             if (result != SQLResultDone)
   1646                 LOG_ERROR("Unabled to delete page with id %lli from disk", pageIDsToDelete[i]);
   1647             pageDeleteSQL.reset();
   1648 
   1649             // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
   1650             // finish the rest later (hopefully)
   1651             if (shouldStopThreadActivity()) {
   1652                 pruningTransaction.commit();
   1653                 return;
   1654             }
   1655         }
   1656         pruningTransaction.commit();
   1657         pageDeleteSQL.finalize();
   1658     }
   1659 
   1660     // Deleting unreferenced icons from the Icon tables has to be atomic -
   1661     // If the user quits while these are taking place, they might have to wait.  Thankfully this will rarely be an issue
   1662     // A user on a network home directory with a wildly inconsistent database might see quite a pause...
   1663 
   1664     SQLiteTransaction pruningTransaction(m_syncDB);
   1665     pruningTransaction.begin();
   1666 
   1667     // Wipe Icons that aren't retained
   1668     if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
   1669         LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
   1670     if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
   1671         LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
   1672 
   1673     pruningTransaction.commit();
   1674 
   1675     checkForDanglingPageURLs(true);
   1676 
   1677     m_initialPruningComplete = true;
   1678 }
   1679 
   1680 void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
   1681 {
   1682     ASSERT_ICON_SYNC_THREAD();
   1683 
   1684     // This check can be relatively expensive so we don't do it in a release build unless the caller has asked us to prune any dangling
   1685     // entries.  We also don't want to keep performing this check and reporting this error if it has already found danglers before so we
   1686     // keep track of whether we've found any.  We skip the check in the release build pretending to have already found danglers already.
   1687 #ifndef NDEBUG
   1688     static bool danglersFound = true;
   1689 #else
   1690     static bool danglersFound = false;
   1691 #endif
   1692 
   1693     if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
   1694         danglersFound = true;
   1695         LOG(IconDatabase, "Dangling PageURL entries found");
   1696         if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
   1697             LOG(IconDatabase, "Unable to prune dangling PageURLs");
   1698     }
   1699 }
   1700 
   1701 void IconDatabase::removeAllIconsOnThread()
   1702 {
   1703     ASSERT_ICON_SYNC_THREAD();
   1704 
   1705     LOG(IconDatabase, "Removing all icons on the sync thread");
   1706 
   1707     // Delete all the prepared statements so they can start over
   1708     deleteAllPreparedStatements();
   1709 
   1710     // To reset the on-disk database, we'll wipe all its tables then vacuum it
   1711     // This is easier and safer than closing it, deleting the file, and recreating from scratch
   1712     m_syncDB.clearAllTables();
   1713     m_syncDB.runVacuumCommand();
   1714     createDatabaseTables(m_syncDB);
   1715 
   1716     LOG(IconDatabase, "Dispatching notification that we removed all icons");
   1717     m_client->dispatchDidRemoveAllIcons();
   1718 }
   1719 
   1720 void IconDatabase::deleteAllPreparedStatements()
   1721 {
   1722     ASSERT_ICON_SYNC_THREAD();
   1723 
   1724     m_setIconIDForPageURLStatement.clear();
   1725     m_removePageURLStatement.clear();
   1726     m_getIconIDForIconURLStatement.clear();
   1727     m_getImageDataForIconURLStatement.clear();
   1728     m_addIconToIconInfoStatement.clear();
   1729     m_addIconToIconDataStatement.clear();
   1730     m_getImageDataStatement.clear();
   1731     m_deletePageURLsForIconURLStatement.clear();
   1732     m_deleteIconFromIconInfoStatement.clear();
   1733     m_deleteIconFromIconDataStatement.clear();
   1734     m_updateIconInfoStatement.clear();
   1735     m_updateIconDataStatement.clear();
   1736     m_setIconInfoStatement.clear();
   1737     m_setIconDataStatement.clear();
   1738 }
   1739 
   1740 void* IconDatabase::cleanupSyncThread()
   1741 {
   1742     ASSERT_ICON_SYNC_THREAD();
   1743 
   1744 #ifndef NDEBUG
   1745     double timeStamp = currentTime();
   1746 #endif
   1747 
   1748     // If the removeIcons flag is set, remove all icons from the db.
   1749     if (m_removeIconsRequested)
   1750         removeAllIconsOnThread();
   1751 
   1752     // Sync remaining icons out
   1753     LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
   1754     writeToDatabase();
   1755 
   1756     // Close the database
   1757     MutexLocker locker(m_syncLock);
   1758 
   1759     m_databaseDirectory = String();
   1760     m_completeDatabasePath = String();
   1761     deleteAllPreparedStatements();
   1762     m_syncDB.close();
   1763 
   1764 #ifndef NDEBUG
   1765     LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
   1766 #endif
   1767 
   1768     m_syncThreadRunning = false;
   1769     return 0;
   1770 }
   1771 
   1772 bool IconDatabase::imported()
   1773 {
   1774     ASSERT_ICON_SYNC_THREAD();
   1775 
   1776     if (m_isImportedSet)
   1777         return m_imported;
   1778 
   1779     SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
   1780     if (query.prepare() != SQLResultOk) {
   1781         LOG_ERROR("Unable to prepare imported statement");
   1782         return false;
   1783     }
   1784 
   1785     int result = query.step();
   1786     if (result == SQLResultRow)
   1787         result = query.getColumnInt(0);
   1788     else {
   1789         if (result != SQLResultDone)
   1790             LOG_ERROR("imported statement failed");
   1791         result = 0;
   1792     }
   1793 
   1794     m_isImportedSet = true;
   1795     return m_imported = result;
   1796 }
   1797 
   1798 void IconDatabase::setImported(bool import)
   1799 {
   1800     ASSERT_ICON_SYNC_THREAD();
   1801 
   1802     m_imported = import;
   1803     m_isImportedSet = true;
   1804 
   1805     String queryString = import ?
   1806         "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
   1807         "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
   1808 
   1809     SQLiteStatement query(m_syncDB, queryString);
   1810 
   1811     if (query.prepare() != SQLResultOk) {
   1812         LOG_ERROR("Unable to prepare set imported statement");
   1813         return;
   1814     }
   1815 
   1816     if (query.step() != SQLResultDone)
   1817         LOG_ERROR("set imported statement failed");
   1818 }
   1819 
   1820 // readySQLiteStatement() handles two things
   1821 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade.  This happens when the user
   1822 //     switches to and from private browsing
   1823 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
   1824 inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
   1825 {
   1826     if (statement && (statement->database() != &db || statement->isExpired())) {
   1827         if (statement->isExpired())
   1828             LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
   1829         statement.set(0);
   1830     }
   1831     if (!statement) {
   1832         statement.set(new SQLiteStatement(db, str));
   1833         if (statement->prepare() != SQLResultOk)
   1834             LOG_ERROR("Preparing statement %s failed", str.ascii().data());
   1835     }
   1836 }
   1837 
   1838 void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
   1839 {
   1840     ASSERT_ICON_SYNC_THREAD();
   1841 
   1842     int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
   1843 
   1844     if (!iconID)
   1845         iconID = addIconURLToSQLDatabase(iconURL);
   1846 
   1847     if (!iconID) {
   1848         LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
   1849         ASSERT(false);
   1850         return;
   1851     }
   1852 
   1853     setIconIDForPageURLInSQLDatabase(iconID, pageURL);
   1854 }
   1855 
   1856 void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
   1857 {
   1858     ASSERT_ICON_SYNC_THREAD();
   1859 
   1860     readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
   1861     m_setIconIDForPageURLStatement->bindText(1, pageURL);
   1862     m_setIconIDForPageURLStatement->bindInt64(2, iconID);
   1863 
   1864     int result = m_setIconIDForPageURLStatement->step();
   1865     if (result != SQLResultDone) {
   1866         ASSERT(false);
   1867         LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
   1868     }
   1869 
   1870     m_setIconIDForPageURLStatement->reset();
   1871 }
   1872 
   1873 void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
   1874 {
   1875     ASSERT_ICON_SYNC_THREAD();
   1876 
   1877     readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
   1878     m_removePageURLStatement->bindText(1, pageURL);
   1879 
   1880     if (m_removePageURLStatement->step() != SQLResultDone)
   1881         LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
   1882 
   1883     m_removePageURLStatement->reset();
   1884 }
   1885 
   1886 
   1887 int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
   1888 {
   1889     ASSERT_ICON_SYNC_THREAD();
   1890 
   1891     readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
   1892     m_getIconIDForIconURLStatement->bindText(1, iconURL);
   1893 
   1894     int64_t result = m_getIconIDForIconURLStatement->step();
   1895     if (result == SQLResultRow)
   1896         result = m_getIconIDForIconURLStatement->getColumnInt64(0);
   1897     else {
   1898         if (result != SQLResultDone)
   1899             LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
   1900         result = 0;
   1901     }
   1902 
   1903     m_getIconIDForIconURLStatement->reset();
   1904     return result;
   1905 }
   1906 
   1907 int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
   1908 {
   1909     ASSERT_ICON_SYNC_THREAD();
   1910 
   1911     // There would be a transaction here to make sure these two inserts are atomic
   1912     // In practice the only caller of this method is always wrapped in a transaction itself so placing another
   1913     // here is unnecessary
   1914 
   1915     readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
   1916     m_addIconToIconInfoStatement->bindText(1, iconURL);
   1917 
   1918     int result = m_addIconToIconInfoStatement->step();
   1919     m_addIconToIconInfoStatement->reset();
   1920     if (result != SQLResultDone) {
   1921         LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
   1922         return 0;
   1923     }
   1924     int64_t iconID = m_syncDB.lastInsertRowID();
   1925 
   1926     readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
   1927     m_addIconToIconDataStatement->bindInt64(1, iconID);
   1928 
   1929     result = m_addIconToIconDataStatement->step();
   1930     m_addIconToIconDataStatement->reset();
   1931     if (result != SQLResultDone) {
   1932         LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
   1933         return 0;
   1934     }
   1935 
   1936     return iconID;
   1937 }
   1938 
   1939 PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
   1940 {
   1941     ASSERT_ICON_SYNC_THREAD();
   1942 
   1943     RefPtr<SharedBuffer> imageData;
   1944 
   1945     readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
   1946     m_getImageDataForIconURLStatement->bindText(1, iconURL);
   1947 
   1948     int result = m_getImageDataForIconURLStatement->step();
   1949     if (result == SQLResultRow) {
   1950         Vector<char> data;
   1951         m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
   1952         imageData = SharedBuffer::create(data.data(), data.size());
   1953     } else if (result != SQLResultDone)
   1954         LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
   1955 
   1956     m_getImageDataForIconURLStatement->reset();
   1957 
   1958     return imageData.release();
   1959 }
   1960 
   1961 void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
   1962 {
   1963     ASSERT_ICON_SYNC_THREAD();
   1964 
   1965     if (iconURL.isEmpty())
   1966         return;
   1967 
   1968     // There would be a transaction here to make sure these removals are atomic
   1969     // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
   1970 
   1971     // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
   1972     // icon is marked to be added then marked for removal before it is ever written to disk.  No big deal, early return
   1973     int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
   1974     if (!iconID)
   1975         return;
   1976 
   1977     readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
   1978     m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
   1979 
   1980     if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
   1981         LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
   1982 
   1983     readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
   1984     m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
   1985 
   1986     if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
   1987         LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
   1988 
   1989     readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
   1990     m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
   1991 
   1992     if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
   1993         LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
   1994 
   1995     m_deletePageURLsForIconURLStatement->reset();
   1996     m_deleteIconFromIconInfoStatement->reset();
   1997     m_deleteIconFromIconDataStatement->reset();
   1998 }
   1999 
   2000 void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
   2001 {
   2002     ASSERT_ICON_SYNC_THREAD();
   2003 
   2004     if (snapshot.iconURL.isEmpty())
   2005         return;
   2006 
   2007     // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
   2008     if (!snapshot.timestamp && !snapshot.data) {
   2009         LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data());
   2010         removeIconFromSQLDatabase(snapshot.iconURL);
   2011         return;
   2012     }
   2013 
   2014     // There would be a transaction here to make sure these removals are atomic
   2015     // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
   2016 
   2017     // Get the iconID for this url
   2018     int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL);
   2019 
   2020     // If there is already an iconID in place, update the database.
   2021     // Otherwise, insert new records
   2022     if (iconID) {
   2023         readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
   2024         m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp);
   2025         m_updateIconInfoStatement->bindText(2, snapshot.iconURL);
   2026         m_updateIconInfoStatement->bindInt64(3, iconID);
   2027 
   2028         if (m_updateIconInfoStatement->step() != SQLResultDone)
   2029             LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
   2030 
   2031         m_updateIconInfoStatement->reset();
   2032 
   2033         readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
   2034         m_updateIconDataStatement->bindInt64(2, iconID);
   2035 
   2036         // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
   2037         // signifying that this icon doesn't have any data
   2038         if (snapshot.data && snapshot.data->size())
   2039             m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size());
   2040         else
   2041             m_updateIconDataStatement->bindNull(1);
   2042 
   2043         if (m_updateIconDataStatement->step() != SQLResultDone)
   2044             LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
   2045 
   2046         m_updateIconDataStatement->reset();
   2047     } else {
   2048         readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
   2049         m_setIconInfoStatement->bindText(1, snapshot.iconURL);
   2050         m_setIconInfoStatement->bindInt64(2, snapshot.timestamp);
   2051 
   2052         if (m_setIconInfoStatement->step() != SQLResultDone)
   2053             LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
   2054 
   2055         m_setIconInfoStatement->reset();
   2056 
   2057         int64_t iconID = m_syncDB.lastInsertRowID();
   2058 
   2059         readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
   2060         m_setIconDataStatement->bindInt64(1, iconID);
   2061 
   2062         // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
   2063         // signifying that this icon doesn't have any data
   2064         if (snapshot.data && snapshot.data->size())
   2065             m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size());
   2066         else
   2067             m_setIconDataStatement->bindNull(2);
   2068 
   2069         if (m_setIconDataStatement->step() != SQLResultDone)
   2070             LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
   2071 
   2072         m_setIconDataStatement->reset();
   2073     }
   2074 }
   2075 
   2076 } // namespace WebCore
   2077 
   2078 #endif // ENABLE(ICONDATABASE)
   2079