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