2009-03-11 Darin Adler <darin@apple.com>
[WebKit-https.git] / WebCore / loader / icon / IconDatabase.cpp
1 /*
2  * Copyright (C) 2006, 2007, 2008 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 #include "AutodrainedPool.h"
31 #include "CString.h"
32 #include "DocumentLoader.h"
33 #include "FileSystem.h"
34 #include "IconDatabaseClient.h"
35 #include "IconRecord.h"
36 #include "Image.h"
37 #include "IntSize.h"
38 #include "KURL.h"
39 #include "Logging.h"
40 #include "PageURLRecord.h"
41 #include "SQLiteStatement.h"
42 #include "SQLiteTransaction.h"
43 #include <runtime/InitializeThreading.h>
44 #include <wtf/CurrentTime.h>
45 #include <wtf/MainThread.h>
46 #include <wtf/StdLibExtras.h>
47
48 #if PLATFORM(WIN_OS)
49 #include <windows.h>
50 #include <winbase.h>
51 #include <shlobj.h>
52 #else
53 #include <sys/stat.h>
54 #endif
55
56 #if PLATFORM(DARWIN)
57 #include <pthread.h>
58 #endif
59
60 // For methods that are meant to support API from the main thread - should not be called internally
61 #define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
62
63 // For methods that are meant to support the sync thread ONLY
64 #define IS_ICON_SYNC_THREAD() (m_syncThread == currentThread())
65 #define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
66
67 #if PLATFORM(QT)
68 #define CAN_THEME_URL_ICON
69 #endif
70
71 namespace WebCore {
72
73 static IconDatabase* sharedIconDatabase = 0;
74 static int databaseCleanupCounter = 0;
75
76 // This version number is in the DB and marks the current generation of the schema
77 // Currently, a mismatched schema causes the DB to be wiped and reset.  This isn't 
78 // so bad during development but in the future, we would need to write a conversion
79 // function to advance older released schemas to "current"
80 const int currentDatabaseVersion = 6;
81
82 // Icons expire once every 4 days
83 const int iconExpirationTime = 60*60*24*4; 
84
85 const int updateTimerDelay = 5; 
86
87 static bool checkIntegrityOnOpen = false;
88
89 #ifndef NDEBUG
90 static String urlForLogging(const String& url)
91 {
92     static unsigned urlTruncationLength = 120;
93
94     if (url.length() < urlTruncationLength)
95         return url;
96     return url.substring(0, urlTruncationLength) + "...";
97 }
98 #endif
99
100 static IconDatabaseClient* defaultClient() 
101 {
102     static IconDatabaseClient* defaultClient = new IconDatabaseClient();
103     return defaultClient;
104 }
105
106 IconDatabase* iconDatabase()
107 {
108     if (!sharedIconDatabase) {
109         JSC::initializeThreading();
110         sharedIconDatabase = new IconDatabase;
111     }
112     return sharedIconDatabase;
113 }
114
115 // ************************
116 // *** Main Thread Only ***
117 // ************************
118
119 void IconDatabase::setClient(IconDatabaseClient* client)
120 {
121     // We don't allow a null client, because we never null check it anywhere in this code
122     // Also don't allow a client change after the thread has already began 
123     // (setting the client should occur before the database is opened)
124     ASSERT(client);
125     ASSERT(!m_syncThreadRunning);
126     if (!client || m_syncThreadRunning)
127         return;
128         
129     m_client = client;
130 }
131
132 bool IconDatabase::open(const String& databasePath)
133 {
134     ASSERT_NOT_SYNC_THREAD();
135
136     if (!m_isEnabled)
137         return false;
138
139     if (isOpen()) {
140         LOG_ERROR("Attempt to reopen the IconDatabase which is already open.  Must close it first.");
141         return false;
142     }
143
144     m_databaseDirectory = databasePath.copy();
145
146     // Formulate the full path for the database file
147     m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename());
148
149     // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call 
150     // completes and m_syncThreadRunning is properly set
151     m_syncLock.lock();
152     m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this, "WebCore: IconDatabase");
153     m_syncLock.unlock();
154     if (!m_syncThread)
155         return false;
156     return true;
157 }
158
159 void IconDatabase::close()
160 {
161     ASSERT_NOT_SYNC_THREAD();
162     
163     if (m_syncThreadRunning) {
164         // Set the flag to tell the sync thread to wrap it up
165         m_threadTerminationRequested = true;
166
167         // Wake up the sync thread if it's waiting
168         wakeSyncThread();
169         
170         // Wait for the sync thread to terminate
171         waitForThreadCompletion(m_syncThread, 0);
172     }
173
174     m_syncThreadRunning = false;    
175     m_threadTerminationRequested = false;
176     m_removeIconsRequested = false;
177
178     m_syncDB.close();
179     ASSERT(!isOpen());
180 }
181
182 void IconDatabase::removeAllIcons()
183 {
184     ASSERT_NOT_SYNC_THREAD();
185     
186     if (!isOpen())
187         return;
188
189     LOG(IconDatabase, "Requesting background thread to remove all icons");
190     
191     // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk
192     {
193         MutexLocker locker(m_urlAndIconLock);
194         
195         // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted
196         // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of
197         HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin();
198         HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end();
199         for (; iter != end; ++iter)
200             (*iter).second->setIconRecord(0);
201             
202         // Clear the iconURL -> IconRecord map
203         m_iconURLToRecordMap.clear();
204                     
205         // Clear all in-memory records of things that need to be synced out to disk
206         {
207             MutexLocker locker(m_pendingSyncLock);
208             m_pageURLsPendingSync.clear();
209             m_iconsPendingSync.clear();
210         }
211         
212         // Clear all in-memory records of things that need to be read in from disk
213         {
214             MutexLocker locker(m_pendingReadingLock);
215             m_pageURLsPendingImport.clear();
216             m_pageURLsInterestedInIcons.clear();
217             m_iconsPendingReading.clear();
218             m_loadersPendingDecision.clear();
219         }
220     }
221     
222     m_removeIconsRequested = true;
223     wakeSyncThread();
224 }
225
226 Image* IconDatabase::iconForPageURL(const String& pageURLOriginal, const IntSize& size)
227 {   
228     ASSERT_NOT_SYNC_THREAD();
229
230     // pageURLOriginal cannot be stored without being deep copied first.  
231     // We should go our of our way to only copy it if we have to store it
232     
233     if (!isOpen() || pageURLOriginal.isEmpty())
234         return defaultIcon(size);
235
236     MutexLocker locker(m_urlAndIconLock);
237     
238     String pageURLCopy; // Creates a null string for easy testing
239     
240     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
241     if (!pageRecord) {
242         pageURLCopy = pageURLOriginal.copy();
243         pageRecord = getOrCreatePageURLRecord(pageURLCopy);
244     }
245     
246     // If pageRecord is NULL, one of two things is true -
247     // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists
248     // 2 - The initial url import IS complete and this pageURL has no icon
249     if (!pageRecord) {
250         MutexLocker locker(m_pendingReadingLock);
251         
252         // Import is ongoing, there might be an icon.  In this case, register to be notified when the icon comes in
253         // If we ever reach this condition, we know we've already made the pageURL copy
254         if (!m_iconURLImportComplete)
255             m_pageURLsInterestedInIcons.add(pageURLCopy);
256         
257         return 0;
258     }
259
260     IconRecord* iconRecord = pageRecord->iconRecord();
261     
262     // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon
263     // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so
264     // we can just bail now
265     if (!m_iconURLImportComplete && !iconRecord)
266         return 0;
267     
268     // 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
269     ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal));
270     
271     if (!iconRecord)
272         return 0;
273         
274     // If it's a new IconRecord object that doesn't have its imageData set yet,
275     // mark it to be read by the background thread
276     if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) {
277         if (pageURLCopy.isNull())
278             pageURLCopy = pageURLOriginal.copy();
279     
280         MutexLocker locker(m_pendingReadingLock);
281         m_pageURLsInterestedInIcons.add(pageURLCopy);
282         m_iconsPendingReading.add(iconRecord);
283         wakeSyncThread();
284         return 0;
285     }
286     
287     // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off
288     // and isn't actually interested in the image return value
289     if (size == IntSize(0, 0))
290         return 0;
291         
292     // PARANOID DISCUSSION: This method makes some assumptions.  It returns a WebCore::image which the icon database might dispose of at anytime in the future,
293     // and Images aren't ref counted.  So there is no way for the client to guarantee continued existence of the image.
294     // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image
295     // and drop the raw Image*.  On Mac an NSImage, and on windows drawing into an HBITMAP.
296     // 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
297     // representation out of it?
298     // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache.  
299     // This is because we make the assumption that anything in memory is newer than whatever is in the database.
300     // 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 
301     // delete the image on the secondary thread if the image already exists.
302     return iconRecord->image(size);
303 }
304
305 void IconDatabase::readIconForPageURLFromDisk(const String& pageURL)
306 {
307     // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk
308     // if it hasn't already been set in memory.  The special IntSize (0, 0) is a special way of telling 
309     // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk.
310     iconForPageURL(pageURL, IntSize(0,0));
311 }
312
313 String IconDatabase::iconURLForPageURL(const String& pageURLOriginal)
314 {    
315     ASSERT_NOT_SYNC_THREAD(); 
316         
317     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
318     // Also, in the case we have a real answer for the caller, we must deep copy that as well
319     
320     if (!isOpen() || pageURLOriginal.isEmpty())
321         return String();
322         
323     MutexLocker locker(m_urlAndIconLock);
324     
325     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
326     if (!pageRecord)
327         pageRecord = getOrCreatePageURLRecord(pageURLOriginal.copy());
328     
329     // If pageRecord is NULL, one of two things is true -
330     // 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
331     // 2 - The initial url import IS complete and this pageURL has no icon
332     if (!pageRecord)
333         return String();
334     
335     // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check
336     return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().copy() : String();
337 }
338
339 #ifdef CAN_THEME_URL_ICON
340 static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
341 {
342      defaultIconRecord->loadImageFromResource("urlIcon");
343 }
344 #else
345 static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord)
346 {
347     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, 
348         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, 
349         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, 
350         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, 
351         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, 
352         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, 
353         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, 
354         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, 
355         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, 
356         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, 
357         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, 
358         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, 
359         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, 
360         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, 
361         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, 
362         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, 
363         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, 
364         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, 
365         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,
366         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, 
367         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, 
368         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, 
369         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, 
370         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, 
371         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, 
372         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, 
373         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, 
374         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, 
375         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, 
376         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, 
377         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, 
378         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, 
379         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, 
380         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, 
381         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, 
382         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, 
383         0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 };
384         
385     DEFINE_STATIC_LOCAL(RefPtr<SharedBuffer>, defaultIconBuffer, (SharedBuffer::create(defaultIconData, sizeof(defaultIconData))));
386     defaultIconRecord->setImageData(defaultIconBuffer);
387 }
388 #endif
389
390 Image* IconDatabase::defaultIcon(const IntSize& size)
391 {
392     ASSERT_NOT_SYNC_THREAD();
393
394     
395     if (!m_defaultIconRecord) {
396         m_defaultIconRecord = IconRecord::create("urlIcon");
397         loadDefaultIconRecord(m_defaultIconRecord.get());
398     }
399     
400     return m_defaultIconRecord->image(size);
401 }
402
403
404 void IconDatabase::retainIconForPageURL(const String& pageURLOriginal)
405 {    
406     ASSERT_NOT_SYNC_THREAD();
407     
408     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
409     
410     if (!isEnabled() || pageURLOriginal.isEmpty())
411         return;
412        
413     MutexLocker locker(m_urlAndIconLock);
414
415     PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal);
416     
417     String pageURL;
418     
419     if (!record) {
420         pageURL = pageURLOriginal.copy();
421
422         record = new PageURLRecord(pageURL);
423         m_pageURLToRecordMap.set(pageURL, record);
424     }
425     
426     if (!record->retain()) {
427         if (pageURL.isNull())
428             pageURL = pageURLOriginal.copy();
429
430         // This page just had its retain count bumped from 0 to 1 - Record that fact
431         m_retainedPageURLs.add(pageURL);
432
433         // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot, 
434         // so we bail here and skip those steps
435         if (!m_iconURLImportComplete)
436             return;
437
438         MutexLocker locker(m_pendingSyncLock);
439         // If this pageURL waiting to be sync'ed, update the sync record
440         // 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!
441         if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) {
442             LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data());
443             m_pageURLsPendingSync.set(pageURL, record->snapshot());
444         }
445     }
446 }
447
448 void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal)
449 {
450     ASSERT_NOT_SYNC_THREAD();
451         
452     // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
453     
454     if (!isEnabled() || pageURLOriginal.isEmpty())
455         return;
456     
457     MutexLocker locker(m_urlAndIconLock);
458
459     // Check if this pageURL is actually retained
460     if (!m_retainedPageURLs.contains(pageURLOriginal)) {
461         LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data());
462         return;
463     }
464     
465     // Get its retain count - if it's retained, we'd better have a PageURLRecord for it
466     PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
467     ASSERT(pageRecord);
468     LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1);
469     ASSERT(pageRecord->retainCount() > 0);
470         
471     // If it still has a positive retain count, store the new count and bail
472     if (pageRecord->release())
473         return;
474         
475     // This pageRecord has now been fully released.  Do the appropriate cleanup
476     LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data());
477     m_pageURLToRecordMap.remove(pageURLOriginal);
478     m_retainedPageURLs.remove(pageURLOriginal);       
479     
480     // Grab the iconRecord for later use (and do a sanity check on it for kicks)
481     IconRecord* iconRecord = pageRecord->iconRecord();
482     
483     ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord));
484
485     {
486         MutexLocker locker(m_pendingReadingLock);
487         
488         // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results    
489         if (!m_iconURLImportComplete)
490             m_pageURLsPendingImport.remove(pageURLOriginal);
491         m_pageURLsInterestedInIcons.remove(pageURLOriginal);
492         
493         // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore
494         if (iconRecord && iconRecord->hasOneRef()) {
495             m_iconURLToRecordMap.remove(iconRecord->iconURL());
496             m_iconsPendingReading.remove(iconRecord);
497         }
498     }
499     
500     // Mark stuff for deletion from the database only if we're not in private browsing
501     if (!m_privateBrowsingEnabled) {
502         MutexLocker locker(m_pendingSyncLock);
503         m_pageURLsPendingSync.set(pageURLOriginal.copy(), pageRecord->snapshot(true));
504     
505         // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to
506         // be marked for deletion
507         if (iconRecord && iconRecord->hasOneRef())
508             m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
509     }
510     
511     delete pageRecord;
512
513     if (isOpen())
514         scheduleOrDeferSyncTimer();
515 }
516
517 void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal)
518 {    
519     ASSERT_NOT_SYNC_THREAD();
520     
521     // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first
522     
523     if (!isOpen() || iconURLOriginal.isEmpty())
524         return;
525     
526     RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : 0;
527     String iconURL = iconURLOriginal.copy();
528     
529     Vector<String> pageURLs;
530     {
531         MutexLocker locker(m_urlAndIconLock);
532     
533         // If this icon was pending a read, remove it from that set because this new data should override what is on disk
534         RefPtr<IconRecord> icon = m_iconURLToRecordMap.get(iconURL);
535         if (icon) {
536             MutexLocker locker(m_pendingReadingLock);
537             m_iconsPendingReading.remove(icon.get());
538         } else
539             icon = getOrCreateIconRecord(iconURL);
540     
541         // Update the data and set the time stamp
542         icon->setImageData(data);
543         icon->setTimestamp((int)currentTime());
544         
545         // Copy the current retaining pageURLs - if any - to notify them of the change
546         pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end());
547         
548         // Mark the IconRecord as requiring an update to the database only if private browsing is disabled
549         if (!m_privateBrowsingEnabled) {
550             MutexLocker locker(m_pendingSyncLock);
551             m_iconsPendingSync.set(iconURL, icon->snapshot());
552         }
553
554         if (icon->hasOneRef()) {
555             ASSERT(icon->retainingPageURLs().isEmpty());
556             LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(icon->iconURL()).ascii().data());
557             m_iconURLToRecordMap.remove(icon->iconURL());
558         }
559     }
560
561     // Send notification out regarding all PageURLs that retain this icon
562     // But not if we're on the sync thread because that implies this mapping
563     // comes from the initial import which we don't want notifications for
564     if (!IS_ICON_SYNC_THREAD()) {
565         // Start the timer to commit this change - or further delay the timer if it was already started
566         scheduleOrDeferSyncTimer();
567
568         // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
569         // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up        
570         AutodrainedPool pool(25);
571
572         for (unsigned i = 0; i < pageURLs.size(); ++i) {
573             LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data());
574             m_client->dispatchDidAddIconForPageURL(pageURLs[i]);
575
576             pool.cycle();
577         }
578     }
579 }
580
581 void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal)
582 {    
583     ASSERT_NOT_SYNC_THREAD();
584
585     // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first
586     
587     ASSERT(!iconURLOriginal.isEmpty());
588         
589     if (!isOpen() || pageURLOriginal.isEmpty())
590         return;
591     
592     String iconURL, pageURL;
593     
594     {
595         MutexLocker locker(m_urlAndIconLock);
596
597         PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
598         
599         // If the urls already map to each other, bail.
600         // This happens surprisingly often, and seems to cream iBench performance
601         if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal)
602             return;
603             
604         pageURL = pageURLOriginal.copy();
605         iconURL = iconURLOriginal.copy();
606
607         if (!pageRecord) {
608             pageRecord = new PageURLRecord(pageURL);
609             m_pageURLToRecordMap.set(pageURL, pageRecord);
610         }
611
612         RefPtr<IconRecord> iconRecord = pageRecord->iconRecord();
613
614         // Otherwise, set the new icon record for this page
615         pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
616
617         // If the current icon has only a single ref left, it is about to get wiped out. 
618         // Remove it from the in-memory records and don't bother reading it in from disk anymore
619         if (iconRecord && iconRecord->hasOneRef()) {
620             ASSERT(iconRecord->retainingPageURLs().size() == 0);
621             LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data());
622             m_iconURLToRecordMap.remove(iconRecord->iconURL());
623             MutexLocker locker(m_pendingReadingLock);
624             m_iconsPendingReading.remove(iconRecord.get());
625         }
626         
627         // And mark this mapping to be added to the database
628         if (!m_privateBrowsingEnabled) {
629             MutexLocker locker(m_pendingSyncLock);
630             m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot());
631             
632             // If the icon is on its last ref, mark it for deletion
633             if (iconRecord && iconRecord->hasOneRef())
634                 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
635         }
636     }
637
638     // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping
639     // comes from the initial import which we don't want notifications for
640     if (!IS_ICON_SYNC_THREAD()) {
641         // Start the timer to commit this change - or further delay the timer if it was already started
642         scheduleOrDeferSyncTimer();
643         
644         LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data());
645         AutodrainedPool pool;
646         m_client->dispatchDidAddIconForPageURL(pageURL);
647     }
648 }
649
650 IconLoadDecision IconDatabase::loadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader)
651 {
652     ASSERT_NOT_SYNC_THREAD();
653
654     if (!isOpen() || iconURL.isEmpty())
655         return IconLoadNo;
656     
657     // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord:
658     // 1 - When we read the icon urls from disk, getting the timeStamp at the same time
659     // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time
660     {
661         MutexLocker locker(m_urlAndIconLock);
662         if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) {
663             LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord");
664             return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo;
665         }
666     }
667     
668     // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now
669     MutexLocker readingLocker(m_pendingReadingLock);
670     if (m_iconURLImportComplete)
671         return IconLoadYes;
672         
673     // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says
674     // "You might be asked to load this later, so flag that"
675     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);
676     m_loadersPendingDecision.add(notificationDocumentLoader);    
677
678     return IconLoadUnknown;
679 }
680
681 bool IconDatabase::iconDataKnownForIconURL(const String& iconURL)
682 {
683     ASSERT_NOT_SYNC_THREAD();
684     
685     MutexLocker locker(m_urlAndIconLock);
686     if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
687         return icon->imageDataStatus() != ImageDataStatusUnknown;
688
689     return false;
690 }
691
692 void IconDatabase::setEnabled(bool enabled)
693 {
694     ASSERT_NOT_SYNC_THREAD();
695     
696     if (!enabled && isOpen())
697         close();
698     m_isEnabled = enabled;
699 }
700
701 bool IconDatabase::isEnabled() const
702 {
703     ASSERT_NOT_SYNC_THREAD();
704     
705      return m_isEnabled;
706 }
707
708 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
709 {
710     m_privateBrowsingEnabled = flag;
711 }
712
713 bool IconDatabase::isPrivateBrowsingEnabled() const
714 {
715     return m_privateBrowsingEnabled;
716 }
717
718 void IconDatabase::delayDatabaseCleanup()
719 {
720     ++databaseCleanupCounter;
721     if (databaseCleanupCounter == 1)
722         LOG(IconDatabase, "Database cleanup is now DISABLED");
723 }
724
725 void IconDatabase::allowDatabaseCleanup()
726 {
727     if (--databaseCleanupCounter < 0)
728         databaseCleanupCounter = 0;
729     if (databaseCleanupCounter == 0)
730         LOG(IconDatabase, "Database cleanup is now ENABLED");
731 }
732
733 void IconDatabase::checkIntegrityBeforeOpening()
734 {
735     checkIntegrityOnOpen = true;
736 }
737
738 size_t IconDatabase::pageURLMappingCount()
739 {
740     MutexLocker locker(m_urlAndIconLock);
741     return m_pageURLToRecordMap.size();
742 }
743
744 size_t IconDatabase::retainedPageURLCount()
745 {
746     MutexLocker locker(m_urlAndIconLock);
747     return m_retainedPageURLs.size();
748 }
749
750 size_t IconDatabase::iconRecordCount()
751 {
752     MutexLocker locker(m_urlAndIconLock);
753     return m_iconURLToRecordMap.size();
754 }
755
756 size_t IconDatabase::iconRecordCountWithData()
757 {
758     MutexLocker locker(m_urlAndIconLock);
759     size_t result = 0;
760     
761     HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
762     HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
763     
764     for (; i != end; ++i)
765         result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
766             
767     return result;
768 }
769
770 IconDatabase::IconDatabase()
771     : m_syncThreadRunning(false)
772     , m_defaultIconRecord(0)
773     , m_isEnabled(false)
774     , m_privateBrowsingEnabled(false)
775     , m_threadTerminationRequested(false)
776     , m_removeIconsRequested(false)
777     , m_iconURLImportComplete(false)
778     , m_initialPruningComplete(false)
779     , m_client(defaultClient())
780     , m_imported(false)
781     , m_isImportedSet(false)
782 {
783 #if PLATFORM(DARWIN)
784     ASSERT(pthread_main_np());
785 #endif
786 }
787
788 IconDatabase::~IconDatabase()
789 {
790     ASSERT_NOT_REACHED();
791 }
792
793 void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context)
794 {
795     static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions();
796 }
797
798 void IconDatabase::notifyPendingLoadDecisions()
799 {
800     ASSERT_NOT_SYNC_THREAD();
801     
802     // This method should only be called upon completion of the initial url import from the database
803     ASSERT(m_iconURLImportComplete);
804     LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
805     
806     HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
807     HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
808     
809     for (; i != end; ++i)
810         if ((*i)->refCount() > 1)
811             (*i)->iconLoadDecisionAvailable();
812             
813     m_loadersPendingDecision.clear();
814 }
815
816 void IconDatabase::wakeSyncThread()
817 {
818     MutexLocker locker(m_syncLock);
819     m_syncCondition.signal();
820 }
821
822 void IconDatabase::scheduleOrDeferSyncTimer()
823 {
824     ASSERT_NOT_SYNC_THREAD();
825     if (!m_syncTimer)
826         m_syncTimer.set(new Timer<IconDatabase>(this, &IconDatabase::syncTimerFired));
827
828     m_syncTimer->startOneShot(updateTimerDelay);
829 }
830
831 void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
832 {
833     ASSERT_NOT_SYNC_THREAD();
834     wakeSyncThread();
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.copy();
851 }
852
853 String IconDatabase::defaultDatabaseFilename()
854 {
855     DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db"));
856     return defaultDatabaseFilename.copy();
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     static bool prunedUnretainedIcons = false;
1332
1333     m_syncLock.lock();
1334
1335     // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
1336     while (!m_threadTerminationRequested) {
1337         m_syncLock.unlock();
1338
1339 #ifndef NDEBUG
1340         double timeStamp = currentTime();
1341 #endif
1342         LOG(IconDatabase, "(THREAD) Main work loop starting");
1343
1344         // 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
1345         if (m_removeIconsRequested) {
1346             removeAllIconsOnThread();
1347             m_removeIconsRequested = false;
1348         }
1349         
1350         // Then, if the thread should be quitting, quit now!
1351         if (m_threadTerminationRequested)
1352             break;
1353         
1354         bool didAnyWork = true;
1355         while (didAnyWork) {
1356             bool didWrite = writeToDatabase();
1357             if (shouldStopThreadActivity())
1358                 break;
1359                 
1360             didAnyWork = readFromDatabase();
1361             if (shouldStopThreadActivity())
1362                 break;
1363                 
1364             // Prune unretained icons after the first time we sync anything out to the database
1365             // This way, pruning won't be the only operation we perform to the database by itself
1366             // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
1367             // or if private browsing is enabled
1368             // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
1369             // has asked to delay pruning
1370             if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
1371 #ifndef NDEBUG
1372                 double time = currentTime();
1373 #endif
1374                 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
1375                 
1376                 pruneUnretainedIcons();
1377                 
1378                 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
1379                 
1380                 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
1381                 // to mark prunedUnretainedIcons true because we're about to terminate anyway
1382                 prunedUnretainedIcons = true;
1383             }
1384             
1385             didAnyWork = didAnyWork || didWrite;
1386             if (shouldStopThreadActivity())
1387                 break;
1388         }
1389         
1390 #ifndef NDEBUG
1391         double newstamp = currentTime();
1392         LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
1393 #endif
1394                     
1395         m_syncLock.lock();
1396         
1397         // There is some condition that is asking us to stop what we're doing now and handle a special case
1398         // This is either removing all icons, or shutting down the thread to quit the app
1399         // We handle those at the top of this main loop so continue to jump back up there
1400         if (shouldStopThreadActivity())
1401             continue;
1402             
1403         m_syncCondition.wait(m_syncLock); 
1404     }
1405     m_syncLock.unlock();
1406     
1407     // Thread is terminating at this point
1408     return cleanupSyncThread();
1409 }
1410
1411 bool IconDatabase::readFromDatabase()
1412 {
1413     ASSERT_ICON_SYNC_THREAD();
1414     
1415 #ifndef NDEBUG
1416     double timeStamp = currentTime();
1417 #endif
1418
1419     bool didAnyWork = false;
1420
1421     // 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
1422     // This way we won't hold the lock for a long period of time
1423     Vector<IconRecord*> icons;
1424     {
1425         MutexLocker locker(m_pendingReadingLock);
1426         icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
1427     }
1428     
1429     // Keep track of icons we actually read to notify them of the new icon    
1430     HashSet<String> urlsToNotify;
1431     
1432     for (unsigned i = 0; i < icons.size(); ++i) {
1433         didAnyWork = true;
1434         RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
1435
1436         // Verify this icon still wants to be read from disk
1437         {
1438             MutexLocker urlLocker(m_urlAndIconLock);
1439             {
1440                 MutexLocker readLocker(m_pendingReadingLock);
1441                 
1442                 if (m_iconsPendingReading.contains(icons[i])) {
1443                     // Set the new data
1444                     icons[i]->setImageData(imageData.get());
1445                     
1446                     // Remove this icon from the set that needs to be read
1447                     m_iconsPendingReading.remove(icons[i]);
1448                     
1449                     // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
1450                     // We want to find the intersection of these two sets to notify them
1451                     // Check the sizes of these two sets to minimize the number of iterations
1452                     const HashSet<String>* outerHash;
1453                     const HashSet<String>* innerHash;
1454                     
1455                     if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
1456                         outerHash = &m_pageURLsInterestedInIcons;
1457                         innerHash = &(icons[i]->retainingPageURLs());
1458                     } else {
1459                         innerHash = &m_pageURLsInterestedInIcons;
1460                         outerHash = &(icons[i]->retainingPageURLs());
1461                     }
1462                     
1463                     HashSet<String>::const_iterator iter = outerHash->begin();
1464                     HashSet<String>::const_iterator end = outerHash->end();
1465                     for (; iter != end; ++iter) {
1466                         if (innerHash->contains(*iter)) {
1467                             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());
1468                             urlsToNotify.add(*iter);
1469                         }
1470                         
1471                         // If we ever get to the point were we've seen every url interested in this icon, break early
1472                         if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1473                             break;
1474                     }
1475                     
1476                     // 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
1477                     if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1478                         m_pageURLsInterestedInIcons.clear();
1479                     else {
1480                         iter = urlsToNotify.begin();
1481                         end = urlsToNotify.end();
1482                         for (; iter != end; ++iter)
1483                             m_pageURLsInterestedInIcons.remove(*iter);
1484                     }
1485                 }
1486             }
1487         }
1488     
1489         if (shouldStopThreadActivity())
1490             return didAnyWork;
1491         
1492         // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1493         // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1494         AutodrainedPool pool(25);
1495
1496         // Now that we don't hold any locks, perform the actual notifications
1497         HashSet<String>::iterator iter = urlsToNotify.begin();
1498         HashSet<String>::iterator end = urlsToNotify.end();
1499         for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
1500             LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
1501             m_client->dispatchDidAddIconForPageURL(*iter);
1502             if (shouldStopThreadActivity())
1503                 return didAnyWork;
1504             
1505             pool.cycle();
1506         }
1507
1508         LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
1509         urlsToNotify.clear();
1510         
1511         if (shouldStopThreadActivity())
1512             return didAnyWork;
1513     }
1514
1515     LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
1516
1517     return didAnyWork;
1518 }
1519
1520 bool IconDatabase::writeToDatabase()
1521 {
1522     ASSERT_ICON_SYNC_THREAD();
1523
1524 #ifndef NDEBUG
1525     double timeStamp = currentTime();
1526 #endif
1527
1528     bool didAnyWork = false;
1529     
1530     // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
1531     // we'll pick it up on the next pass.  This greatly simplifies the locking strategy for this method and remains cohesive with changes
1532     // asked for by the database on the main thread
1533     Vector<IconSnapshot> iconSnapshots;
1534     Vector<PageURLSnapshot> pageSnapshots;
1535     {
1536         MutexLocker locker(m_pendingSyncLock);
1537         
1538         iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
1539         m_iconsPendingSync.clear();
1540         
1541         pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
1542         m_pageURLsPendingSync.clear();
1543     }
1544     
1545     if (iconSnapshots.size() || pageSnapshots.size())
1546         didAnyWork = true;
1547         
1548     SQLiteTransaction syncTransaction(m_syncDB);
1549     syncTransaction.begin();
1550     
1551     for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
1552         writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
1553         LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp);
1554     }
1555     
1556     for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
1557         // If the icon URL is empty, this page is meant to be deleted
1558         // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
1559         if (pageSnapshots[i].iconURL.isEmpty())
1560             removePageURLFromSQLDatabase(pageSnapshots[i].pageURL);
1561         else
1562             setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL);
1563         LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data());
1564     }
1565
1566     syncTransaction.commit();
1567     
1568     // 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
1569     if (didAnyWork)
1570         checkForDanglingPageURLs(false);
1571
1572     LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
1573
1574     return didAnyWork;
1575 }
1576
1577 void IconDatabase::pruneUnretainedIcons()
1578 {
1579     ASSERT_ICON_SYNC_THREAD();
1580
1581     if (!isOpen())
1582         return;        
1583     
1584     // This method should only be called once per run
1585     ASSERT(!m_initialPruningComplete);
1586
1587     // This method relies on having read in all page URLs from the database earlier.
1588     ASSERT(m_iconURLImportComplete);
1589
1590     // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
1591     Vector<int64_t> pageIDsToDelete; 
1592
1593     SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
1594     pageSQL.prepare();
1595     
1596     int result;
1597     while ((result = pageSQL.step()) == SQLResultRow) {
1598         MutexLocker locker(m_urlAndIconLock);
1599         if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1)))
1600             pageIDsToDelete.append(pageSQL.getColumnInt64(0));
1601     }
1602     
1603     if (result != SQLResultDone)
1604         LOG_ERROR("Error reading PageURL table from on-disk DB");
1605     pageSQL.finalize();
1606     
1607     // Delete page URLs that were in the table, but not in our retain count set.
1608     size_t numToDelete = pageIDsToDelete.size();
1609     if (numToDelete) {
1610         SQLiteTransaction pruningTransaction(m_syncDB);
1611         pruningTransaction.begin();
1612         
1613         SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
1614         pageDeleteSQL.prepare();
1615         for (size_t i = 0; i < numToDelete; ++i) {
1616             LOG(IconDatabase, "Pruning page with rowid %lli from disk", pageIDsToDelete[i]);
1617             pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
1618             int result = pageDeleteSQL.step();
1619             if (result != SQLResultDone)
1620                 LOG_ERROR("Unabled to delete page with id %lli from disk", pageIDsToDelete[i]);
1621             pageDeleteSQL.reset();
1622             
1623             // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
1624             // finish the rest later (hopefully)
1625             if (shouldStopThreadActivity()) {
1626                 pruningTransaction.commit();
1627                 return;
1628             }
1629         }
1630         pruningTransaction.commit();
1631         pageDeleteSQL.finalize();
1632     }
1633     
1634     // Deleting unreferenced icons from the Icon tables has to be atomic - 
1635     // If the user quits while these are taking place, they might have to wait.  Thankfully this will rarely be an issue
1636     // A user on a network home directory with a wildly inconsistent database might see quite a pause...
1637
1638     SQLiteTransaction pruningTransaction(m_syncDB);
1639     pruningTransaction.begin();
1640     
1641     // Wipe Icons that aren't retained
1642     if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1643         LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");    
1644     if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1645         LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");    
1646     
1647     pruningTransaction.commit();
1648         
1649     checkForDanglingPageURLs(true);
1650
1651     m_initialPruningComplete = true;
1652 }
1653
1654 void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
1655 {
1656     ASSERT_ICON_SYNC_THREAD();
1657
1658     // 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
1659     // entries.  We also don't want to keep performing this check and reporting this error if it has already found danglers before so we
1660     // keep track of whether we've found any.  We skip the check in the release build pretending to have already found danglers already.
1661 #ifndef NDEBUG
1662     static bool danglersFound = true;
1663 #else
1664     static bool danglersFound = false;
1665 #endif
1666
1667     if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
1668         danglersFound = true;
1669         LOG(IconDatabase, "Dangling PageURL entries found");
1670         if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
1671             LOG(IconDatabase, "Unable to prune dangling PageURLs");
1672     }
1673 }
1674
1675 void IconDatabase::removeAllIconsOnThread()
1676 {
1677     ASSERT_ICON_SYNC_THREAD();
1678
1679     LOG(IconDatabase, "Removing all icons on the sync thread");
1680         
1681     // Delete all the prepared statements so they can start over
1682     deleteAllPreparedStatements();    
1683     
1684     // To reset the on-disk database, we'll wipe all its tables then vacuum it
1685     // This is easier and safer than closing it, deleting the file, and recreating from scratch
1686     m_syncDB.clearAllTables();
1687     m_syncDB.runVacuumCommand();
1688     createDatabaseTables(m_syncDB);
1689     
1690     LOG(IconDatabase, "Dispatching notification that we removed all icons");
1691     m_client->dispatchDidRemoveAllIcons();    
1692 }
1693
1694 void IconDatabase::deleteAllPreparedStatements()
1695 {        
1696     ASSERT_ICON_SYNC_THREAD();
1697     
1698     m_setIconIDForPageURLStatement.set(0);
1699     m_removePageURLStatement.set(0);
1700     m_getIconIDForIconURLStatement.set(0);
1701     m_getImageDataForIconURLStatement.set(0);
1702     m_addIconToIconInfoStatement.set(0);
1703     m_addIconToIconDataStatement.set(0);
1704     m_getImageDataStatement.set(0);
1705     m_deletePageURLsForIconURLStatement.set(0);
1706     m_deleteIconFromIconInfoStatement.set(0);
1707     m_deleteIconFromIconDataStatement.set(0);
1708     m_updateIconInfoStatement.set(0);
1709     m_updateIconDataStatement.set(0);
1710     m_setIconInfoStatement.set(0);
1711     m_setIconDataStatement.set(0);
1712 }
1713
1714 void* IconDatabase::cleanupSyncThread()
1715 {
1716     ASSERT_ICON_SYNC_THREAD();
1717     
1718 #ifndef NDEBUG
1719     double timeStamp = currentTime();
1720 #endif 
1721
1722     // If the removeIcons flag is set, remove all icons from the db.
1723     if (m_removeIconsRequested)
1724         removeAllIconsOnThread();
1725
1726     // Sync remaining icons out
1727     LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
1728     writeToDatabase();
1729     
1730     // Close the database
1731     MutexLocker locker(m_syncLock);
1732     
1733     m_databaseDirectory = String();
1734     m_completeDatabasePath = String();
1735     deleteAllPreparedStatements();    
1736     m_syncDB.close();
1737     
1738 #ifndef NDEBUG
1739     LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
1740 #endif
1741     
1742     m_syncThreadRunning = false;
1743     return 0;
1744 }
1745
1746 bool IconDatabase::imported()
1747 {
1748     ASSERT_ICON_SYNC_THREAD();
1749     
1750     if (m_isImportedSet)
1751         return m_imported;
1752         
1753     SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
1754     if (query.prepare() != SQLResultOk) {
1755         LOG_ERROR("Unable to prepare imported statement");
1756         return false;
1757     }
1758     
1759     int result = query.step();
1760     if (result == SQLResultRow)
1761         result = query.getColumnInt(0);
1762     else {
1763         if (result != SQLResultDone)
1764             LOG_ERROR("imported statement failed");
1765         result = 0;
1766     }
1767     
1768     m_isImportedSet = true;
1769     return m_imported = result;
1770 }
1771
1772 void IconDatabase::setImported(bool import)
1773 {
1774     ASSERT_ICON_SYNC_THREAD();
1775
1776     m_imported = import;
1777     m_isImportedSet = true;
1778     
1779     String queryString = import ?
1780         "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
1781         "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
1782         
1783     SQLiteStatement query(m_syncDB, queryString);
1784     
1785     if (query.prepare() != SQLResultOk) {
1786         LOG_ERROR("Unable to prepare set imported statement");
1787         return;
1788     }    
1789     
1790     if (query.step() != SQLResultDone)
1791         LOG_ERROR("set imported statement failed");
1792 }
1793
1794 // readySQLiteStatement() handles two things
1795 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade.  This happens when the user
1796 //     switches to and from private browsing
1797 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
1798 inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
1799 {
1800     if (statement && (statement->database() != &db || statement->isExpired())) {
1801         if (statement->isExpired())
1802             LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
1803         statement.set(0);
1804     }
1805     if (!statement) {
1806         statement.set(new SQLiteStatement(db, str));
1807         if (statement->prepare() != SQLResultOk)
1808             LOG_ERROR("Preparing statement %s failed", str.ascii().data());
1809     }
1810 }
1811
1812 void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
1813 {
1814     ASSERT_ICON_SYNC_THREAD();
1815     
1816     int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1817
1818     if (!iconID)
1819         iconID = addIconURLToSQLDatabase(iconURL);
1820     
1821     if (!iconID) {
1822         LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
1823         ASSERT(false);
1824         return;
1825     }
1826     
1827     setIconIDForPageURLInSQLDatabase(iconID, pageURL);
1828 }
1829
1830 void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
1831 {
1832     ASSERT_ICON_SYNC_THREAD();
1833     
1834     readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
1835     m_setIconIDForPageURLStatement->bindText(1, pageURL);
1836     m_setIconIDForPageURLStatement->bindInt64(2, iconID);
1837
1838     int result = m_setIconIDForPageURLStatement->step();
1839     if (result != SQLResultDone) {
1840         ASSERT(false);
1841         LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
1842     }
1843
1844     m_setIconIDForPageURLStatement->reset();
1845 }
1846
1847 void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
1848 {
1849     ASSERT_ICON_SYNC_THREAD();
1850     
1851     readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
1852     m_removePageURLStatement->bindText(1, pageURL);
1853
1854     if (m_removePageURLStatement->step() != SQLResultDone)
1855         LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
1856     
1857     m_removePageURLStatement->reset();
1858 }
1859
1860
1861 int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
1862 {
1863     ASSERT_ICON_SYNC_THREAD();
1864     
1865     readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
1866     m_getIconIDForIconURLStatement->bindText(1, iconURL);
1867     
1868     int64_t result = m_getIconIDForIconURLStatement->step();
1869     if (result == SQLResultRow)
1870         result = m_getIconIDForIconURLStatement->getColumnInt64(0);
1871     else {
1872         if (result != SQLResultDone)
1873             LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1874         result = 0;
1875     }
1876
1877     m_getIconIDForIconURLStatement->reset();
1878     return result;
1879 }
1880
1881 int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
1882 {
1883     ASSERT_ICON_SYNC_THREAD();
1884     
1885     // There would be a transaction here to make sure these two inserts are atomic
1886     // In practice the only caller of this method is always wrapped in a transaction itself so placing another
1887     // here is unnecessary
1888     
1889     readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
1890     m_addIconToIconInfoStatement->bindText(1, iconURL);
1891     
1892     int result = m_addIconToIconInfoStatement->step();
1893     m_addIconToIconInfoStatement->reset();
1894     if (result != SQLResultDone) {
1895         LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
1896         return 0;
1897     }
1898     int64_t iconID = m_syncDB.lastInsertRowID();
1899     
1900     readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
1901     m_addIconToIconDataStatement->bindInt64(1, iconID);
1902     
1903     result = m_addIconToIconDataStatement->step();
1904     m_addIconToIconDataStatement->reset();
1905     if (result != SQLResultDone) {
1906         LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
1907         return 0;
1908     }
1909     
1910     return iconID;
1911 }
1912
1913 PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
1914 {
1915     ASSERT_ICON_SYNC_THREAD();
1916     
1917     RefPtr<SharedBuffer> imageData;
1918     
1919     readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
1920     m_getImageDataForIconURLStatement->bindText(1, iconURL);
1921     
1922     int result = m_getImageDataForIconURLStatement->step();
1923     if (result == SQLResultRow) {
1924         Vector<char> data;
1925         m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
1926         imageData = SharedBuffer::create(data.data(), data.size());
1927     } else if (result != SQLResultDone)
1928         LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1929
1930     m_getImageDataForIconURLStatement->reset();
1931     
1932     return imageData.release();
1933 }
1934
1935 void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
1936 {
1937     ASSERT_ICON_SYNC_THREAD();
1938     
1939     if (iconURL.isEmpty())
1940         return;
1941
1942     // There would be a transaction here to make sure these removals are atomic
1943     // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1944     
1945     // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
1946     // icon is marked to be added then marked for removal before it is ever written to disk.  No big deal, early return
1947     int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1948     if (!iconID)
1949         return;
1950     
1951     readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
1952     m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
1953     
1954     if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
1955         LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1956     
1957     readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
1958     m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
1959     
1960     if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
1961         LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1962         
1963     readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
1964     m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
1965     
1966     if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
1967         LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1968         
1969     m_deletePageURLsForIconURLStatement->reset();
1970     m_deleteIconFromIconInfoStatement->reset();
1971     m_deleteIconFromIconDataStatement->reset();
1972 }
1973
1974 void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
1975 {
1976     ASSERT_ICON_SYNC_THREAD();
1977     
1978     if (snapshot.iconURL.isEmpty())
1979         return;
1980         
1981     // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
1982     if (!snapshot.timestamp && !snapshot.data) {
1983         LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data());
1984         removeIconFromSQLDatabase(snapshot.iconURL);
1985         return;
1986     }
1987
1988     // There would be a transaction here to make sure these removals are atomic
1989     // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1990         
1991     // Get the iconID for this url
1992     int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL);
1993     
1994     // If there is already an iconID in place, update the database.  
1995     // Otherwise, insert new records
1996     if (iconID) {    
1997         readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
1998         m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp);
1999         m_updateIconInfoStatement->bindText(2, snapshot.iconURL);
2000         m_updateIconInfoStatement->bindInt64(3, iconID);
2001
2002         if (m_updateIconInfoStatement->step() != SQLResultDone)
2003             LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2004         
2005         m_updateIconInfoStatement->reset();
2006         
2007         readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
2008         m_updateIconDataStatement->bindInt64(2, iconID);
2009                 
2010         // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 
2011         // signifying that this icon doesn't have any data    
2012         if (snapshot.data && snapshot.data->size())
2013             m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size());
2014         else
2015             m_updateIconDataStatement->bindNull(1);
2016         
2017         if (m_updateIconDataStatement->step() != SQLResultDone)
2018             LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2019
2020         m_updateIconDataStatement->reset();
2021     } else {    
2022         readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
2023         m_setIconInfoStatement->bindText(1, snapshot.iconURL);
2024         m_setIconInfoStatement->bindInt64(2, snapshot.timestamp);
2025
2026         if (m_setIconInfoStatement->step() != SQLResultDone)
2027             LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2028         
2029         m_setIconInfoStatement->reset();
2030         
2031         int64_t iconID = m_syncDB.lastInsertRowID();
2032
2033         readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
2034         m_setIconDataStatement->bindInt64(1, iconID);
2035
2036         // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 
2037         // signifying that this icon doesn't have any data    
2038         if (snapshot.data && snapshot.data->size())
2039             m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size());
2040         else
2041             m_setIconDataStatement->bindNull(2);
2042         
2043         if (m_setIconDataStatement->step() != SQLResultDone)
2044             LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2045
2046         m_setIconDataStatement->reset();
2047     }
2048 }
2049
2050 } // namespace WebCore