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