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