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