IconDatabase.cpp revision 81bc750723a18f21cd17d1b173cd2a4dda9cea6e
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 IconDatabase* sharedIconDatabase = 0;
62static int databaseCleanupCounter = 0;
63
64// This version number is in the DB and marks the current generation of the schema
65// Currently, a mismatched schema causes the DB to be wiped and reset.  This isn't
66// so bad during development but in the future, we would need to write a conversion
67// function to advance older released schemas to "current"
68static const int currentDatabaseVersion = 6;
69
70// Icons expire once every 4 days
71static const int iconExpirationTime = 60*60*24*4;
72
73static const int updateTimerDelay = 5;
74
75static bool checkIntegrityOnOpen = false;
76
77#ifndef NDEBUG
78static String urlForLogging(const String& url)
79{
80    static unsigned urlTruncationLength = 120;
81
82    if (url.length() < urlTruncationLength)
83        return url;
84    return url.substring(0, urlTruncationLength) + "...";
85}
86#endif
87
88static IconDatabaseClient* defaultClient()
89{
90    static IconDatabaseClient* defaultClient = new IconDatabaseClient();
91    return defaultClient;
92}
93
94IconDatabase& iconDatabase()
95{
96    if (!sharedIconDatabase) {
97        ScriptController::initializeThreading();
98        sharedIconDatabase = new IconDatabase;
99    }
100    return *sharedIconDatabase;
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& databasePath)
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 = databasePath.crossThreadString();
133
134    // Formulate the full path for the database file
135    m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename());
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::iconForPageURL(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    iconForPageURL(pageURL, IntSize(0,0));
305}
306
307String IconDatabase::iconURLForPageURL(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->dispatchDidAddIconForPageURL(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->dispatchDidAddIconForPageURL(pageURL);
641    }
642}
643
644IconLoadDecision IconDatabase::loadDecisionForIconURL(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    m_loadersPendingDecision.add(notificationDocumentLoader);
671
672    return IconLoadUnknown;
673}
674
675bool IconDatabase::iconDataKnownForIconURL(const String& iconURL)
676{
677    ASSERT_NOT_SYNC_THREAD();
678
679    MutexLocker locker(m_urlAndIconLock);
680    if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
681        return icon->imageDataStatus() != ImageDataStatusUnknown;
682
683    return false;
684}
685
686void IconDatabase::setEnabled(bool enabled)
687{
688    ASSERT_NOT_SYNC_THREAD();
689
690    if (!enabled && isOpen())
691        close();
692    m_isEnabled = enabled;
693}
694
695bool IconDatabase::isEnabled() const
696{
697    ASSERT_NOT_SYNC_THREAD();
698
699     return m_isEnabled;
700}
701
702void IconDatabase::setPrivateBrowsingEnabled(bool flag)
703{
704    m_privateBrowsingEnabled = flag;
705}
706
707bool IconDatabase::isPrivateBrowsingEnabled() const
708{
709    return m_privateBrowsingEnabled;
710}
711
712void IconDatabase::delayDatabaseCleanup()
713{
714    ++databaseCleanupCounter;
715    if (databaseCleanupCounter == 1)
716        LOG(IconDatabase, "Database cleanup is now DISABLED");
717}
718
719void IconDatabase::allowDatabaseCleanup()
720{
721    if (--databaseCleanupCounter < 0)
722        databaseCleanupCounter = 0;
723    if (databaseCleanupCounter == 0)
724        LOG(IconDatabase, "Database cleanup is now ENABLED");
725}
726
727void IconDatabase::checkIntegrityBeforeOpening()
728{
729    checkIntegrityOnOpen = true;
730}
731
732size_t IconDatabase::pageURLMappingCount()
733{
734    MutexLocker locker(m_urlAndIconLock);
735    return m_pageURLToRecordMap.size();
736}
737
738size_t IconDatabase::retainedPageURLCount()
739{
740    MutexLocker locker(m_urlAndIconLock);
741    return m_retainedPageURLs.size();
742}
743
744size_t IconDatabase::iconRecordCount()
745{
746    MutexLocker locker(m_urlAndIconLock);
747    return m_iconURLToRecordMap.size();
748}
749
750size_t IconDatabase::iconRecordCountWithData()
751{
752    MutexLocker locker(m_urlAndIconLock);
753    size_t result = 0;
754
755    HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
756    HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
757
758    for (; i != end; ++i)
759        result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
760
761    return result;
762}
763
764IconDatabase::IconDatabase()
765    : m_syncTimer(this, &IconDatabase::syncTimerFired)
766    , m_syncThreadRunning(false)
767    , m_isEnabled(false)
768    , m_privateBrowsingEnabled(false)
769    , m_threadTerminationRequested(false)
770    , m_removeIconsRequested(false)
771    , m_iconURLImportComplete(false)
772    , m_disabledSuddenTerminationForSyncThread(false)
773    , m_initialPruningComplete(false)
774    , m_client(defaultClient())
775    , m_imported(false)
776    , m_isImportedSet(false)
777{
778    ASSERT(isMainThread());
779}
780
781IconDatabase::~IconDatabase()
782{
783    ASSERT_NOT_REACHED();
784}
785
786void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context)
787{
788    static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions();
789}
790
791void IconDatabase::notifyPendingLoadDecisions()
792{
793    ASSERT_NOT_SYNC_THREAD();
794
795    // This method should only be called upon completion of the initial url import from the database
796    ASSERT(m_iconURLImportComplete);
797    LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
798
799    HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
800    HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
801
802    for (; i != end; ++i)
803        if ((*i)->refCount() > 1)
804            (*i)->iconLoadDecisionAvailable();
805
806    m_loadersPendingDecision.clear();
807}
808
809void IconDatabase::wakeSyncThread()
810{
811    MutexLocker locker(m_syncLock);
812
813    if (!m_disabledSuddenTerminationForSyncThread) {
814        m_disabledSuddenTerminationForSyncThread = true;
815        // The following is balanced by the call to enableSuddenTermination in the
816        // syncThreadMainLoop function.
817        // FIXME: It would be better to only disable sudden termination if we have
818        // something to write, not just if we have something to read.
819        disableSuddenTermination();
820    }
821
822    m_syncCondition.signal();
823}
824
825void IconDatabase::scheduleOrDeferSyncTimer()
826{
827    ASSERT_NOT_SYNC_THREAD();
828
829    if (!m_syncTimer.isActive()) {
830        // The following is balanced by the call to enableSuddenTermination in the
831        // syncTimerFired function.
832        disableSuddenTermination();
833    }
834
835    m_syncTimer.startOneShot(updateTimerDelay);
836}
837
838void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
839{
840    ASSERT_NOT_SYNC_THREAD();
841    wakeSyncThread();
842
843    // The following is balanced by the call to disableSuddenTermination in the
844    // scheduleOrDeferSyncTimer function.
845    enableSuddenTermination();
846}
847
848// ******************
849// *** Any Thread ***
850// ******************
851
852bool IconDatabase::isOpen() const
853{
854    MutexLocker locker(m_syncLock);
855    return m_syncDB.isOpen();
856}
857
858String IconDatabase::databasePath() const
859{
860    MutexLocker locker(m_syncLock);
861    return m_completeDatabasePath.threadsafeCopy();
862}
863
864String IconDatabase::defaultDatabaseFilename()
865{
866    DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db"));
867    return defaultDatabaseFilename.threadsafeCopy();
868}
869
870// Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import"
871PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL)
872{
873    // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method
874    ASSERT(!m_urlAndIconLock.tryLock());
875
876    if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
877        return icon;
878
879    RefPtr<IconRecord> newIcon = IconRecord::create(iconURL);
880    m_iconURLToRecordMap.set(iconURL, newIcon.get());
881
882    return newIcon.release();
883}
884
885// This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification
886PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL)
887{
888    // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method
889    ASSERT(!m_urlAndIconLock.tryLock());
890
891    if (pageURL.isEmpty())
892        return 0;
893
894    PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
895
896    MutexLocker locker(m_pendingReadingLock);
897    if (!m_iconURLImportComplete) {
898        // 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
899        if (!pageRecord) {
900            LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data());
901            pageRecord = new PageURLRecord(pageURL);
902            m_pageURLToRecordMap.set(pageURL, pageRecord);
903        }
904
905        // 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
906        // Mark the URL as "interested in the result of the import" then bail
907        if (!pageRecord->iconRecord()) {
908            m_pageURLsPendingImport.add(pageURL);
909            return 0;
910        }
911    }
912
913    // We've done the initial import of all URLs known in the database.  If this record doesn't exist now, it never will
914     return pageRecord;
915}
916
917
918// ************************
919// *** Sync Thread Only ***
920// ************************
921
922void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL)
923{
924    ASSERT_ICON_SYNC_THREAD();
925
926    // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty
927    ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty());
928
929    setIconURLForPageURLInSQLDatabase(iconURL, pageURL);
930}
931
932void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL)
933{
934    ASSERT_ICON_SYNC_THREAD();
935
936    ASSERT(!iconURL.isEmpty());
937
938    writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get()));
939}
940
941bool IconDatabase::shouldStopThreadActivity() const
942{
943    ASSERT_ICON_SYNC_THREAD();
944
945    return m_threadTerminationRequested || m_removeIconsRequested;
946}
947
948void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase)
949{
950    IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase);
951
952    return iconDB->iconDatabaseSyncThread();
953}
954
955void* IconDatabase::iconDatabaseSyncThread()
956{
957    // 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
958    // to our thread structure hasn't been filled in yet.
959    // 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
960    // prevent us from running before that call completes
961    m_syncLock.lock();
962    m_syncLock.unlock();
963
964    ASSERT_ICON_SYNC_THREAD();
965
966    LOG(IconDatabase, "(THREAD) IconDatabase sync thread started");
967
968#ifndef NDEBUG
969    double startTime = currentTime();
970#endif
971
972    // Need to create the database path if it doesn't already exist
973    makeAllDirectories(m_databaseDirectory);
974
975    // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies
976    // us to do an integrity check
977    String journalFilename = m_completeDatabasePath + "-journal";
978    if (!checkIntegrityOnOpen) {
979        AutodrainedPool pool;
980        checkIntegrityOnOpen = fileExists(journalFilename);
981    }
982
983    {
984        MutexLocker locker(m_syncLock);
985        if (!m_syncDB.open(m_completeDatabasePath)) {
986            LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
987            return 0;
988        }
989    }
990
991    if (shouldStopThreadActivity())
992        return syncThreadMainLoop();
993
994#ifndef NDEBUG
995    double timeStamp = currentTime();
996    LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime);
997#endif
998
999    performOpenInitialization();
1000    if (shouldStopThreadActivity())
1001        return syncThreadMainLoop();
1002
1003#ifndef NDEBUG
1004    double newStamp = currentTime();
1005    LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1006    timeStamp = newStamp;
1007#endif
1008
1009    if (!imported()) {
1010        LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure");
1011        SQLiteTransaction importTransaction(m_syncDB);
1012        importTransaction.begin();
1013
1014        // Commit the transaction only if the import completes (the import should be atomic)
1015        if (m_client->performImport()) {
1016            setImported(true);
1017            importTransaction.commit();
1018        } else {
1019            LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled");
1020            importTransaction.rollback();
1021        }
1022
1023        if (shouldStopThreadActivity())
1024            return syncThreadMainLoop();
1025
1026#ifndef NDEBUG
1027        newStamp = currentTime();
1028        LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1029        timeStamp = newStamp;
1030#endif
1031    }
1032
1033    // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories)
1034    // while (currentTime() - timeStamp < 10);
1035
1036    // Read in URL mappings from the database
1037    LOG(IconDatabase, "(THREAD) Starting iconURL import");
1038    performURLImport();
1039
1040    if (shouldStopThreadActivity())
1041        return syncThreadMainLoop();
1042
1043#ifndef NDEBUG
1044    newStamp = currentTime();
1045    LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds.  Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1046#endif
1047
1048    LOG(IconDatabase, "(THREAD) Beginning sync");
1049    return syncThreadMainLoop();
1050}
1051
1052static int databaseVersionNumber(SQLiteDatabase& db)
1053{
1054    return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
1055}
1056
1057static bool isValidDatabase(SQLiteDatabase& db)
1058{
1059    // These four tables should always exist in a valid db
1060    if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
1061        return false;
1062
1063    if (databaseVersionNumber(db) < currentDatabaseVersion) {
1064        LOG(IconDatabase, "DB version is not found or below expected valid version");
1065        return false;
1066    }
1067
1068    return true;
1069}
1070
1071static void createDatabaseTables(SQLiteDatabase& db)
1072{
1073    if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
1074        LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1075        db.close();
1076        return;
1077    }
1078    if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
1079        LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1080        db.close();
1081        return;
1082    }
1083    if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) {
1084        LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1085        db.close();
1086        return;
1087    }
1088    if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
1089        LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1090        db.close();
1091        return;
1092    }
1093    if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
1094        LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1095        db.close();
1096        return;
1097    }
1098    if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
1099        LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1100        db.close();
1101        return;
1102    }
1103    if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
1104        LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1105        db.close();
1106        return;
1107    }
1108    if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
1109        LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
1110        db.close();
1111        return;
1112    }
1113}
1114
1115void IconDatabase::performOpenInitialization()
1116{
1117    ASSERT_ICON_SYNC_THREAD();
1118
1119    if (!isOpen())
1120        return;
1121
1122    if (checkIntegrityOnOpen) {
1123        checkIntegrityOnOpen = false;
1124        if (!checkIntegrity()) {
1125            LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
1126
1127            m_syncDB.close();
1128
1129            {
1130                MutexLocker locker(m_syncLock);
1131                // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
1132                deleteFile(m_completeDatabasePath + "-journal");
1133                deleteFile(m_completeDatabasePath);
1134            }
1135
1136            // Reopen the write database, creating it from scratch
1137            if (!m_syncDB.open(m_completeDatabasePath)) {
1138                LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
1139                return;
1140            }
1141        }
1142    }
1143
1144    int version = databaseVersionNumber(m_syncDB);
1145
1146    if (version > currentDatabaseVersion) {
1147        LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
1148        m_syncDB.close();
1149        m_threadTerminationRequested = true;
1150        return;
1151    }
1152
1153    if (!isValidDatabase(m_syncDB)) {
1154        LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data());
1155        m_syncDB.clearAllTables();
1156        createDatabaseTables(m_syncDB);
1157    }
1158
1159    // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
1160    if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
1161        LOG_ERROR("SQLite database could not set cache_size");
1162
1163    // Tell backup software (i.e., Time Machine) to never back up the icon database, because
1164    // it's a large file that changes frequently, thus using a lot of backup disk space, and
1165    // it's unlikely that many users would be upset about it not being backed up. We could
1166    // make this configurable on a per-client basis some day if some clients don't want this.
1167    if (canExcludeFromBackup() && !wasExcludedFromBackup() && excludeFromBackup(m_completeDatabasePath))
1168        setWasExcludedFromBackup();
1169}
1170
1171bool IconDatabase::checkIntegrity()
1172{
1173    ASSERT_ICON_SYNC_THREAD();
1174
1175    SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
1176    if (integrity.prepare() != SQLResultOk) {
1177        LOG_ERROR("checkIntegrity failed to execute");
1178        return false;
1179    }
1180
1181    int resultCode = integrity.step();
1182    if (resultCode == SQLResultOk)
1183        return true;
1184
1185    if (resultCode != SQLResultRow)
1186        return false;
1187
1188    int columns = integrity.columnCount();
1189    if (columns != 1) {
1190        LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
1191        return false;
1192    }
1193
1194    String resultText = integrity.getColumnText(0);
1195
1196    // A successful, no-error integrity check will be "ok" - all other strings imply failure
1197    if (resultText == "ok")
1198        return true;
1199
1200    LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
1201    return false;
1202}
1203
1204void IconDatabase::performURLImport()
1205{
1206    ASSERT_ICON_SYNC_THREAD();
1207
1208    SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
1209
1210    if (query.prepare() != SQLResultOk) {
1211        LOG_ERROR("Unable to prepare icon url import query");
1212        return;
1213    }
1214
1215    // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1216    // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1217    AutodrainedPool pool(25);
1218
1219    int result = query.step();
1220    while (result == SQLResultRow) {
1221        String pageURL = query.getColumnText(0);
1222        String iconURL = query.getColumnText(1);
1223
1224        {
1225            MutexLocker locker(m_urlAndIconLock);
1226
1227            PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
1228
1229            // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
1230            // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
1231            // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
1232            // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
1233            // in - we'll prune it later instead!
1234            if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) {
1235                pageRecord = new PageURLRecord(pageURL);
1236                m_pageURLToRecordMap.set(pageURL, pageRecord);
1237            }
1238
1239            if (pageRecord) {
1240                IconRecord* currentIcon = pageRecord->iconRecord();
1241
1242                if (!currentIcon || currentIcon->iconURL() != iconURL) {
1243                    pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
1244                    currentIcon = pageRecord->iconRecord();
1245                }
1246
1247                // 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
1248                // so we marked the timestamp as "now", but it's really much older
1249                currentIcon->setTimestamp(query.getColumnInt(2));
1250            }
1251        }
1252
1253        // 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
1254        // 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 -
1255        // one for the URL and one for the Image itself
1256        // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
1257        {
1258            MutexLocker locker(m_pendingReadingLock);
1259            if (m_pageURLsPendingImport.contains(pageURL)) {
1260                m_client->dispatchDidAddIconForPageURL(pageURL);
1261                m_pageURLsPendingImport.remove(pageURL);
1262
1263                pool.cycle();
1264            }
1265        }
1266
1267        // Stop the import at any time of the thread has been asked to shutdown
1268        if (shouldStopThreadActivity()) {
1269            LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
1270            return;
1271        }
1272
1273        result = query.step();
1274    }
1275
1276    if (result != SQLResultDone)
1277        LOG(IconDatabase, "Error reading page->icon url mappings from database");
1278
1279    // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
1280    // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
1281    Vector<String> urls;
1282    {
1283        MutexLocker locker(m_pendingReadingLock);
1284
1285        urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
1286        m_pageURLsPendingImport.clear();
1287        m_iconURLImportComplete = true;
1288    }
1289
1290    Vector<String> urlsToNotify;
1291
1292    // Loop through the urls pending import
1293    // Remove unretained ones if database cleanup is allowed
1294    // Keep a set of ones that are retained and pending notification
1295
1296    {
1297        MutexLocker locker(m_urlAndIconLock);
1298
1299        for (unsigned i = 0; i < urls.size(); ++i) {
1300            if (!m_retainedPageURLs.contains(urls[i])) {
1301                PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
1302                if (record && !databaseCleanupCounter) {
1303                    m_pageURLToRecordMap.remove(urls[i]);
1304                    IconRecord* iconRecord = record->iconRecord();
1305
1306                    // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
1307                    // reading anything related to it
1308                    if (iconRecord && iconRecord->hasOneRef()) {
1309                        m_iconURLToRecordMap.remove(iconRecord->iconURL());
1310
1311                        {
1312                            MutexLocker locker(m_pendingReadingLock);
1313                            m_pageURLsInterestedInIcons.remove(urls[i]);
1314                            m_iconsPendingReading.remove(iconRecord);
1315                        }
1316                        {
1317                            MutexLocker locker(m_pendingSyncLock);
1318                            m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
1319                        }
1320                    }
1321
1322                    delete record;
1323                }
1324            } else {
1325                urlsToNotify.append(urls[i]);
1326            }
1327        }
1328    }
1329
1330    LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size()));
1331    // Now that we don't hold any locks, perform the actual notifications
1332    for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
1333        LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
1334        m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]);
1335        if (shouldStopThreadActivity())
1336            return;
1337
1338        pool.cycle();
1339    }
1340
1341    // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
1342    callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this);
1343}
1344
1345void* IconDatabase::syncThreadMainLoop()
1346{
1347    ASSERT_ICON_SYNC_THREAD();
1348
1349    bool shouldReenableSuddenTermination = false;
1350
1351    m_syncLock.lock();
1352
1353    // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
1354    while (!m_threadTerminationRequested) {
1355        m_syncLock.unlock();
1356
1357#ifndef NDEBUG
1358        double timeStamp = currentTime();
1359#endif
1360        LOG(IconDatabase, "(THREAD) Main work loop starting");
1361
1362        // 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
1363        if (m_removeIconsRequested) {
1364            removeAllIconsOnThread();
1365            m_removeIconsRequested = false;
1366        }
1367
1368        // Then, if the thread should be quitting, quit now!
1369        if (m_threadTerminationRequested)
1370            break;
1371
1372        bool didAnyWork = true;
1373        while (didAnyWork) {
1374            bool didWrite = writeToDatabase();
1375            if (shouldStopThreadActivity())
1376                break;
1377
1378            didAnyWork = readFromDatabase();
1379            if (shouldStopThreadActivity())
1380                break;
1381
1382            // Prune unretained icons after the first time we sync anything out to the database
1383            // This way, pruning won't be the only operation we perform to the database by itself
1384            // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
1385            // or if private browsing is enabled
1386            // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
1387            // has asked to delay pruning
1388            static bool prunedUnretainedIcons = false;
1389            if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
1390#ifndef NDEBUG
1391                double time = currentTime();
1392#endif
1393                LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
1394
1395                pruneUnretainedIcons();
1396
1397                LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
1398
1399                // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
1400                // to mark prunedUnretainedIcons true because we're about to terminate anyway
1401                prunedUnretainedIcons = true;
1402            }
1403
1404            didAnyWork = didAnyWork || didWrite;
1405            if (shouldStopThreadActivity())
1406                break;
1407        }
1408
1409#ifndef NDEBUG
1410        double newstamp = currentTime();
1411        LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
1412#endif
1413
1414        m_syncLock.lock();
1415
1416        // There is some condition that is asking us to stop what we're doing now and handle a special case
1417        // This is either removing all icons, or shutting down the thread to quit the app
1418        // We handle those at the top of this main loop so continue to jump back up there
1419        if (shouldStopThreadActivity())
1420            continue;
1421
1422        if (shouldReenableSuddenTermination) {
1423            // The following is balanced by the call to disableSuddenTermination in the
1424            // wakeSyncThread function. Any time we wait on the condition, we also have
1425            // to enableSuddenTermation, after doing the next batch of work.
1426            ASSERT(m_disabledSuddenTerminationForSyncThread);
1427            enableSuddenTermination();
1428            m_disabledSuddenTerminationForSyncThread = false;
1429        }
1430
1431        m_syncCondition.wait(m_syncLock);
1432
1433        shouldReenableSuddenTermination = true;
1434    }
1435
1436    m_syncLock.unlock();
1437
1438    // Thread is terminating at this point
1439    cleanupSyncThread();
1440
1441    if (shouldReenableSuddenTermination) {
1442        // The following is balanced by the call to disableSuddenTermination in the
1443        // wakeSyncThread function. Any time we wait on the condition, we also have
1444        // to enableSuddenTermation, after doing the next batch of work.
1445        ASSERT(m_disabledSuddenTerminationForSyncThread);
1446        enableSuddenTermination();
1447        m_disabledSuddenTerminationForSyncThread = false;
1448    }
1449
1450    return 0;
1451}
1452
1453bool IconDatabase::readFromDatabase()
1454{
1455    ASSERT_ICON_SYNC_THREAD();
1456
1457#ifndef NDEBUG
1458    double timeStamp = currentTime();
1459#endif
1460
1461    bool didAnyWork = false;
1462
1463    // 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
1464    // This way we won't hold the lock for a long period of time
1465    Vector<IconRecord*> icons;
1466    {
1467        MutexLocker locker(m_pendingReadingLock);
1468        icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
1469    }
1470
1471    // Keep track of icons we actually read to notify them of the new icon
1472    HashSet<String> urlsToNotify;
1473
1474    for (unsigned i = 0; i < icons.size(); ++i) {
1475        didAnyWork = true;
1476        RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
1477
1478        // Verify this icon still wants to be read from disk
1479        {
1480            MutexLocker urlLocker(m_urlAndIconLock);
1481            {
1482                MutexLocker readLocker(m_pendingReadingLock);
1483
1484                if (m_iconsPendingReading.contains(icons[i])) {
1485                    // Set the new data
1486                    icons[i]->setImageData(imageData.get());
1487
1488                    // Remove this icon from the set that needs to be read
1489                    m_iconsPendingReading.remove(icons[i]);
1490
1491                    // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
1492                    // We want to find the intersection of these two sets to notify them
1493                    // Check the sizes of these two sets to minimize the number of iterations
1494                    const HashSet<String>* outerHash;
1495                    const HashSet<String>* innerHash;
1496
1497                    if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
1498                        outerHash = &m_pageURLsInterestedInIcons;
1499                        innerHash = &(icons[i]->retainingPageURLs());
1500                    } else {
1501                        innerHash = &m_pageURLsInterestedInIcons;
1502                        outerHash = &(icons[i]->retainingPageURLs());
1503                    }
1504
1505                    HashSet<String>::const_iterator iter = outerHash->begin();
1506                    HashSet<String>::const_iterator end = outerHash->end();
1507                    for (; iter != end; ++iter) {
1508                        if (innerHash->contains(*iter)) {
1509                            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());
1510                            urlsToNotify.add(*iter);
1511                        }
1512
1513                        // If we ever get to the point were we've seen every url interested in this icon, break early
1514                        if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1515                            break;
1516                    }
1517
1518                    // 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
1519                    if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1520                        m_pageURLsInterestedInIcons.clear();
1521                    else {
1522                        iter = urlsToNotify.begin();
1523                        end = urlsToNotify.end();
1524                        for (; iter != end; ++iter)
1525                            m_pageURLsInterestedInIcons.remove(*iter);
1526                    }
1527                }
1528            }
1529        }
1530
1531        if (shouldStopThreadActivity())
1532            return didAnyWork;
1533
1534        // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1535        // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1536        AutodrainedPool pool(25);
1537
1538        // Now that we don't hold any locks, perform the actual notifications
1539        HashSet<String>::iterator iter = urlsToNotify.begin();
1540        HashSet<String>::iterator end = urlsToNotify.end();
1541        for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
1542            LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
1543            m_client->dispatchDidAddIconForPageURL(*iter);
1544            if (shouldStopThreadActivity())
1545                return didAnyWork;
1546
1547            pool.cycle();
1548        }
1549
1550        LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
1551        urlsToNotify.clear();
1552
1553        if (shouldStopThreadActivity())
1554            return didAnyWork;
1555    }
1556
1557    LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
1558
1559    return didAnyWork;
1560}
1561
1562bool IconDatabase::writeToDatabase()
1563{
1564    ASSERT_ICON_SYNC_THREAD();
1565
1566#ifndef NDEBUG
1567    double timeStamp = currentTime();
1568#endif
1569
1570    bool didAnyWork = false;
1571
1572    // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
1573    // we'll pick it up on the next pass.  This greatly simplifies the locking strategy for this method and remains cohesive with changes
1574    // asked for by the database on the main thread
1575    Vector<IconSnapshot> iconSnapshots;
1576    Vector<PageURLSnapshot> pageSnapshots;
1577    {
1578        MutexLocker locker(m_pendingSyncLock);
1579
1580        iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
1581        m_iconsPendingSync.clear();
1582
1583        pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
1584        m_pageURLsPendingSync.clear();
1585    }
1586
1587    if (iconSnapshots.size() || pageSnapshots.size())
1588        didAnyWork = true;
1589
1590    SQLiteTransaction syncTransaction(m_syncDB);
1591    syncTransaction.begin();
1592
1593    for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
1594        writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
1595        LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp);
1596    }
1597
1598    for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
1599        // If the icon URL is empty, this page is meant to be deleted
1600        // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
1601        if (pageSnapshots[i].iconURL.isEmpty())
1602            removePageURLFromSQLDatabase(pageSnapshots[i].pageURL);
1603        else
1604            setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL);
1605        LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data());
1606    }
1607
1608    syncTransaction.commit();
1609
1610    // 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
1611    if (didAnyWork)
1612        checkForDanglingPageURLs(false);
1613
1614    LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
1615
1616    return didAnyWork;
1617}
1618
1619void IconDatabase::pruneUnretainedIcons()
1620{
1621    ASSERT_ICON_SYNC_THREAD();
1622
1623    if (!isOpen())
1624        return;
1625
1626    // This method should only be called once per run
1627    ASSERT(!m_initialPruningComplete);
1628
1629    // This method relies on having read in all page URLs from the database earlier.
1630    ASSERT(m_iconURLImportComplete);
1631
1632    // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
1633    Vector<int64_t> pageIDsToDelete;
1634
1635    SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
1636    pageSQL.prepare();
1637
1638    int result;
1639    while ((result = pageSQL.step()) == SQLResultRow) {
1640        MutexLocker locker(m_urlAndIconLock);
1641        if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1)))
1642            pageIDsToDelete.append(pageSQL.getColumnInt64(0));
1643    }
1644
1645    if (result != SQLResultDone)
1646        LOG_ERROR("Error reading PageURL table from on-disk DB");
1647    pageSQL.finalize();
1648
1649    // Delete page URLs that were in the table, but not in our retain count set.
1650    size_t numToDelete = pageIDsToDelete.size();
1651    if (numToDelete) {
1652        SQLiteTransaction pruningTransaction(m_syncDB);
1653        pruningTransaction.begin();
1654
1655        SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
1656        pageDeleteSQL.prepare();
1657        for (size_t i = 0; i < numToDelete; ++i) {
1658#if OS(WINDOWS)
1659            LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
1660#else
1661            LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
1662#endif
1663            pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
1664            int result = pageDeleteSQL.step();
1665            if (result != SQLResultDone)
1666#if OS(WINDOWS)
1667                LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
1668#else
1669                LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
1670#endif
1671            pageDeleteSQL.reset();
1672
1673            // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
1674            // finish the rest later (hopefully)
1675            if (shouldStopThreadActivity()) {
1676                pruningTransaction.commit();
1677                return;
1678            }
1679        }
1680        pruningTransaction.commit();
1681        pageDeleteSQL.finalize();
1682    }
1683
1684    // Deleting unreferenced icons from the Icon tables has to be atomic -
1685    // If the user quits while these are taking place, they might have to wait.  Thankfully this will rarely be an issue
1686    // A user on a network home directory with a wildly inconsistent database might see quite a pause...
1687
1688    SQLiteTransaction pruningTransaction(m_syncDB);
1689    pruningTransaction.begin();
1690
1691    // Wipe Icons that aren't retained
1692    if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1693        LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
1694    if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1695        LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
1696
1697    pruningTransaction.commit();
1698
1699    checkForDanglingPageURLs(true);
1700
1701    m_initialPruningComplete = true;
1702}
1703
1704void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
1705{
1706    ASSERT_ICON_SYNC_THREAD();
1707
1708    // 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
1709    // entries.  We also don't want to keep performing this check and reporting this error if it has already found danglers before so we
1710    // keep track of whether we've found any.  We skip the check in the release build pretending to have already found danglers already.
1711#ifndef NDEBUG
1712    static bool danglersFound = true;
1713#else
1714    static bool danglersFound = false;
1715#endif
1716
1717    if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
1718        danglersFound = true;
1719        LOG(IconDatabase, "Dangling PageURL entries found");
1720        if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
1721            LOG(IconDatabase, "Unable to prune dangling PageURLs");
1722    }
1723}
1724
1725void IconDatabase::removeAllIconsOnThread()
1726{
1727    ASSERT_ICON_SYNC_THREAD();
1728
1729    LOG(IconDatabase, "Removing all icons on the sync thread");
1730
1731    // Delete all the prepared statements so they can start over
1732    deleteAllPreparedStatements();
1733
1734    // To reset the on-disk database, we'll wipe all its tables then vacuum it
1735    // This is easier and safer than closing it, deleting the file, and recreating from scratch
1736    m_syncDB.clearAllTables();
1737    m_syncDB.runVacuumCommand();
1738    createDatabaseTables(m_syncDB);
1739
1740    LOG(IconDatabase, "Dispatching notification that we removed all icons");
1741    m_client->dispatchDidRemoveAllIcons();
1742}
1743
1744void IconDatabase::deleteAllPreparedStatements()
1745{
1746    ASSERT_ICON_SYNC_THREAD();
1747
1748    m_setIconIDForPageURLStatement.clear();
1749    m_removePageURLStatement.clear();
1750    m_getIconIDForIconURLStatement.clear();
1751    m_getImageDataForIconURLStatement.clear();
1752    m_addIconToIconInfoStatement.clear();
1753    m_addIconToIconDataStatement.clear();
1754    m_getImageDataStatement.clear();
1755    m_deletePageURLsForIconURLStatement.clear();
1756    m_deleteIconFromIconInfoStatement.clear();
1757    m_deleteIconFromIconDataStatement.clear();
1758    m_updateIconInfoStatement.clear();
1759    m_updateIconDataStatement.clear();
1760    m_setIconInfoStatement.clear();
1761    m_setIconDataStatement.clear();
1762}
1763
1764void* IconDatabase::cleanupSyncThread()
1765{
1766    ASSERT_ICON_SYNC_THREAD();
1767
1768#ifndef NDEBUG
1769    double timeStamp = currentTime();
1770#endif
1771
1772    // If the removeIcons flag is set, remove all icons from the db.
1773    if (m_removeIconsRequested)
1774        removeAllIconsOnThread();
1775
1776    // Sync remaining icons out
1777    LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
1778    writeToDatabase();
1779
1780    // Close the database
1781    MutexLocker locker(m_syncLock);
1782
1783    m_databaseDirectory = String();
1784    m_completeDatabasePath = String();
1785    deleteAllPreparedStatements();
1786    m_syncDB.close();
1787
1788#ifndef NDEBUG
1789    LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
1790#endif
1791
1792    m_syncThreadRunning = false;
1793    return 0;
1794}
1795
1796bool IconDatabase::imported()
1797{
1798    ASSERT_ICON_SYNC_THREAD();
1799
1800    if (m_isImportedSet)
1801        return m_imported;
1802
1803    SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
1804    if (query.prepare() != SQLResultOk) {
1805        LOG_ERROR("Unable to prepare imported statement");
1806        return false;
1807    }
1808
1809    int result = query.step();
1810    if (result == SQLResultRow)
1811        result = query.getColumnInt(0);
1812    else {
1813        if (result != SQLResultDone)
1814            LOG_ERROR("imported statement failed");
1815        result = 0;
1816    }
1817
1818    m_isImportedSet = true;
1819    return m_imported = result;
1820}
1821
1822void IconDatabase::setImported(bool import)
1823{
1824    ASSERT_ICON_SYNC_THREAD();
1825
1826    m_imported = import;
1827    m_isImportedSet = true;
1828
1829    String queryString = import ?
1830        "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
1831        "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
1832
1833    SQLiteStatement query(m_syncDB, queryString);
1834
1835    if (query.prepare() != SQLResultOk) {
1836        LOG_ERROR("Unable to prepare set imported statement");
1837        return;
1838    }
1839
1840    if (query.step() != SQLResultDone)
1841        LOG_ERROR("set imported statement failed");
1842}
1843
1844// readySQLiteStatement() handles two things
1845// 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade.  This happens when the user
1846//     switches to and from private browsing
1847// 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
1848inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
1849{
1850    if (statement && (statement->database() != &db || statement->isExpired())) {
1851        if (statement->isExpired())
1852            LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
1853        statement.set(0);
1854    }
1855    if (!statement) {
1856        statement = adoptPtr(new SQLiteStatement(db, str));
1857        if (statement->prepare() != SQLResultOk)
1858            LOG_ERROR("Preparing statement %s failed", str.ascii().data());
1859    }
1860}
1861
1862void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
1863{
1864    ASSERT_ICON_SYNC_THREAD();
1865
1866    int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1867
1868    if (!iconID)
1869        iconID = addIconURLToSQLDatabase(iconURL);
1870
1871    if (!iconID) {
1872        LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
1873        ASSERT(false);
1874        return;
1875    }
1876
1877    setIconIDForPageURLInSQLDatabase(iconID, pageURL);
1878}
1879
1880void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
1881{
1882    ASSERT_ICON_SYNC_THREAD();
1883
1884    readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
1885    m_setIconIDForPageURLStatement->bindText(1, pageURL);
1886    m_setIconIDForPageURLStatement->bindInt64(2, iconID);
1887
1888    int result = m_setIconIDForPageURLStatement->step();
1889    if (result != SQLResultDone) {
1890        ASSERT(false);
1891        LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
1892    }
1893
1894    m_setIconIDForPageURLStatement->reset();
1895}
1896
1897void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
1898{
1899    ASSERT_ICON_SYNC_THREAD();
1900
1901    readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
1902    m_removePageURLStatement->bindText(1, pageURL);
1903
1904    if (m_removePageURLStatement->step() != SQLResultDone)
1905        LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
1906
1907    m_removePageURLStatement->reset();
1908}
1909
1910
1911int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
1912{
1913    ASSERT_ICON_SYNC_THREAD();
1914
1915    readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
1916    m_getIconIDForIconURLStatement->bindText(1, iconURL);
1917
1918    int64_t result = m_getIconIDForIconURLStatement->step();
1919    if (result == SQLResultRow)
1920        result = m_getIconIDForIconURLStatement->getColumnInt64(0);
1921    else {
1922        if (result != SQLResultDone)
1923            LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1924        result = 0;
1925    }
1926
1927    m_getIconIDForIconURLStatement->reset();
1928    return result;
1929}
1930
1931int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
1932{
1933    ASSERT_ICON_SYNC_THREAD();
1934
1935    // There would be a transaction here to make sure these two inserts are atomic
1936    // In practice the only caller of this method is always wrapped in a transaction itself so placing another
1937    // here is unnecessary
1938
1939    readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
1940    m_addIconToIconInfoStatement->bindText(1, iconURL);
1941
1942    int result = m_addIconToIconInfoStatement->step();
1943    m_addIconToIconInfoStatement->reset();
1944    if (result != SQLResultDone) {
1945        LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
1946        return 0;
1947    }
1948    int64_t iconID = m_syncDB.lastInsertRowID();
1949
1950    readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
1951    m_addIconToIconDataStatement->bindInt64(1, iconID);
1952
1953    result = m_addIconToIconDataStatement->step();
1954    m_addIconToIconDataStatement->reset();
1955    if (result != SQLResultDone) {
1956        LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
1957        return 0;
1958    }
1959
1960    return iconID;
1961}
1962
1963PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
1964{
1965    ASSERT_ICON_SYNC_THREAD();
1966
1967    RefPtr<SharedBuffer> imageData;
1968
1969    readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
1970    m_getImageDataForIconURLStatement->bindText(1, iconURL);
1971
1972    int result = m_getImageDataForIconURLStatement->step();
1973    if (result == SQLResultRow) {
1974        Vector<char> data;
1975        m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
1976        imageData = SharedBuffer::create(data.data(), data.size());
1977    } else if (result != SQLResultDone)
1978        LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1979
1980    m_getImageDataForIconURLStatement->reset();
1981
1982    return imageData.release();
1983}
1984
1985void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
1986{
1987    ASSERT_ICON_SYNC_THREAD();
1988
1989    if (iconURL.isEmpty())
1990        return;
1991
1992    // There would be a transaction here to make sure these removals are atomic
1993    // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1994
1995    // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
1996    // icon is marked to be added then marked for removal before it is ever written to disk.  No big deal, early return
1997    int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1998    if (!iconID)
1999        return;
2000
2001    readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
2002    m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
2003
2004    if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
2005        LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2006
2007    readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
2008    m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
2009
2010    if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
2011        LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2012
2013    readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
2014    m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
2015
2016    if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
2017        LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2018
2019    m_deletePageURLsForIconURLStatement->reset();
2020    m_deleteIconFromIconInfoStatement->reset();
2021    m_deleteIconFromIconDataStatement->reset();
2022}
2023
2024void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
2025{
2026    ASSERT_ICON_SYNC_THREAD();
2027
2028    if (snapshot.iconURL.isEmpty())
2029        return;
2030
2031    // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
2032    if (!snapshot.timestamp && !snapshot.data) {
2033        LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data());
2034        removeIconFromSQLDatabase(snapshot.iconURL);
2035        return;
2036    }
2037
2038    // There would be a transaction here to make sure these removals are atomic
2039    // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
2040
2041    // Get the iconID for this url
2042    int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL);
2043
2044    // If there is already an iconID in place, update the database.
2045    // Otherwise, insert new records
2046    if (iconID) {
2047        readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
2048        m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp);
2049        m_updateIconInfoStatement->bindText(2, snapshot.iconURL);
2050        m_updateIconInfoStatement->bindInt64(3, iconID);
2051
2052        if (m_updateIconInfoStatement->step() != SQLResultDone)
2053            LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2054
2055        m_updateIconInfoStatement->reset();
2056
2057        readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
2058        m_updateIconDataStatement->bindInt64(2, iconID);
2059
2060        // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2061        // signifying that this icon doesn't have any data
2062        if (snapshot.data && snapshot.data->size())
2063            m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size());
2064        else
2065            m_updateIconDataStatement->bindNull(1);
2066
2067        if (m_updateIconDataStatement->step() != SQLResultDone)
2068            LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2069
2070        m_updateIconDataStatement->reset();
2071    } else {
2072        readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
2073        m_setIconInfoStatement->bindText(1, snapshot.iconURL);
2074        m_setIconInfoStatement->bindInt64(2, snapshot.timestamp);
2075
2076        if (m_setIconInfoStatement->step() != SQLResultDone)
2077            LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2078
2079        m_setIconInfoStatement->reset();
2080
2081        int64_t iconID = m_syncDB.lastInsertRowID();
2082
2083        readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
2084        m_setIconDataStatement->bindInt64(1, iconID);
2085
2086        // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2087        // signifying that this icon doesn't have any data
2088        if (snapshot.data && snapshot.data->size())
2089            m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size());
2090        else
2091            m_setIconDataStatement->bindNull(2);
2092
2093        if (m_setIconDataStatement->step() != SQLResultDone)
2094            LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2095
2096        m_setIconDataStatement->reset();
2097    }
2098}
2099
2100bool IconDatabase::wasExcludedFromBackup()
2101{
2102    ASSERT_ICON_SYNC_THREAD();
2103
2104    return SQLiteStatement(m_syncDB, "SELECT value FROM IconDatabaseInfo WHERE key = 'ExcludedFromBackup';").getColumnInt(0);
2105}
2106
2107void IconDatabase::setWasExcludedFromBackup()
2108{
2109    ASSERT_ICON_SYNC_THREAD();
2110
2111    SQLiteStatement(m_syncDB, "INSERT INTO IconDatabaseInfo (key, value) VALUES ('ExcludedFromBackup', 1)").executeCommand();
2112}
2113
2114} // namespace WebCore
2115
2116#endif // ENABLE(ICONDATABASE)
2117