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