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