2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
3 * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com)
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
28 #include "IconDatabase.h"
30 #include "AutodrainedPool.h"
32 #include "DocumentLoader.h"
33 #include "FileSystem.h"
34 #include "IconDatabaseClient.h"
35 #include "IconRecord.h"
40 #include "PageURLRecord.h"
41 #include "SQLiteStatement.h"
42 #include "SQLiteTransaction.h"
43 #include "SystemTime.h"
59 // For methods that are meant to support API from the main thread - should not be called internally
60 #define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
62 // For methods that are meant to support the sync thread ONLY
63 #define IS_ICON_SYNC_THREAD() m_syncThread == currentThread()
64 #define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
68 static IconDatabase* sharedIconDatabase = 0;
69 static int databaseCleanupCounter = 0;
71 // This version number is in the DB and marks the current generation of the schema
72 // Currently, a mismatched schema causes the DB to be wiped and reset. This isn't
73 // so bad during development but in the future, we would need to write a conversion
74 // function to advance older released schemas to "current"
75 const int currentDatabaseVersion = 6;
77 // Icons expire once every 4 days
78 const int iconExpirationTime = 60*60*24*4;
80 const int updateTimerDelay = 5;
82 static bool checkIntegrityOnOpen = false;
85 static String urlForLogging(const String& url)
87 static unsigned urlTruncationLength = 120;
89 if (url.length() < urlTruncationLength)
91 return url.substring(0, urlTruncationLength) + "...";
95 static IconDatabaseClient* defaultClient()
97 static IconDatabaseClient* defaultClient = new IconDatabaseClient();
101 IconDatabase* iconDatabase()
103 if (!sharedIconDatabase) {
104 initializeThreading();
105 sharedIconDatabase = new IconDatabase;
107 return sharedIconDatabase;
110 // ************************
111 // *** Main Thread Only ***
112 // ************************
114 void IconDatabase::setClient(IconDatabaseClient* client)
116 // We don't allow a null client, because we never null check it anywhere in this code
117 // Also don't allow a client change after the thread has already began
118 // (setting the client should occur before the database is opened)
120 ASSERT(!m_syncThreadRunning);
121 if (!client || m_syncThreadRunning)
127 bool IconDatabase::open(const String& databasePath)
129 ASSERT_NOT_SYNC_THREAD();
135 LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first.");
139 m_databaseDirectory = databasePath.copy();
141 // Formulate the full path for the database file
142 m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename());
144 // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call
145 // completes and m_syncThreadRunning is properly set
147 m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this);
153 void IconDatabase::close()
155 ASSERT_NOT_SYNC_THREAD();
157 if (m_syncThreadRunning) {
158 // Set the flag to tell the sync thread to wrap it up
159 m_threadTerminationRequested = true;
161 // Wake up the sync thread if it's waiting
164 // Wait for the sync thread to terminate
165 waitForThreadCompletion(m_syncThread, 0);
168 m_syncThreadRunning = false;
169 m_threadTerminationRequested = false;
170 m_removeIconsRequested = false;
173 void IconDatabase::removeAllIcons()
175 ASSERT_NOT_SYNC_THREAD();
180 LOG(IconDatabase, "Requesting background thread to remove all icons");
182 // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk
184 MutexLocker locker(m_urlAndIconLock);
186 // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted
187 // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of
188 HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin();
189 HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end();
190 for (; iter != end; ++iter)
191 (*iter).second->setIconRecord(0);
193 // Clear the iconURL -> IconRecord map
194 m_iconURLToRecordMap.clear();
196 // Clear all in-memory records of things that need to be synced out to disk
198 MutexLocker locker(m_pendingSyncLock);
199 m_pageURLsPendingSync.clear();
200 m_iconsPendingSync.clear();
203 // Clear all in-memory records of things that need to be read in from disk
205 MutexLocker locker(m_pendingReadingLock);
206 m_pageURLsPendingImport.clear();
207 m_pageURLsInterestedInIcons.clear();
208 m_iconsPendingReading.clear();
209 m_loadersPendingDecision.clear();
213 m_removeIconsRequested = true;
217 Image* IconDatabase::iconForPageURL(const String& pageURLOriginal, const IntSize& size, bool cache)
219 ASSERT_NOT_SYNC_THREAD();
221 ASSERT(!pageURLOriginal.isNull());
223 // pageURLOriginal can not be stored without being deep copied first.
224 // We should go our of our way to only copy it if we have to store it
227 return defaultIcon(size);
229 MutexLocker locker(m_urlAndIconLock);
231 String pageURLCopy; // Creates a null string for easy testing
233 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
235 pageURLCopy = pageURLOriginal.copy();
236 pageRecord = getOrCreatePageURLRecord(pageURLCopy);
239 // If pageRecord is NULL, one of two things is true -
240 // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists
241 // 2 - The initial url import IS complete and this pageURL has no icon
243 MutexLocker locker(m_pendingReadingLock);
245 // Import is ongoing, there might be an icon. In this case, register to be notified when the icon comes in
246 // If we ever reach this condition, we know we've already made the pageURL copy
247 if (!m_iconURLImportComplete)
248 m_pageURLsInterestedInIcons.add(pageURLCopy);
253 IconRecord* iconRecord = pageRecord->iconRecord();
255 // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon
256 // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so
257 // we can just bail now
258 if (!m_iconURLImportComplete && !iconRecord)
261 // 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
262 ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal));
267 // If it's a new IconRecord object that doesn't have its imageData set yet,
268 // mark it to be read by the background thread
269 if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) {
270 if (pageURLCopy.isNull())
271 pageURLCopy = pageURLOriginal.copy();
273 MutexLocker locker(m_pendingReadingLock);
274 m_pageURLsInterestedInIcons.add(pageURLCopy);
275 m_iconsPendingReading.add(iconRecord);
280 // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off
281 // and isn't actually interested in the image return value
282 if (size == IntSize(0, 0))
285 // PARANOID DISCUSSION: This method makes some assumptions. It returns a WebCore::image which the icon database might dispose of at anytime in the future,
286 // and Images aren't ref counted. So there is no way for the client to guarantee continued existence of the image.
287 // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image
288 // and drop the raw Image*. On Mac an NSImage, and on windows drawing into an HBITMAP.
289 // 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
290 // representation out of it?
291 // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache.
292 // This is because we make the assumption that anything in memory is newer than whatever is in the database.
293 // 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
294 // delete the image on the secondary thread if the image already exists.
295 return iconRecord->image(size);
298 void IconDatabase::readIconForPageURLFromDisk(const String& pageURL)
300 // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk
301 // if it hasn't already been set in memory. The special IntSize (0, 0) is a special way of telling
302 // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk.
303 iconForPageURL(pageURL, IntSize(0,0));
306 String IconDatabase::iconURLForPageURL(const String& pageURLOriginal)
308 ASSERT_NOT_SYNC_THREAD();
310 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
311 // Also, in the case we have a real answer for the caller, we must deep copy that as well
313 if (!isOpen() || pageURLOriginal.isEmpty())
316 MutexLocker locker(m_urlAndIconLock);
318 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
320 pageRecord = getOrCreatePageURLRecord(pageURLOriginal.copy());
322 // If pageRecord is NULL, one of two things is true -
323 // 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
324 // 2 - The initial url import IS complete and this pageURL has no icon
328 // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check
329 return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().copy() : String();
332 Image* IconDatabase::defaultIcon(const IntSize& size)
334 ASSERT_NOT_SYNC_THREAD();
336 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,
337 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,
338 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,
339 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,
340 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,
341 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,
342 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,
343 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,
344 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,
345 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,
346 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,
347 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,
348 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,
349 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,
350 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,
351 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,
352 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,
353 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,
354 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,
355 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,
356 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,
357 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,
358 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,
359 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,
360 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,
361 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,
362 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,
363 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,
364 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,
365 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,
366 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,
367 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,
368 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,
369 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,
370 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,
371 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,
372 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 };
374 static RefPtr<SharedBuffer> defaultIconBuffer(new SharedBuffer(defaultIconData, sizeof(defaultIconData)));
376 if (!m_defaultIconRecord) {
377 m_defaultIconRecord = new IconRecord("urlIcon");
378 m_defaultIconRecord->setImageData(defaultIconBuffer);
381 return m_defaultIconRecord->image(size);
385 void IconDatabase::retainIconForPageURL(const String& pageURLOriginal)
387 ASSERT_NOT_SYNC_THREAD();
389 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
391 if (!isEnabled() || pageURLOriginal.isEmpty())
394 MutexLocker locker(m_urlAndIconLock);
396 PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal);
401 pageURL = pageURLOriginal.copy();
403 record = new PageURLRecord(pageURL);
404 m_pageURLToRecordMap.set(pageURL, record);
407 if (!record->retain()) {
408 if (pageURL.isNull())
409 pageURL = pageURLOriginal.copy();
411 // This page just had its retain count bumped from 0 to 1 - Record that fact
412 m_retainedPageURLs.add(pageURL);
414 // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
415 // so we bail here and skip those steps
416 if (!m_iconURLImportComplete)
419 MutexLocker locker(m_pendingSyncLock);
420 // If this pageURL waiting to be sync'ed, update the sync record
421 // 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!
422 if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) {
423 LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data());
424 m_pageURLsPendingSync.set(pageURL, record->snapshot());
429 void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal)
431 ASSERT_NOT_SYNC_THREAD();
433 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
435 if (!isEnabled() || pageURLOriginal.isEmpty())
438 MutexLocker locker(m_urlAndIconLock);
440 // Check if this pageURL is actually retained
441 if (!m_retainedPageURLs.contains(pageURLOriginal)) {
442 LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data());
446 // Get its retain count - if it's retained, we'd better have a PageURLRecord for it
447 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
449 LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1);
450 ASSERT(pageRecord->retainCount() > 0);
452 // If it still has a positive retain count, store the new count and bail
453 if (pageRecord->release())
456 // This pageRecord has now been fully released. Do the appropriate cleanup
457 LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data());
458 m_pageURLToRecordMap.remove(pageURLOriginal);
459 m_retainedPageURLs.remove(pageURLOriginal);
461 // Grab the iconRecord for later use (and do a sanity check on it for kicks)
462 IconRecord* iconRecord = pageRecord->iconRecord();
464 ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord));
467 MutexLocker locker(m_pendingReadingLock);
469 // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results
470 if (!m_iconURLImportComplete)
471 m_pageURLsPendingImport.remove(pageURLOriginal);
472 m_pageURLsInterestedInIcons.remove(pageURLOriginal);
474 // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore
475 if (iconRecord && iconRecord->hasOneRef()) {
476 m_iconURLToRecordMap.remove(iconRecord->iconURL());
477 m_iconsPendingReading.remove(iconRecord);
481 // Mark stuff for deletion from the database only if we're not in private browsing
482 if (!m_privateBrowsingEnabled) {
483 MutexLocker locker(m_pendingSyncLock);
484 m_pageURLsPendingSync.set(pageURLOriginal.copy(), pageRecord->snapshot(true));
486 // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to
487 // be marked for deletion
488 if (iconRecord && iconRecord->hasOneRef())
489 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
495 scheduleOrDeferSyncTimer();
498 void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal)
500 ASSERT_NOT_SYNC_THREAD();
502 // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first
504 if (!isOpen() || iconURLOriginal.isEmpty())
507 RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : 0;
508 String iconURL = iconURLOriginal.copy();
510 Vector<String> pageURLs;
512 MutexLocker locker(m_urlAndIconLock);
514 // If this icon was pending a read, remove it from that set because this new data should override what is on disk
515 IconRecord* icon = m_iconURLToRecordMap.get(iconURL);
517 MutexLocker locker(m_pendingReadingLock);
518 m_iconsPendingReading.remove(icon);
520 icon = getOrCreateIconRecord(iconURL);
522 // Update the data and set the time stamp
523 icon->setImageData(data);
524 icon->setTimestamp((int)currentTime());
526 // Copy the current retaining pageURLs - if any - to notify them of the change
527 pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end());
529 // Mark the IconRecord as requiring an update to the database only if private browsing is disabled
530 if (!m_privateBrowsingEnabled) {
531 MutexLocker locker(m_pendingSyncLock);
532 m_iconsPendingSync.set(iconURL, icon->snapshot());
536 // Send notification out regarding all PageURLs that retain this icon
537 // But not if we're on the sync thread because that implies this mapping
538 // comes from the initial import which we don't want notifications for
539 if (!IS_ICON_SYNC_THREAD()) {
540 // Start the timer to commit this change - or further delay the timer if it was already started
541 scheduleOrDeferSyncTimer();
543 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
544 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
545 AutodrainedPool pool(25);
547 for (unsigned i = 0; i < pageURLs.size(); ++i) {
548 LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data());
549 m_client->dispatchDidAddIconForPageURL(pageURLs[i]);
556 void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal)
558 ASSERT_NOT_SYNC_THREAD();
560 // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first
562 ASSERT(!iconURLOriginal.isEmpty());
564 if (!isOpen() || pageURLOriginal.isEmpty())
567 String iconURL, pageURL;
570 MutexLocker locker(m_urlAndIconLock);
572 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
574 // If the urls already map to each other, bail.
575 // This happens surprisingly often, and seems to cream iBench performance
576 if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal)
579 pageURL = pageURLOriginal.copy();
580 iconURL = iconURLOriginal.copy();
583 pageRecord = new PageURLRecord(pageURL);
584 m_pageURLToRecordMap.set(pageURL, pageRecord);
587 RefPtr<IconRecord> iconRecord = pageRecord->iconRecord();
589 // Otherwise, set the new icon record for this page
590 IconRecord* newIconRecord = getOrCreateIconRecord(iconURL);
591 pageRecord->setIconRecord(newIconRecord);
593 // If the current icon has only a single ref left, it is about to get wiped out.
594 // Remove it from the in-memory records and don't bother reading it in from disk anymore
595 if (iconRecord && iconRecord->hasOneRef()) {
596 ASSERT(iconRecord->retainingPageURLs().size() == 0);
597 LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data());
598 m_iconURLToRecordMap.remove(iconRecord->iconURL());
599 MutexLocker locker(m_pendingReadingLock);
600 m_iconsPendingReading.remove(iconRecord.get());
603 // And mark this mapping to be added to the database
604 if (!m_privateBrowsingEnabled) {
605 MutexLocker locker(m_pendingSyncLock);
606 m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot());
608 // If the icon is on it's last ref, mark it for deletion
609 if (iconRecord && iconRecord->hasOneRef())
610 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
614 // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping
615 // comes from the initial import which we don't want notifications for
616 if (!IS_ICON_SYNC_THREAD()) {
617 // Start the timer to commit this change - or further delay the timer if it was already started
618 scheduleOrDeferSyncTimer();
620 LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data());
621 AutodrainedPool pool;
622 m_client->dispatchDidAddIconForPageURL(pageURL);
626 IconLoadDecision IconDatabase::loadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader)
628 ASSERT_NOT_SYNC_THREAD();
630 if (!isOpen() || iconURL.isEmpty())
633 // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord:
634 // 1 - When we read the icon urls from disk, getting the timeStamp at the same time
635 // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time
637 MutexLocker locker(m_urlAndIconLock);
638 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) {
639 LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord");
640 return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo;
644 // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now
645 MutexLocker readingLocker(m_pendingReadingLock);
646 if (m_iconURLImportComplete)
649 // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says
650 // "You might be asked to load this later, so flag that"
651 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);
652 m_loadersPendingDecision.add(notificationDocumentLoader);
654 return IconLoadUnknown;
657 bool IconDatabase::iconDataKnownForIconURL(const String& iconURL)
659 ASSERT_NOT_SYNC_THREAD();
661 MutexLocker locker(m_urlAndIconLock);
662 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
663 return icon->imageDataStatus() != ImageDataStatusUnknown;
668 void IconDatabase::setEnabled(bool enabled)
670 ASSERT_NOT_SYNC_THREAD();
672 if (!enabled && isOpen())
674 m_isEnabled = enabled;
677 bool IconDatabase::isEnabled() const
679 ASSERT_NOT_SYNC_THREAD();
684 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
686 m_privateBrowsingEnabled = flag;
689 bool IconDatabase::isPrivateBrowsingEnabled() const
691 return m_privateBrowsingEnabled;
694 void IconDatabase::delayDatabaseCleanup()
696 ++databaseCleanupCounter;
697 if (databaseCleanupCounter == 1)
698 LOG(IconDatabase, "Database cleanup is now DISABLED");
701 void IconDatabase::allowDatabaseCleanup()
703 if (--databaseCleanupCounter < 0)
704 databaseCleanupCounter = 0;
705 if (databaseCleanupCounter == 0)
706 LOG(IconDatabase, "Database cleanup is now ENABLED");
709 void IconDatabase::checkIntegrityBeforeOpening()
711 checkIntegrityOnOpen = true;
714 size_t IconDatabase::pageURLMappingCount()
716 MutexLocker locker(m_urlAndIconLock);
717 return m_pageURLToRecordMap.size();
720 size_t IconDatabase::retainedPageURLCount()
722 MutexLocker locker(m_urlAndIconLock);
723 return m_retainedPageURLs.size();
726 size_t IconDatabase::iconRecordCount()
728 MutexLocker locker(m_urlAndIconLock);
729 return m_iconURLToRecordMap.size();
732 size_t IconDatabase::iconRecordCountWithData()
734 MutexLocker locker(m_urlAndIconLock);
737 HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
738 HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
740 for (; i != end; ++i)
741 result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
746 IconDatabase::IconDatabase()
747 : m_syncThreadRunning(false)
748 , m_defaultIconRecord(0)
750 , m_privateBrowsingEnabled(false)
751 , m_threadTerminationRequested(false)
752 , m_removeIconsRequested(false)
753 , m_iconURLImportComplete(false)
754 , m_initialPruningComplete(false)
755 , m_client(defaultClient())
757 , m_isImportedSet(false)
760 ASSERT(pthread_main_np());
764 IconDatabase::~IconDatabase()
766 ASSERT_NOT_REACHED();
769 void IconDatabase::notifyPendingLoadDecisions()
771 iconDatabase()->notifyPendingLoadDecisionsInternal();
774 void IconDatabase::notifyPendingLoadDecisionsInternal()
776 ASSERT_NOT_SYNC_THREAD();
778 // This method should only be called upon completion of the initial url import from the database
779 ASSERT(m_iconURLImportComplete);
780 LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
782 HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
783 HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
785 for (; i != end; ++i)
786 if ((*i)->refCount() > 1)
787 (*i)->iconLoadDecisionAvailable();
789 m_loadersPendingDecision.clear();
792 void IconDatabase::wakeSyncThread()
794 MutexLocker locker(m_syncLock);
795 m_syncCondition.signal();
798 void IconDatabase::scheduleOrDeferSyncTimer()
800 ASSERT_NOT_SYNC_THREAD();
802 m_syncTimer.set(new Timer<IconDatabase>(this, &IconDatabase::syncTimerFired));
804 m_syncTimer->startOneShot(updateTimerDelay);
807 void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
809 ASSERT_NOT_SYNC_THREAD();
813 // ******************
814 // *** Any Thread ***
815 // ******************
817 bool IconDatabase::isOpen() const
819 MutexLocker locker(m_syncLock);
820 return m_syncDB.isOpen();
823 String IconDatabase::databasePath() const
825 MutexLocker locker(m_syncLock);
826 return m_completeDatabasePath.copy();
829 String IconDatabase::defaultDatabaseFilename()
831 static String defaultDatabaseFilename = "WebpageIcons.db";
832 return defaultDatabaseFilename.copy();
835 // Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import"
836 IconRecord* IconDatabase::getOrCreateIconRecord(const String& iconURL)
838 // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method
839 ASSERT(!m_urlAndIconLock.tryLock());
841 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
844 IconRecord* newIcon = new IconRecord(iconURL);
845 m_iconURLToRecordMap.set(iconURL, newIcon);
850 // This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification
851 PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL)
853 // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method
854 ASSERT(!m_urlAndIconLock.tryLock());
856 if (pageURL.isEmpty())
859 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
861 MutexLocker locker(m_pendingReadingLock);
862 if (!m_iconURLImportComplete) {
863 // 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
865 LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data());
866 pageRecord = new PageURLRecord(pageURL);
867 m_pageURLToRecordMap.set(pageURL, pageRecord);
870 // 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
871 // Mark the URL as "interested in the result of the import" then bail
872 if (!pageRecord->iconRecord()) {
873 m_pageURLsPendingImport.add(pageURL);
878 // We've done the initial import of all URLs known in the database. If this record doesn't exist now, it never will
883 // ************************
884 // *** Sync Thread Only ***
885 // ************************
887 void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL)
889 ASSERT_ICON_SYNC_THREAD();
891 // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty
892 ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty());
894 setIconURLForPageURLInSQLDatabase(iconURL, pageURL);
897 void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL)
899 ASSERT_ICON_SYNC_THREAD();
901 ASSERT(!iconURL.isEmpty());
903 writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get()));
906 bool IconDatabase::shouldStopThreadActivity() const
908 ASSERT_ICON_SYNC_THREAD();
910 return m_threadTerminationRequested || m_removeIconsRequested;
913 void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase)
915 IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase);
917 return iconDB->iconDatabaseSyncThread();
920 void* IconDatabase::iconDatabaseSyncThread()
922 // 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
923 // to our thread structure hasn't been filled in yet.
924 // 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
925 // prevent us from running before that call completes
929 ASSERT_ICON_SYNC_THREAD();
931 LOG(IconDatabase, "(THREAD) IconDatabase sync thread started");
934 double startTime = currentTime();
937 // Need to create the database path if it doesn't already exist
938 makeAllDirectories(m_databaseDirectory);
940 // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies
941 // us to do an integrity check
942 String journalFilename = m_completeDatabasePath + "-journal";
943 if (!checkIntegrityOnOpen) {
944 AutodrainedPool pool;
945 checkIntegrityOnOpen = fileExists(journalFilename);
949 MutexLocker locker(m_syncLock);
950 if (!m_syncDB.open(m_completeDatabasePath)) {
951 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
956 if (shouldStopThreadActivity())
957 return syncThreadMainLoop();
960 double timeStamp = currentTime();
961 LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime);
964 performOpenInitialization();
965 if (shouldStopThreadActivity())
966 return syncThreadMainLoop();
969 double newStamp = currentTime();
970 LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
971 timeStamp = newStamp;
975 LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure");
976 SQLiteTransaction importTransaction(m_syncDB);
977 importTransaction.begin();
979 // Commit the transaction only if the import completes (the import should be atomic)
980 if (m_client->performImport()) {
982 importTransaction.commit();
984 LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled");
985 importTransaction.rollback();
988 if (shouldStopThreadActivity())
989 return syncThreadMainLoop();
992 newStamp = currentTime();
993 LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
994 timeStamp = newStamp;
998 // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories)
999 // while (currentTime() - timeStamp < 10);
1001 // Read in URL mappings from the database
1002 LOG(IconDatabase, "(THREAD) Starting iconURL import");
1005 if (shouldStopThreadActivity())
1006 return syncThreadMainLoop();
1009 newStamp = currentTime();
1010 LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds. Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1013 LOG(IconDatabase, "(THREAD) Beginning sync");
1014 return syncThreadMainLoop();
1017 static int databaseVersionNumber(SQLiteDatabase& db)
1019 return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
1022 static bool isValidDatabase(SQLiteDatabase& db)
1025 // These four tables should always exist in a valid db
1026 if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
1029 if (databaseVersionNumber(db) < currentDatabaseVersion) {
1030 LOG(IconDatabase, "DB version is not found or below expected valid version");
1037 static void createDatabaseTables(SQLiteDatabase& db)
1039 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
1040 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1044 if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
1045 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1049 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);")) {
1050 LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1054 if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
1055 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1059 if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
1060 LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1064 if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
1065 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1069 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
1070 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1074 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
1075 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
1081 void IconDatabase::performOpenInitialization()
1083 ASSERT_ICON_SYNC_THREAD();
1088 if (checkIntegrityOnOpen) {
1089 checkIntegrityOnOpen = false;
1090 if (!checkIntegrity()) {
1091 LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
1096 MutexLocker locker(m_syncLock);
1097 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
1098 deleteFile(m_completeDatabasePath + "-journal");
1099 deleteFile(m_completeDatabasePath);
1102 // Reopen the write database, creating it from scratch
1103 if (!m_syncDB.open(m_completeDatabasePath)) {
1104 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
1110 int version = databaseVersionNumber(m_syncDB);
1112 if (version > currentDatabaseVersion) {
1113 LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
1115 m_threadTerminationRequested = true;
1119 if (!isValidDatabase(m_syncDB)) {
1120 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_syncDB.path().ascii().data());
1121 m_syncDB.clearAllTables();
1122 createDatabaseTables(m_syncDB);
1125 // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
1126 if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
1127 LOG_ERROR("SQLite database could not set cache_size");
1130 bool IconDatabase::checkIntegrity()
1132 ASSERT_ICON_SYNC_THREAD();
1134 SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
1135 if (integrity.prepare() != SQLResultOk) {
1136 LOG_ERROR("checkIntegrity failed to execute");
1140 int resultCode = integrity.step();
1141 if (resultCode == SQLResultOk)
1144 if (resultCode != SQLResultRow)
1147 int columns = integrity.columnCount();
1149 LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
1153 String resultText = integrity.getColumnText16(0);
1155 // A successful, no-error integrity check will be "ok" - all other strings imply failure
1156 if (resultText == "ok")
1159 LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
1163 void IconDatabase::performURLImport()
1165 ASSERT_ICON_SYNC_THREAD();
1167 SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
1169 if (query.prepare() != SQLResultOk) {
1170 LOG_ERROR("Unable to prepare icon url import query");
1174 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1175 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1176 AutodrainedPool pool(25);
1178 int result = query.step();
1179 while (result == SQLResultRow) {
1180 String pageURL = query.getColumnText16(0);
1181 String iconURL = query.getColumnText16(1);
1184 MutexLocker locker(m_urlAndIconLock);
1186 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
1188 // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
1189 // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
1190 // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
1191 // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
1192 // in - we'll prune it later instead!
1193 if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) {
1194 pageRecord = new PageURLRecord(pageURL);
1195 m_pageURLToRecordMap.set(pageURL, pageRecord);
1199 IconRecord* currentIcon = pageRecord->iconRecord();
1201 if (!currentIcon || currentIcon->iconURL() != iconURL) {
1202 currentIcon = getOrCreateIconRecord(iconURL);
1203 pageRecord->setIconRecord(currentIcon);
1206 // 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
1207 // so we marked the timestamp as "now", but it's really much older
1208 currentIcon->setTimestamp(query.getColumnInt(2));
1212 // 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
1213 // 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 -
1214 // one for the URL and one for the Image itself
1215 // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
1217 MutexLocker locker(m_pendingReadingLock);
1218 if (m_pageURLsPendingImport.contains(pageURL)) {
1219 m_client->dispatchDidAddIconForPageURL(pageURL);
1220 m_pageURLsPendingImport.remove(pageURL);
1226 // Stop the import at any time of the thread has been asked to shutdown
1227 if (shouldStopThreadActivity()) {
1228 LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
1232 result = query.step();
1235 if (result != SQLResultDone)
1236 LOG(IconDatabase, "Error reading page->icon url mappings from database");
1238 // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
1239 // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
1240 Vector<String> urls;
1242 MutexLocker locker(m_pendingReadingLock);
1244 urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
1245 m_pageURLsPendingImport.clear();
1246 m_iconURLImportComplete = true;
1249 Vector<String> urlsToNotify;
1251 // Loop through the urls pending import
1252 // Remove unretained ones if database cleanup is allowed
1253 // Keep a set of ones that are retained and pending notification
1256 MutexLocker locker(m_urlAndIconLock);
1258 for (unsigned i = 0; i < urls.size(); ++i) {
1259 if (!m_retainedPageURLs.contains(urls[i])) {
1260 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
1261 if (record && !databaseCleanupCounter) {
1262 m_pageURLToRecordMap.remove(urls[i]);
1263 IconRecord* iconRecord = record->iconRecord();
1265 // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
1266 // reading anything related to it
1267 if (iconRecord && iconRecord->hasOneRef()) {
1268 m_iconURLToRecordMap.remove(iconRecord->iconURL());
1271 MutexLocker locker(m_pendingReadingLock);
1272 m_pageURLsInterestedInIcons.remove(urls[i]);
1273 m_iconsPendingReading.remove(iconRecord);
1276 MutexLocker locker(m_pendingSyncLock);
1277 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
1284 urlsToNotify.append(urls[i]);
1289 LOG(IconDatabase, "Notifying %i interested page URLs that their icon URL is known due to the import", urlsToNotify.size());
1290 // Now that we don't hold any locks, perform the actual notifications
1291 for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
1292 LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
1293 m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]);
1294 if (shouldStopThreadActivity())
1300 // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
1301 callOnMainThread(notifyPendingLoadDecisions);
1304 void* IconDatabase::syncThreadMainLoop()
1306 ASSERT_ICON_SYNC_THREAD();
1307 static bool prunedUnretainedIcons = false;
1311 // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
1312 while (!m_threadTerminationRequested) {
1313 m_syncLock.unlock();
1316 double timeStamp = currentTime();
1318 LOG(IconDatabase, "(THREAD) Main work loop starting");
1320 // 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
1321 if (m_removeIconsRequested) {
1322 removeAllIconsOnThread();
1323 m_removeIconsRequested = false;
1326 // Then, if the thread should be quitting, quit now!
1327 if (m_threadTerminationRequested)
1330 bool didAnyWork = true;
1331 while (didAnyWork) {
1332 didAnyWork = readFromDatabase();
1333 if (shouldStopThreadActivity())
1336 bool didWrite = writeToDatabase();
1337 if (shouldStopThreadActivity())
1340 // Prune unretained icons after the first time we sync anything out to the database
1341 // This way, pruning won't be the only operation we perform to the database by itself
1342 // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
1343 // or if private browsing is enabled
1344 // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
1345 // has asked to delay pruning
1346 if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
1348 double time = currentTime();
1350 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
1352 pruneUnretainedIcons();
1354 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
1356 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
1357 // to mark prunedUnretainedIcons true because we're about to terminate anyway
1358 prunedUnretainedIcons = true;
1361 didAnyWork = didAnyWork || didWrite;
1362 if (shouldStopThreadActivity())
1367 double newstamp = currentTime();
1368 LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
1373 // There is some condition that is asking us to stop what we're doing now and handle a special case
1374 // This is either removing all icons, or shutting down the thread to quit the app
1375 // We handle those at the top of this main loop so continue to jump back up there
1376 if (shouldStopThreadActivity())
1379 m_syncCondition.wait(m_syncLock);
1381 m_syncLock.unlock();
1383 // Thread is terminating at this point
1384 return cleanupSyncThread();
1387 bool IconDatabase::readFromDatabase()
1389 ASSERT_ICON_SYNC_THREAD();
1392 double timeStamp = currentTime();
1395 bool didAnyWork = false;
1397 // 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
1398 // This way we won't hold the lock for a long period of time
1399 Vector<IconRecord*> icons;
1401 MutexLocker locker(m_pendingReadingLock);
1402 icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
1405 // Keep track of icons we actually read to notify them of the new icon
1406 HashSet<String> urlsToNotify;
1408 for (unsigned i = 0; i < icons.size(); ++i) {
1410 RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
1412 // Verify this icon still wants to be read from disk
1414 MutexLocker urlLocker(m_urlAndIconLock);
1416 MutexLocker readLocker(m_pendingReadingLock);
1418 if (m_iconsPendingReading.contains(icons[i])) {
1420 icons[i]->setImageData(imageData.get());
1422 // Remove this icon from the set that needs to be read
1423 m_iconsPendingReading.remove(icons[i]);
1425 // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
1426 // We want to find the intersection of these two sets to notify them
1427 // Check the sizes of these two sets to minimize the number of iterations
1428 const HashSet<String>* outerHash;
1429 const HashSet<String>* innerHash;
1431 if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
1432 outerHash = &m_pageURLsInterestedInIcons;
1433 innerHash = &(icons[i]->retainingPageURLs());
1435 innerHash = &m_pageURLsInterestedInIcons;
1436 outerHash = &(icons[i]->retainingPageURLs());
1439 HashSet<String>::const_iterator iter = outerHash->begin();
1440 HashSet<String>::const_iterator end = outerHash->end();
1441 for (; iter != end; ++iter) {
1442 if (innerHash->contains(*iter)) {
1443 LOG(IconDatabase, "%s is interesting in the icon we just read. Adding it to the list and removing it from the interested set", urlForLogging(*iter).ascii().data());
1444 urlsToNotify.add(*iter);
1447 // If we ever get to the point were we've seen every url interested in this icon, break early
1448 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1452 // 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
1453 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1454 m_pageURLsInterestedInIcons.clear();
1456 iter = urlsToNotify.begin();
1457 end = urlsToNotify.end();
1458 for (; iter != end; ++iter)
1459 m_pageURLsInterestedInIcons.remove(*iter);
1465 if (shouldStopThreadActivity())
1468 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1469 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1470 AutodrainedPool pool(25);
1472 // Now that we don't hold any locks, perform the actual notifications
1473 HashSet<String>::iterator iter = urlsToNotify.begin();
1474 HashSet<String>::iterator end = urlsToNotify.end();
1475 for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
1476 LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
1477 m_client->dispatchDidAddIconForPageURL(*iter);
1478 if (shouldStopThreadActivity())
1484 LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
1485 urlsToNotify.clear();
1487 if (shouldStopThreadActivity())
1491 LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
1496 bool IconDatabase::writeToDatabase()
1498 ASSERT_ICON_SYNC_THREAD();
1501 double timeStamp = currentTime();
1504 bool didAnyWork = false;
1506 // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
1507 // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes
1508 // asked for by the database on the main thread
1509 Vector<IconSnapshot> iconSnapshots;
1510 Vector<PageURLSnapshot> pageSnapshots;
1512 MutexLocker locker(m_pendingSyncLock);
1514 iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
1515 m_iconsPendingSync.clear();
1517 pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
1518 m_pageURLsPendingSync.clear();
1521 if (iconSnapshots.size() || pageSnapshots.size())
1524 SQLiteTransaction syncTransaction(m_syncDB);
1525 syncTransaction.begin();
1527 for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
1528 writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
1529 LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %li to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp);
1532 for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
1533 String iconURL = pageSnapshots[i].iconURL;
1535 // If the icon URL is empty, this page is meant to be deleted
1536 // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
1537 if (pageSnapshots[i].iconURL.isEmpty())
1538 removePageURLFromSQLDatabase(pageSnapshots[i].pageURL);
1540 setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL);
1542 LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data());
1545 syncTransaction.commit();
1547 // 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
1548 checkForDanglingPageURLs(false);
1550 LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
1555 void IconDatabase::pruneUnretainedIcons()
1557 ASSERT_ICON_SYNC_THREAD();
1562 // This method should only be called once per run
1563 ASSERT(!m_initialPruningComplete);
1565 // This method relies on having read in all page URLs from the database earlier.
1566 ASSERT(m_iconURLImportComplete);
1568 // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
1569 Vector<int64_t> pageIDsToDelete;
1571 SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
1575 while ((result = pageSQL.step()) == SQLResultRow) {
1576 MutexLocker locker(m_urlAndIconLock);
1577 if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText16(1)))
1578 pageIDsToDelete.append(pageSQL.getColumnInt64(0));
1581 if (result != SQLResultDone)
1582 LOG_ERROR("Error reading PageURL table from on-disk DB");
1585 // Delete page URLs that were in the table, but not in our retain count set.
1586 size_t numToDelete = pageIDsToDelete.size();
1588 SQLiteTransaction pruningTransaction(m_syncDB);
1589 pruningTransaction.begin();
1591 SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
1592 pageDeleteSQL.prepare();
1593 for (size_t i = 0; i < numToDelete; ++i) {
1594 LOG(IconDatabase, "Pruning page with rowid %lli from disk", pageIDsToDelete[i]);
1595 pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
1596 int result = pageDeleteSQL.step();
1597 if (result != SQLResultDone)
1598 LOG_ERROR("Unabled to delete page with id %lli from disk", pageIDsToDelete[i]);
1599 pageDeleteSQL.reset();
1601 // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
1602 // finish the rest later (hopefully)
1603 if (shouldStopThreadActivity()) {
1604 pruningTransaction.commit();
1608 pruningTransaction.commit();
1609 pageDeleteSQL.finalize();
1612 // Deleting unreferenced icons from the Icon tables has to be atomic -
1613 // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue
1614 // A user on a network home directory with a wildly inconsistent database might see quite a pause...
1616 SQLiteTransaction pruningTransaction(m_syncDB);
1617 pruningTransaction.begin();
1619 // Wipe Icons that aren't retained
1620 if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1621 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
1622 if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1623 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
1625 pruningTransaction.commit();
1627 checkForDanglingPageURLs(true);
1629 m_initialPruningComplete = true;
1632 void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
1634 ASSERT_ICON_SYNC_THREAD();
1636 // We don't want to keep performing this check and reporting this error if it has already found danglers so we keep track
1637 static bool danglersFound = false;
1639 // However, if the caller wants us to prune the danglers, we will reset this flag and prune every time
1641 danglersFound = false;
1643 if (!danglersFound && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
1644 danglersFound = true;
1645 LOG_ERROR("Dangling PageURL entries found");
1646 if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
1647 LOG_ERROR("Unable to prune dangling PageURLs");
1651 void IconDatabase::removeAllIconsOnThread()
1653 ASSERT_ICON_SYNC_THREAD();
1655 LOG(IconDatabase, "Removing all icons on the sync thread");
1657 // Delete all the prepared statements so they can start over
1658 deleteAllPreparedStatements();
1660 // To reset the on-disk database, we'll wipe all its tables then vacuum it
1661 // This is easier and safer than closing it, deleting the file, and recreating from scratch
1662 m_syncDB.clearAllTables();
1663 m_syncDB.runVacuumCommand();
1664 createDatabaseTables(m_syncDB);
1666 LOG(IconDatabase, "Dispatching notification that we removed all icons");
1667 m_client->dispatchDidRemoveAllIcons();
1670 void IconDatabase::deleteAllPreparedStatements()
1672 ASSERT_ICON_SYNC_THREAD();
1674 m_setIconIDForPageURLStatement.set(0);
1675 m_removePageURLStatement.set(0);
1676 m_getIconIDForIconURLStatement.set(0);
1677 m_getImageDataForIconURLStatement.set(0);
1678 m_addIconToIconInfoStatement.set(0);
1679 m_addIconToIconDataStatement.set(0);
1680 m_getImageDataStatement.set(0);
1681 m_deletePageURLsForIconURLStatement.set(0);
1682 m_deleteIconFromIconInfoStatement.set(0);
1683 m_deleteIconFromIconDataStatement.set(0);
1684 m_updateIconInfoStatement.set(0);
1685 m_updateIconDataStatement.set(0);
1686 m_setIconInfoStatement.set(0);
1687 m_setIconDataStatement.set(0);
1690 void* IconDatabase::cleanupSyncThread()
1692 ASSERT_ICON_SYNC_THREAD();
1695 double timeStamp = currentTime();
1698 // If the removeIcons flag is set, remove all icons from the db.
1699 if (m_removeIconsRequested)
1700 removeAllIconsOnThread();
1702 // Sync remaining icons out
1703 LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
1706 // Close the database
1707 MutexLocker locker(m_syncLock);
1709 m_databaseDirectory = String();
1710 m_completeDatabasePath = String();
1711 deleteAllPreparedStatements();
1715 LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
1718 m_syncThreadRunning = false;
1722 bool IconDatabase::imported()
1724 ASSERT_ICON_SYNC_THREAD();
1726 if (m_isImportedSet)
1729 SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
1730 if (query.prepare() != SQLResultOk) {
1731 LOG_ERROR("Unable to prepare imported statement");
1735 int result = query.step();
1736 if (result == SQLResultRow)
1737 result = query.getColumnInt(0);
1739 if (result != SQLResultDone)
1740 LOG_ERROR("imported statement failed");
1744 m_isImportedSet = true;
1745 return m_imported = result;
1748 void IconDatabase::setImported(bool import)
1750 ASSERT_ICON_SYNC_THREAD();
1752 m_imported = import;
1753 m_isImportedSet = true;
1755 String queryString = import ?
1756 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
1757 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
1759 SQLiteStatement query(m_syncDB, queryString);
1761 if (query.prepare() != SQLResultOk) {
1762 LOG_ERROR("Unable to prepare set imported statement");
1766 if (query.step() != SQLResultDone)
1767 LOG_ERROR("set imported statement failed");
1770 // readySQLiteStatement() handles two things
1771 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user
1772 // switches to and from private browsing
1773 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
1774 inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
1776 if (statement && (statement->database() != &db || statement->isExpired())) {
1777 if (statement->isExpired())
1778 LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
1782 statement.set(new SQLiteStatement(db, str));
1783 if (statement->prepare() != SQLResultOk)
1784 LOG_ERROR("Preparing statement %s failed", str.ascii().data());
1788 void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
1790 ASSERT_ICON_SYNC_THREAD();
1792 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1795 iconID = addIconURLToSQLDatabase(iconURL);
1798 LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
1803 setIconIDForPageURLInSQLDatabase(iconID, pageURL);
1806 void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
1808 ASSERT_ICON_SYNC_THREAD();
1810 readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
1811 m_setIconIDForPageURLStatement->bindText16(1, pageURL, false);
1812 m_setIconIDForPageURLStatement->bindInt64(2, iconID);
1814 int result = m_setIconIDForPageURLStatement->step();
1815 if (result != SQLResultDone) {
1817 LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
1820 m_setIconIDForPageURLStatement->reset();
1823 void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
1825 ASSERT_ICON_SYNC_THREAD();
1827 readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
1828 m_removePageURLStatement->bindText16(1, pageURL, false);
1830 if (m_removePageURLStatement->step() != SQLResultDone)
1831 LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
1833 m_removePageURLStatement->reset();
1837 int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
1839 ASSERT_ICON_SYNC_THREAD();
1841 readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
1842 m_getIconIDForIconURLStatement->bindText16(1, iconURL, false);
1844 int64_t result = m_getIconIDForIconURLStatement->step();
1845 if (result == SQLResultRow)
1846 result = m_getIconIDForIconURLStatement->getColumnInt64(0);
1848 if (result != SQLResultDone)
1849 LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1853 m_getIconIDForIconURLStatement->reset();
1857 int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
1859 ASSERT_ICON_SYNC_THREAD();
1861 // There would be a transaction here to make sure these two inserts are atomic
1862 // In practice the only caller of this method is always wrapped in a transaction itself so placing another
1863 // here is unnecessary
1865 readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
1866 m_addIconToIconInfoStatement->bindText16(1, iconURL);
1868 int result = m_addIconToIconInfoStatement->step();
1869 m_addIconToIconInfoStatement->reset();
1870 if (result != SQLResultDone) {
1871 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
1874 int64_t iconID = m_syncDB.lastInsertRowID();
1876 readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
1877 m_addIconToIconDataStatement->bindInt64(1, iconID);
1879 result = m_addIconToIconDataStatement->step();
1880 m_addIconToIconDataStatement->reset();
1881 if (result != SQLResultDone) {
1882 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
1889 PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
1891 ASSERT_ICON_SYNC_THREAD();
1893 RefPtr<SharedBuffer> imageData;
1895 readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
1896 m_getImageDataForIconURLStatement->bindText16(1, iconURL, false);
1898 int result = m_getImageDataForIconURLStatement->step();
1899 if (result == SQLResultRow) {
1901 m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
1902 imageData = new SharedBuffer;
1903 imageData->append(data.data(), data.size());
1904 } else if (result != SQLResultDone)
1905 LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1907 m_getImageDataForIconURLStatement->reset();
1909 return imageData.release();
1912 void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
1914 ASSERT_ICON_SYNC_THREAD();
1916 if (iconURL.isEmpty())
1919 // There would be a transaction here to make sure these removals are atomic
1920 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1922 // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
1923 // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return
1924 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1928 readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
1929 m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
1931 if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
1932 LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1934 readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
1935 m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
1937 if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
1938 LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1940 readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
1941 m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
1943 if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
1944 LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1946 m_deletePageURLsForIconURLStatement->reset();
1947 m_deleteIconFromIconInfoStatement->reset();
1948 m_deleteIconFromIconDataStatement->reset();
1951 void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
1953 ASSERT_ICON_SYNC_THREAD();
1955 if (snapshot.iconURL.isEmpty())
1958 // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
1959 if (!snapshot.timestamp && !snapshot.data) {
1960 LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data());
1961 removeIconFromSQLDatabase(snapshot.iconURL);
1965 // There would be a transaction here to make sure these removals are atomic
1966 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1968 // Get the iconID for this url
1969 int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL);
1971 // If there is already an iconID in place, update the database.
1972 // Otherwise, insert new records
1974 readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
1975 m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp);
1976 m_updateIconInfoStatement->bindText16(2, snapshot.iconURL);
1977 m_updateIconInfoStatement->bindInt64(3, iconID);
1979 if (m_updateIconInfoStatement->step() != SQLResultDone)
1980 LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
1982 m_updateIconInfoStatement->reset();
1984 readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
1985 m_updateIconDataStatement->bindInt64(2, iconID);
1987 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
1988 // signifying that this icon doesn't have any data
1989 if (snapshot.data && snapshot.data->size())
1990 m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size());
1992 m_updateIconDataStatement->bindNull(1);
1994 if (m_updateIconDataStatement->step() != SQLResultDone)
1995 LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
1997 m_updateIconDataStatement->reset();
1999 readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
2000 m_setIconInfoStatement->bindText16(1, snapshot.iconURL);
2001 m_setIconInfoStatement->bindInt64(2, snapshot.timestamp);
2003 if (m_setIconInfoStatement->step() != SQLResultDone)
2004 LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2006 m_setIconInfoStatement->reset();
2008 int64_t iconID = m_syncDB.lastInsertRowID();
2010 readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
2011 m_setIconDataStatement->bindInt64(1, iconID);
2013 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2014 // signifying that this icon doesn't have any data
2015 if (snapshot.data && snapshot.data->size())
2016 m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size());
2018 m_setIconDataStatement->bindNull(2);
2020 if (m_setIconDataStatement->step() != SQLResultDone)
2021 LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2023 m_setIconDataStatement->reset();
2027 } // namespace WebCore