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