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