IconDatabase.cpp revision cad810f21b803229eb11403f9209855525a25d57
1/*
2 * Copyright (C) 2006, 2007, 2008, 2009 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
1060    // These four tables should always exist in a valid db
1061    if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
1062        return false;
1063
1064    if (databaseVersionNumber(db) < currentDatabaseVersion) {
1065        LOG(IconDatabase, "DB version is not found or below expected valid version");
1066        return false;
1067    }
1068
1069    return true;
1070}
1071
1072static void createDatabaseTables(SQLiteDatabase& db)
1073{
1074    if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
1075        LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1076        db.close();
1077        return;
1078    }
1079    if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
1080        LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1081        db.close();
1082        return;
1083    }
1084    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);")) {
1085        LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1086        db.close();
1087        return;
1088    }
1089    if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
1090        LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1091        db.close();
1092        return;
1093    }
1094    if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
1095        LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1096        db.close();
1097        return;
1098    }
1099    if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
1100        LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1101        db.close();
1102        return;
1103    }
1104    if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
1105        LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1106        db.close();
1107        return;
1108    }
1109    if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
1110        LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
1111        db.close();
1112        return;
1113    }
1114}
1115
1116void IconDatabase::performOpenInitialization()
1117{
1118    ASSERT_ICON_SYNC_THREAD();
1119
1120    if (!isOpen())
1121        return;
1122
1123    if (checkIntegrityOnOpen) {
1124        checkIntegrityOnOpen = false;
1125        if (!checkIntegrity()) {
1126            LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
1127
1128            m_syncDB.close();
1129
1130            {
1131                MutexLocker locker(m_syncLock);
1132                // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
1133                deleteFile(m_completeDatabasePath + "-journal");
1134                deleteFile(m_completeDatabasePath);
1135            }
1136
1137            // Reopen the write database, creating it from scratch
1138            if (!m_syncDB.open(m_completeDatabasePath)) {
1139                LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
1140                return;
1141            }
1142        }
1143    }
1144
1145    int version = databaseVersionNumber(m_syncDB);
1146
1147    if (version > currentDatabaseVersion) {
1148        LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
1149        m_syncDB.close();
1150        m_threadTerminationRequested = true;
1151        return;
1152    }
1153
1154    if (!isValidDatabase(m_syncDB)) {
1155        LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data());
1156        m_syncDB.clearAllTables();
1157        createDatabaseTables(m_syncDB);
1158    }
1159
1160    // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
1161    if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
1162        LOG_ERROR("SQLite database could not set cache_size");
1163}
1164
1165bool IconDatabase::checkIntegrity()
1166{
1167    ASSERT_ICON_SYNC_THREAD();
1168
1169    SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
1170    if (integrity.prepare() != SQLResultOk) {
1171        LOG_ERROR("checkIntegrity failed to execute");
1172        return false;
1173    }
1174
1175    int resultCode = integrity.step();
1176    if (resultCode == SQLResultOk)
1177        return true;
1178
1179    if (resultCode != SQLResultRow)
1180        return false;
1181
1182    int columns = integrity.columnCount();
1183    if (columns != 1) {
1184        LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
1185        return false;
1186    }
1187
1188    String resultText = integrity.getColumnText(0);
1189
1190    // A successful, no-error integrity check will be "ok" - all other strings imply failure
1191    if (resultText == "ok")
1192        return true;
1193
1194    LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
1195    return false;
1196}
1197
1198void IconDatabase::performURLImport()
1199{
1200    ASSERT_ICON_SYNC_THREAD();
1201
1202    SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
1203
1204    if (query.prepare() != SQLResultOk) {
1205        LOG_ERROR("Unable to prepare icon url import query");
1206        return;
1207    }
1208
1209    // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1210    // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1211    AutodrainedPool pool(25);
1212
1213    int result = query.step();
1214    while (result == SQLResultRow) {
1215        String pageURL = query.getColumnText(0);
1216        String iconURL = query.getColumnText(1);
1217
1218        {
1219            MutexLocker locker(m_urlAndIconLock);
1220
1221            PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
1222
1223            // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
1224            // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
1225            // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
1226            // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
1227            // in - we'll prune it later instead!
1228            if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) {
1229                pageRecord = new PageURLRecord(pageURL);
1230                m_pageURLToRecordMap.set(pageURL, pageRecord);
1231            }
1232
1233            if (pageRecord) {
1234                IconRecord* currentIcon = pageRecord->iconRecord();
1235
1236                if (!currentIcon || currentIcon->iconURL() != iconURL) {
1237                    pageRecord->setIconRecord(getOrCreateIconRecord(iconURL));
1238                    currentIcon = pageRecord->iconRecord();
1239                }
1240
1241                // 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
1242                // so we marked the timestamp as "now", but it's really much older
1243                currentIcon->setTimestamp(query.getColumnInt(2));
1244            }
1245        }
1246
1247        // 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
1248        // 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 -
1249        // one for the URL and one for the Image itself
1250        // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
1251        {
1252            MutexLocker locker(m_pendingReadingLock);
1253            if (m_pageURLsPendingImport.contains(pageURL)) {
1254                m_client->dispatchDidAddIconForPageURL(pageURL);
1255                m_pageURLsPendingImport.remove(pageURL);
1256
1257                pool.cycle();
1258            }
1259        }
1260
1261        // Stop the import at any time of the thread has been asked to shutdown
1262        if (shouldStopThreadActivity()) {
1263            LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
1264            return;
1265        }
1266
1267        result = query.step();
1268    }
1269
1270    if (result != SQLResultDone)
1271        LOG(IconDatabase, "Error reading page->icon url mappings from database");
1272
1273    // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
1274    // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
1275    Vector<String> urls;
1276    {
1277        MutexLocker locker(m_pendingReadingLock);
1278
1279        urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
1280        m_pageURLsPendingImport.clear();
1281        m_iconURLImportComplete = true;
1282    }
1283
1284    Vector<String> urlsToNotify;
1285
1286    // Loop through the urls pending import
1287    // Remove unretained ones if database cleanup is allowed
1288    // Keep a set of ones that are retained and pending notification
1289
1290    {
1291        MutexLocker locker(m_urlAndIconLock);
1292
1293        for (unsigned i = 0; i < urls.size(); ++i) {
1294            if (!m_retainedPageURLs.contains(urls[i])) {
1295                PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
1296                if (record && !databaseCleanupCounter) {
1297                    m_pageURLToRecordMap.remove(urls[i]);
1298                    IconRecord* iconRecord = record->iconRecord();
1299
1300                    // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
1301                    // reading anything related to it
1302                    if (iconRecord && iconRecord->hasOneRef()) {
1303                        m_iconURLToRecordMap.remove(iconRecord->iconURL());
1304
1305                        {
1306                            MutexLocker locker(m_pendingReadingLock);
1307                            m_pageURLsInterestedInIcons.remove(urls[i]);
1308                            m_iconsPendingReading.remove(iconRecord);
1309                        }
1310                        {
1311                            MutexLocker locker(m_pendingSyncLock);
1312                            m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
1313                        }
1314                    }
1315
1316                    delete record;
1317                }
1318            } else {
1319                urlsToNotify.append(urls[i]);
1320            }
1321        }
1322    }
1323
1324    LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size()));
1325    // Now that we don't hold any locks, perform the actual notifications
1326    for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
1327        LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
1328        m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]);
1329        if (shouldStopThreadActivity())
1330            return;
1331
1332        pool.cycle();
1333    }
1334
1335    // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
1336    callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this);
1337}
1338
1339void* IconDatabase::syncThreadMainLoop()
1340{
1341    ASSERT_ICON_SYNC_THREAD();
1342
1343    bool shouldReenableSuddenTermination = false;
1344
1345    m_syncLock.lock();
1346
1347    // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
1348    while (!m_threadTerminationRequested) {
1349        m_syncLock.unlock();
1350
1351#ifndef NDEBUG
1352        double timeStamp = currentTime();
1353#endif
1354        LOG(IconDatabase, "(THREAD) Main work loop starting");
1355
1356        // 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
1357        if (m_removeIconsRequested) {
1358            removeAllIconsOnThread();
1359            m_removeIconsRequested = false;
1360        }
1361
1362        // Then, if the thread should be quitting, quit now!
1363        if (m_threadTerminationRequested)
1364            break;
1365
1366        bool didAnyWork = true;
1367        while (didAnyWork) {
1368            bool didWrite = writeToDatabase();
1369            if (shouldStopThreadActivity())
1370                break;
1371
1372            didAnyWork = readFromDatabase();
1373            if (shouldStopThreadActivity())
1374                break;
1375
1376            // Prune unretained icons after the first time we sync anything out to the database
1377            // This way, pruning won't be the only operation we perform to the database by itself
1378            // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
1379            // or if private browsing is enabled
1380            // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
1381            // has asked to delay pruning
1382            static bool prunedUnretainedIcons = false;
1383            if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
1384#ifndef NDEBUG
1385                double time = currentTime();
1386#endif
1387                LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
1388
1389                pruneUnretainedIcons();
1390
1391                LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
1392
1393                // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
1394                // to mark prunedUnretainedIcons true because we're about to terminate anyway
1395                prunedUnretainedIcons = true;
1396            }
1397
1398            didAnyWork = didAnyWork || didWrite;
1399            if (shouldStopThreadActivity())
1400                break;
1401        }
1402
1403#ifndef NDEBUG
1404        double newstamp = currentTime();
1405        LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
1406#endif
1407
1408        m_syncLock.lock();
1409
1410        // There is some condition that is asking us to stop what we're doing now and handle a special case
1411        // This is either removing all icons, or shutting down the thread to quit the app
1412        // We handle those at the top of this main loop so continue to jump back up there
1413        if (shouldStopThreadActivity())
1414            continue;
1415
1416        if (shouldReenableSuddenTermination) {
1417            // The following is balanced by the call to disableSuddenTermination in the
1418            // wakeSyncThread function. Any time we wait on the condition, we also have
1419            // to enableSuddenTermation, after doing the next batch of work.
1420            ASSERT(m_disabledSuddenTerminationForSyncThread);
1421            enableSuddenTermination();
1422            m_disabledSuddenTerminationForSyncThread = false;
1423        }
1424
1425        m_syncCondition.wait(m_syncLock);
1426
1427        shouldReenableSuddenTermination = true;
1428    }
1429
1430    m_syncLock.unlock();
1431
1432    // Thread is terminating at this point
1433    cleanupSyncThread();
1434
1435    if (shouldReenableSuddenTermination) {
1436        // The following is balanced by the call to disableSuddenTermination in the
1437        // wakeSyncThread function. Any time we wait on the condition, we also have
1438        // to enableSuddenTermation, after doing the next batch of work.
1439        ASSERT(m_disabledSuddenTerminationForSyncThread);
1440        enableSuddenTermination();
1441        m_disabledSuddenTerminationForSyncThread = false;
1442    }
1443
1444    return 0;
1445}
1446
1447bool IconDatabase::readFromDatabase()
1448{
1449    ASSERT_ICON_SYNC_THREAD();
1450
1451#ifndef NDEBUG
1452    double timeStamp = currentTime();
1453#endif
1454
1455    bool didAnyWork = false;
1456
1457    // 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
1458    // This way we won't hold the lock for a long period of time
1459    Vector<IconRecord*> icons;
1460    {
1461        MutexLocker locker(m_pendingReadingLock);
1462        icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
1463    }
1464
1465    // Keep track of icons we actually read to notify them of the new icon
1466    HashSet<String> urlsToNotify;
1467
1468    for (unsigned i = 0; i < icons.size(); ++i) {
1469        didAnyWork = true;
1470        RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
1471
1472        // Verify this icon still wants to be read from disk
1473        {
1474            MutexLocker urlLocker(m_urlAndIconLock);
1475            {
1476                MutexLocker readLocker(m_pendingReadingLock);
1477
1478                if (m_iconsPendingReading.contains(icons[i])) {
1479                    // Set the new data
1480                    icons[i]->setImageData(imageData.get());
1481
1482                    // Remove this icon from the set that needs to be read
1483                    m_iconsPendingReading.remove(icons[i]);
1484
1485                    // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
1486                    // We want to find the intersection of these two sets to notify them
1487                    // Check the sizes of these two sets to minimize the number of iterations
1488                    const HashSet<String>* outerHash;
1489                    const HashSet<String>* innerHash;
1490
1491                    if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
1492                        outerHash = &m_pageURLsInterestedInIcons;
1493                        innerHash = &(icons[i]->retainingPageURLs());
1494                    } else {
1495                        innerHash = &m_pageURLsInterestedInIcons;
1496                        outerHash = &(icons[i]->retainingPageURLs());
1497                    }
1498
1499                    HashSet<String>::const_iterator iter = outerHash->begin();
1500                    HashSet<String>::const_iterator end = outerHash->end();
1501                    for (; iter != end; ++iter) {
1502                        if (innerHash->contains(*iter)) {
1503                            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());
1504                            urlsToNotify.add(*iter);
1505                        }
1506
1507                        // If we ever get to the point were we've seen every url interested in this icon, break early
1508                        if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1509                            break;
1510                    }
1511
1512                    // 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
1513                    if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1514                        m_pageURLsInterestedInIcons.clear();
1515                    else {
1516                        iter = urlsToNotify.begin();
1517                        end = urlsToNotify.end();
1518                        for (; iter != end; ++iter)
1519                            m_pageURLsInterestedInIcons.remove(*iter);
1520                    }
1521                }
1522            }
1523        }
1524
1525        if (shouldStopThreadActivity())
1526            return didAnyWork;
1527
1528        // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1529        // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1530        AutodrainedPool pool(25);
1531
1532        // Now that we don't hold any locks, perform the actual notifications
1533        HashSet<String>::iterator iter = urlsToNotify.begin();
1534        HashSet<String>::iterator end = urlsToNotify.end();
1535        for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
1536            LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
1537            m_client->dispatchDidAddIconForPageURL(*iter);
1538            if (shouldStopThreadActivity())
1539                return didAnyWork;
1540
1541            pool.cycle();
1542        }
1543
1544        LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
1545        urlsToNotify.clear();
1546
1547        if (shouldStopThreadActivity())
1548            return didAnyWork;
1549    }
1550
1551    LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
1552
1553    return didAnyWork;
1554}
1555
1556bool IconDatabase::writeToDatabase()
1557{
1558    ASSERT_ICON_SYNC_THREAD();
1559
1560#ifndef NDEBUG
1561    double timeStamp = currentTime();
1562#endif
1563
1564    bool didAnyWork = false;
1565
1566    // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
1567    // we'll pick it up on the next pass.  This greatly simplifies the locking strategy for this method and remains cohesive with changes
1568    // asked for by the database on the main thread
1569    Vector<IconSnapshot> iconSnapshots;
1570    Vector<PageURLSnapshot> pageSnapshots;
1571    {
1572        MutexLocker locker(m_pendingSyncLock);
1573
1574        iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
1575        m_iconsPendingSync.clear();
1576
1577        pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
1578        m_pageURLsPendingSync.clear();
1579    }
1580
1581    if (iconSnapshots.size() || pageSnapshots.size())
1582        didAnyWork = true;
1583
1584    SQLiteTransaction syncTransaction(m_syncDB);
1585    syncTransaction.begin();
1586
1587    for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
1588        writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
1589        LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp);
1590    }
1591
1592    for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
1593        // If the icon URL is empty, this page is meant to be deleted
1594        // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
1595        if (pageSnapshots[i].iconURL.isEmpty())
1596            removePageURLFromSQLDatabase(pageSnapshots[i].pageURL);
1597        else
1598            setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL);
1599        LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data());
1600    }
1601
1602    syncTransaction.commit();
1603
1604    // 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
1605    if (didAnyWork)
1606        checkForDanglingPageURLs(false);
1607
1608    LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
1609
1610    return didAnyWork;
1611}
1612
1613void IconDatabase::pruneUnretainedIcons()
1614{
1615    ASSERT_ICON_SYNC_THREAD();
1616
1617    if (!isOpen())
1618        return;
1619
1620    // This method should only be called once per run
1621    ASSERT(!m_initialPruningComplete);
1622
1623    // This method relies on having read in all page URLs from the database earlier.
1624    ASSERT(m_iconURLImportComplete);
1625
1626    // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
1627    Vector<int64_t> pageIDsToDelete;
1628
1629    SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
1630    pageSQL.prepare();
1631
1632    int result;
1633    while ((result = pageSQL.step()) == SQLResultRow) {
1634        MutexLocker locker(m_urlAndIconLock);
1635        if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1)))
1636            pageIDsToDelete.append(pageSQL.getColumnInt64(0));
1637    }
1638
1639    if (result != SQLResultDone)
1640        LOG_ERROR("Error reading PageURL table from on-disk DB");
1641    pageSQL.finalize();
1642
1643    // Delete page URLs that were in the table, but not in our retain count set.
1644    size_t numToDelete = pageIDsToDelete.size();
1645    if (numToDelete) {
1646        SQLiteTransaction pruningTransaction(m_syncDB);
1647        pruningTransaction.begin();
1648
1649        SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
1650        pageDeleteSQL.prepare();
1651        for (size_t i = 0; i < numToDelete; ++i) {
1652#if OS(WINDOWS)
1653            LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
1654#else
1655            LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
1656#endif
1657            pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
1658            int result = pageDeleteSQL.step();
1659            if (result != SQLResultDone)
1660#if OS(WINDOWS)
1661                LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i]));
1662#else
1663                LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i]));
1664#endif
1665            pageDeleteSQL.reset();
1666
1667            // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
1668            // finish the rest later (hopefully)
1669            if (shouldStopThreadActivity()) {
1670                pruningTransaction.commit();
1671                return;
1672            }
1673        }
1674        pruningTransaction.commit();
1675        pageDeleteSQL.finalize();
1676    }
1677
1678    // Deleting unreferenced icons from the Icon tables has to be atomic -
1679    // If the user quits while these are taking place, they might have to wait.  Thankfully this will rarely be an issue
1680    // A user on a network home directory with a wildly inconsistent database might see quite a pause...
1681
1682    SQLiteTransaction pruningTransaction(m_syncDB);
1683    pruningTransaction.begin();
1684
1685    // Wipe Icons that aren't retained
1686    if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1687        LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
1688    if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1689        LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
1690
1691    pruningTransaction.commit();
1692
1693    checkForDanglingPageURLs(true);
1694
1695    m_initialPruningComplete = true;
1696}
1697
1698void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
1699{
1700    ASSERT_ICON_SYNC_THREAD();
1701
1702    // 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
1703    // entries.  We also don't want to keep performing this check and reporting this error if it has already found danglers before so we
1704    // keep track of whether we've found any.  We skip the check in the release build pretending to have already found danglers already.
1705#ifndef NDEBUG
1706    static bool danglersFound = true;
1707#else
1708    static bool danglersFound = false;
1709#endif
1710
1711    if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
1712        danglersFound = true;
1713        LOG(IconDatabase, "Dangling PageURL entries found");
1714        if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
1715            LOG(IconDatabase, "Unable to prune dangling PageURLs");
1716    }
1717}
1718
1719void IconDatabase::removeAllIconsOnThread()
1720{
1721    ASSERT_ICON_SYNC_THREAD();
1722
1723    LOG(IconDatabase, "Removing all icons on the sync thread");
1724
1725    // Delete all the prepared statements so they can start over
1726    deleteAllPreparedStatements();
1727
1728    // To reset the on-disk database, we'll wipe all its tables then vacuum it
1729    // This is easier and safer than closing it, deleting the file, and recreating from scratch
1730    m_syncDB.clearAllTables();
1731    m_syncDB.runVacuumCommand();
1732    createDatabaseTables(m_syncDB);
1733
1734    LOG(IconDatabase, "Dispatching notification that we removed all icons");
1735    m_client->dispatchDidRemoveAllIcons();
1736}
1737
1738void IconDatabase::deleteAllPreparedStatements()
1739{
1740    ASSERT_ICON_SYNC_THREAD();
1741
1742    m_setIconIDForPageURLStatement.clear();
1743    m_removePageURLStatement.clear();
1744    m_getIconIDForIconURLStatement.clear();
1745    m_getImageDataForIconURLStatement.clear();
1746    m_addIconToIconInfoStatement.clear();
1747    m_addIconToIconDataStatement.clear();
1748    m_getImageDataStatement.clear();
1749    m_deletePageURLsForIconURLStatement.clear();
1750    m_deleteIconFromIconInfoStatement.clear();
1751    m_deleteIconFromIconDataStatement.clear();
1752    m_updateIconInfoStatement.clear();
1753    m_updateIconDataStatement.clear();
1754    m_setIconInfoStatement.clear();
1755    m_setIconDataStatement.clear();
1756}
1757
1758void* IconDatabase::cleanupSyncThread()
1759{
1760    ASSERT_ICON_SYNC_THREAD();
1761
1762#ifndef NDEBUG
1763    double timeStamp = currentTime();
1764#endif
1765
1766    // If the removeIcons flag is set, remove all icons from the db.
1767    if (m_removeIconsRequested)
1768        removeAllIconsOnThread();
1769
1770    // Sync remaining icons out
1771    LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
1772    writeToDatabase();
1773
1774    // Close the database
1775    MutexLocker locker(m_syncLock);
1776
1777    m_databaseDirectory = String();
1778    m_completeDatabasePath = String();
1779    deleteAllPreparedStatements();
1780    m_syncDB.close();
1781
1782#ifndef NDEBUG
1783    LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
1784#endif
1785
1786    m_syncThreadRunning = false;
1787    return 0;
1788}
1789
1790bool IconDatabase::imported()
1791{
1792    ASSERT_ICON_SYNC_THREAD();
1793
1794    if (m_isImportedSet)
1795        return m_imported;
1796
1797    SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
1798    if (query.prepare() != SQLResultOk) {
1799        LOG_ERROR("Unable to prepare imported statement");
1800        return false;
1801    }
1802
1803    int result = query.step();
1804    if (result == SQLResultRow)
1805        result = query.getColumnInt(0);
1806    else {
1807        if (result != SQLResultDone)
1808            LOG_ERROR("imported statement failed");
1809        result = 0;
1810    }
1811
1812    m_isImportedSet = true;
1813    return m_imported = result;
1814}
1815
1816void IconDatabase::setImported(bool import)
1817{
1818    ASSERT_ICON_SYNC_THREAD();
1819
1820    m_imported = import;
1821    m_isImportedSet = true;
1822
1823    String queryString = import ?
1824        "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
1825        "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
1826
1827    SQLiteStatement query(m_syncDB, queryString);
1828
1829    if (query.prepare() != SQLResultOk) {
1830        LOG_ERROR("Unable to prepare set imported statement");
1831        return;
1832    }
1833
1834    if (query.step() != SQLResultDone)
1835        LOG_ERROR("set imported statement failed");
1836}
1837
1838// readySQLiteStatement() handles two things
1839// 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade.  This happens when the user
1840//     switches to and from private browsing
1841// 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
1842inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
1843{
1844    if (statement && (statement->database() != &db || statement->isExpired())) {
1845        if (statement->isExpired())
1846            LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
1847        statement.set(0);
1848    }
1849    if (!statement) {
1850        statement = adoptPtr(new SQLiteStatement(db, str));
1851        if (statement->prepare() != SQLResultOk)
1852            LOG_ERROR("Preparing statement %s failed", str.ascii().data());
1853    }
1854}
1855
1856void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
1857{
1858    ASSERT_ICON_SYNC_THREAD();
1859
1860    int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1861
1862    if (!iconID)
1863        iconID = addIconURLToSQLDatabase(iconURL);
1864
1865    if (!iconID) {
1866        LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
1867        ASSERT(false);
1868        return;
1869    }
1870
1871    setIconIDForPageURLInSQLDatabase(iconID, pageURL);
1872}
1873
1874void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
1875{
1876    ASSERT_ICON_SYNC_THREAD();
1877
1878    readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
1879    m_setIconIDForPageURLStatement->bindText(1, pageURL);
1880    m_setIconIDForPageURLStatement->bindInt64(2, iconID);
1881
1882    int result = m_setIconIDForPageURLStatement->step();
1883    if (result != SQLResultDone) {
1884        ASSERT(false);
1885        LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
1886    }
1887
1888    m_setIconIDForPageURLStatement->reset();
1889}
1890
1891void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
1892{
1893    ASSERT_ICON_SYNC_THREAD();
1894
1895    readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
1896    m_removePageURLStatement->bindText(1, pageURL);
1897
1898    if (m_removePageURLStatement->step() != SQLResultDone)
1899        LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
1900
1901    m_removePageURLStatement->reset();
1902}
1903
1904
1905int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
1906{
1907    ASSERT_ICON_SYNC_THREAD();
1908
1909    readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
1910    m_getIconIDForIconURLStatement->bindText(1, iconURL);
1911
1912    int64_t result = m_getIconIDForIconURLStatement->step();
1913    if (result == SQLResultRow)
1914        result = m_getIconIDForIconURLStatement->getColumnInt64(0);
1915    else {
1916        if (result != SQLResultDone)
1917            LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1918        result = 0;
1919    }
1920
1921    m_getIconIDForIconURLStatement->reset();
1922    return result;
1923}
1924
1925int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
1926{
1927    ASSERT_ICON_SYNC_THREAD();
1928
1929    // There would be a transaction here to make sure these two inserts are atomic
1930    // In practice the only caller of this method is always wrapped in a transaction itself so placing another
1931    // here is unnecessary
1932
1933    readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
1934    m_addIconToIconInfoStatement->bindText(1, iconURL);
1935
1936    int result = m_addIconToIconInfoStatement->step();
1937    m_addIconToIconInfoStatement->reset();
1938    if (result != SQLResultDone) {
1939        LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
1940        return 0;
1941    }
1942    int64_t iconID = m_syncDB.lastInsertRowID();
1943
1944    readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
1945    m_addIconToIconDataStatement->bindInt64(1, iconID);
1946
1947    result = m_addIconToIconDataStatement->step();
1948    m_addIconToIconDataStatement->reset();
1949    if (result != SQLResultDone) {
1950        LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
1951        return 0;
1952    }
1953
1954    return iconID;
1955}
1956
1957PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
1958{
1959    ASSERT_ICON_SYNC_THREAD();
1960
1961    RefPtr<SharedBuffer> imageData;
1962
1963    readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
1964    m_getImageDataForIconURLStatement->bindText(1, iconURL);
1965
1966    int result = m_getImageDataForIconURLStatement->step();
1967    if (result == SQLResultRow) {
1968        Vector<char> data;
1969        m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
1970        imageData = SharedBuffer::create(data.data(), data.size());
1971    } else if (result != SQLResultDone)
1972        LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1973
1974    m_getImageDataForIconURLStatement->reset();
1975
1976    return imageData.release();
1977}
1978
1979void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
1980{
1981    ASSERT_ICON_SYNC_THREAD();
1982
1983    if (iconURL.isEmpty())
1984        return;
1985
1986    // There would be a transaction here to make sure these removals are atomic
1987    // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1988
1989    // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
1990    // icon is marked to be added then marked for removal before it is ever written to disk.  No big deal, early return
1991    int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1992    if (!iconID)
1993        return;
1994
1995    readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
1996    m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
1997
1998    if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
1999        LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2000
2001    readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
2002    m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
2003
2004    if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
2005        LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2006
2007    readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
2008    m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
2009
2010    if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
2011        LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
2012
2013    m_deletePageURLsForIconURLStatement->reset();
2014    m_deleteIconFromIconInfoStatement->reset();
2015    m_deleteIconFromIconDataStatement->reset();
2016}
2017
2018void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
2019{
2020    ASSERT_ICON_SYNC_THREAD();
2021
2022    if (snapshot.iconURL.isEmpty())
2023        return;
2024
2025    // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
2026    if (!snapshot.timestamp && !snapshot.data) {
2027        LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data());
2028        removeIconFromSQLDatabase(snapshot.iconURL);
2029        return;
2030    }
2031
2032    // There would be a transaction here to make sure these removals are atomic
2033    // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
2034
2035    // Get the iconID for this url
2036    int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL);
2037
2038    // If there is already an iconID in place, update the database.
2039    // Otherwise, insert new records
2040    if (iconID) {
2041        readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
2042        m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp);
2043        m_updateIconInfoStatement->bindText(2, snapshot.iconURL);
2044        m_updateIconInfoStatement->bindInt64(3, iconID);
2045
2046        if (m_updateIconInfoStatement->step() != SQLResultDone)
2047            LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2048
2049        m_updateIconInfoStatement->reset();
2050
2051        readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
2052        m_updateIconDataStatement->bindInt64(2, iconID);
2053
2054        // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2055        // signifying that this icon doesn't have any data
2056        if (snapshot.data && snapshot.data->size())
2057            m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size());
2058        else
2059            m_updateIconDataStatement->bindNull(1);
2060
2061        if (m_updateIconDataStatement->step() != SQLResultDone)
2062            LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2063
2064        m_updateIconDataStatement->reset();
2065    } else {
2066        readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
2067        m_setIconInfoStatement->bindText(1, snapshot.iconURL);
2068        m_setIconInfoStatement->bindInt64(2, snapshot.timestamp);
2069
2070        if (m_setIconInfoStatement->step() != SQLResultDone)
2071            LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2072
2073        m_setIconInfoStatement->reset();
2074
2075        int64_t iconID = m_syncDB.lastInsertRowID();
2076
2077        readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
2078        m_setIconDataStatement->bindInt64(1, iconID);
2079
2080        // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2081        // signifying that this icon doesn't have any data
2082        if (snapshot.data && snapshot.data->size())
2083            m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size());
2084        else
2085            m_setIconDataStatement->bindNull(2);
2086
2087        if (m_setIconDataStatement->step() != SQLResultDone)
2088            LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2089
2090        m_setIconDataStatement->reset();
2091    }
2092}
2093
2094} // namespace WebCore
2095
2096#endif // ENABLE(ICONDATABASE)
2097