2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "IconDatabase.h"
29 #include "AutodrainedPool.h"
31 #include "DocumentLoader.h"
32 #include "FileSystem.h"
33 #include "IconDatabaseClient.h"
34 #include "IconRecord.h"
39 #include "PageURLRecord.h"
40 #include "SQLiteStatement.h"
41 #include "SQLiteTransaction.h"
42 #include "SystemTime.h"
54 // For methods that are meant to support API from the main thread - should not be called internally
55 #define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD())
57 // For methods that are meant to support the sync thread ONLY
58 #define IS_ICON_SYNC_THREAD() pthread_equal(pthread_self(), m_syncThread)
59 #define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD())
63 static IconDatabase* sharedIconDatabase = 0;
64 static int databaseCleanupCounter = 0;
66 // This version number is in the DB and marks the current generation of the schema
67 // Currently, a mismatched schema causes the DB to be wiped and reset. This isn't
68 // so bad during development but in the future, we would need to write a conversion
69 // function to advance older released schemas to "current"
70 const int currentDatabaseVersion = 6;
72 // Icons expire once every 4 days
73 const int iconExpirationTime = 60*60*24*4;
75 const int updateTimerDelay = 5;
77 static bool checkIntegrityOnOpen = false;
80 static String urlForLogging(const String& url)
82 static unsigned urlTruncationLength = 120;
84 if (url.length() < urlTruncationLength)
86 return url.substring(0, urlTruncationLength) + "...";
90 static IconDatabaseClient* defaultClient()
92 static IconDatabaseClient* defaultClient = new IconDatabaseClient();
96 IconDatabase* iconDatabase()
98 if (!sharedIconDatabase) {
99 initializeThreading();
100 sharedIconDatabase = new IconDatabase;
102 return sharedIconDatabase;
105 // ************************
106 // *** Main Thread Only ***
107 // ************************
109 void IconDatabase::setClient(IconDatabaseClient* client)
111 // We don't allow a null client, because we never null check it anywhere in this code
112 // Also don't allow a client change after the thread has already began
113 // (setting the client should occur before the database is opened)
115 ASSERT(!m_syncThreadRunning);
116 if (!client || m_syncThreadRunning)
122 bool IconDatabase::open(const String& databasePath)
124 ASSERT_NOT_SYNC_THREAD();
130 LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first.");
134 m_databaseDirectory = databasePath.copy();
136 // Formulate the full path for the database file
137 m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename());
139 // Lock here as well as first thing in the thread so the tread doesn't actually commence until the pthread_create() call
140 // completes and m_syncThreadRunning is properly set
142 m_syncThreadRunning = !pthread_create(&m_syncThread, NULL, IconDatabase::iconDatabaseSyncThreadStart, this);
145 return m_syncThreadRunning;
148 void IconDatabase::close()
150 ASSERT_NOT_SYNC_THREAD();
152 if (m_syncThreadRunning) {
153 // Set the flag to tell the sync thread to wrap it up
154 m_threadTerminationRequested = true;
156 // Wake up the sync thread if it's waiting
159 // Wait for the sync thread to terminate
160 if (pthread_join(m_syncThread, NULL) == EDEADLK)
161 LOG_ERROR("m_syncThread was found to be deadlocked trying to quit");
164 m_syncThreadRunning = false;
165 m_threadTerminationRequested = false;
166 m_removeIconsRequested = false;
169 void IconDatabase::removeAllIcons()
171 ASSERT_NOT_SYNC_THREAD();
176 LOG(IconDatabase, "Requesting background thread to remove all icons");
178 // Clear the in-memory record of every IconRecord, anything waiting to be read from disk, and anything waiting to be written to disk
180 MutexLocker locker(m_urlAndIconLock);
182 // Clear the IconRecords for every page URL - RefCounting will cause the IconRecords themselves to be deleted
183 // We don't delete the actual PageRecords because we have the "retain icon for url" count to keep track of
184 HashMap<String, PageURLRecord*>::iterator iter = m_pageURLToRecordMap.begin();
185 HashMap<String, PageURLRecord*>::iterator end = m_pageURLToRecordMap.end();
186 for (; iter != end; ++iter)
187 (*iter).second->setIconRecord(0);
189 // Clear the iconURL -> IconRecord map
190 m_iconURLToRecordMap.clear();
192 // Clear all in-memory records of things that need to be synced out to disk
194 MutexLocker locker(m_pendingSyncLock);
195 m_pageURLsPendingSync.clear();
196 m_iconsPendingSync.clear();
199 // Clear all in-memory records of things that need to be read in from disk
201 MutexLocker locker(m_pendingReadingLock);
202 m_pageURLsPendingImport.clear();
203 m_pageURLsInterestedInIcons.clear();
204 m_iconsPendingReading.clear();
205 m_loadersPendingDecision.clear();
209 m_removeIconsRequested = true;
213 Image* IconDatabase::iconForPageURL(const String& pageURLOriginal, const IntSize& size, bool cache)
215 ASSERT_NOT_SYNC_THREAD();
217 ASSERT(!pageURLOriginal.isNull());
219 // pageURLOriginal can not be stored without being deep copied first.
220 // We should go our of our way to only copy it if we have to store it
223 return defaultIcon(size);
225 MutexLocker locker(m_urlAndIconLock);
227 String pageURLCopy; // Creates a null string for easy testing
229 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
231 pageURLCopy = pageURLOriginal.copy();
232 pageRecord = getOrCreatePageURLRecord(pageURLCopy);
235 // If pageRecord is NULL, one of two things is true -
236 // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists
237 // 2 - The initial url import IS complete and this pageURL has no icon
239 MutexLocker locker(m_pendingReadingLock);
241 // Import is ongoing, there might be an icon. In this case, register to be notified when the icon comes in
242 // If we ever reach this condition, we know we've already made the pageURL copy
243 if (!m_iconURLImportComplete)
244 m_pageURLsInterestedInIcons.add(pageURLCopy);
249 IconRecord* iconRecord = pageRecord->iconRecord();
251 // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon
252 // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so
253 // we can just bail now
254 if (!m_iconURLImportComplete && !iconRecord)
257 // 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
258 ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal));
263 // If it's a new IconRecord object that doesn't have its imageData set yet,
264 // mark it to be read by the background thread
265 if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) {
266 if (pageURLCopy.isNull())
267 pageURLCopy = pageURLOriginal.copy();
269 MutexLocker locker(m_pendingReadingLock);
270 m_pageURLsInterestedInIcons.add(pageURLCopy);
271 m_iconsPendingReading.add(iconRecord);
276 // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off
277 // and isn't actually interested in the image return value
278 if (size == IntSize(0, 0))
281 // PARANOID DISCUSSION: This method makes some assumptions. It returns a WebCore::image which the icon database might dispose of at anytime in the future,
282 // and Images aren't ref counted. So there is no way for the client to guarantee continued existence of the image.
283 // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image
284 // and drop the raw Image*. On Mac an NSImage, and on windows drawing into an HBITMAP.
285 // 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
286 // representation out of it?
287 // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache.
288 // This is because we make the assumption that anything in memory is newer than whatever is in the database.
289 // 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
290 // delete the image on the secondary thread if the image already exists.
291 return iconRecord->image(size);
294 void IconDatabase::readIconForPageURLFromDisk(const String& pageURL)
296 // The effect of asking for an Icon for a pageURL automatically queues it to be read from disk
297 // if it hasn't already been set in memory. The special IntSize (0, 0) is a special way of telling
298 // that method "I don't care about the actual Image, i just want you to make sure you're getting it from disk.
299 iconForPageURL(pageURL, IntSize(0,0));
302 String IconDatabase::iconURLForPageURL(const String& pageURLOriginal)
304 ASSERT_NOT_SYNC_THREAD();
306 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
307 // Also, in the case we have a real answer for the caller, we must deep copy that as well
309 if (!isOpen() || pageURLOriginal.isEmpty())
312 MutexLocker locker(m_urlAndIconLock);
314 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
316 pageRecord = getOrCreatePageURLRecord(pageURLOriginal.copy());
318 // If pageRecord is NULL, one of two things is true -
319 // 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
320 // 2 - The initial url import IS complete and this pageURL has no icon
324 // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check
325 return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().copy() : String();
328 Image* IconDatabase::defaultIcon(const IntSize& size)
330 ASSERT_NOT_SYNC_THREAD();
332 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,
333 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,
334 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,
335 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,
336 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,
337 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,
338 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,
339 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,
340 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,
341 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,
342 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,
343 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,
344 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,
345 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,
346 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,
347 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,
348 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,
349 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,
350 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,
351 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,
352 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,
353 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,
354 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,
355 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,
356 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,
357 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,
358 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,
359 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,
360 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,
361 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,
362 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,
363 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,
364 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,
365 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,
366 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,
367 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,
368 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10, 0x00, 0x0A, 0xFC, 0x80, 0x00, 0x00, 0x27, 0x10 };
370 static RefPtr<SharedBuffer> defaultIconBuffer(new SharedBuffer(defaultIconData, sizeof(defaultIconData)));
372 if (!m_defaultIconRecord) {
373 m_defaultIconRecord = new IconRecord("urlIcon");
374 m_defaultIconRecord->setImageData(defaultIconBuffer);
377 return m_defaultIconRecord->image(size);
381 void IconDatabase::retainIconForPageURL(const String& pageURLOriginal)
383 ASSERT_NOT_SYNC_THREAD();
385 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
387 if (!isEnabled() || pageURLOriginal.isEmpty())
390 MutexLocker locker(m_urlAndIconLock);
392 PageURLRecord* record = m_pageURLToRecordMap.get(pageURLOriginal);
397 pageURL = pageURLOriginal.copy();
399 record = new PageURLRecord(pageURL);
400 m_pageURLToRecordMap.set(pageURL, record);
403 if (!record->retain()) {
404 if (pageURL.isNull())
405 pageURL = pageURLOriginal.copy();
407 // This page just had its retain count bumped from 0 to 1 - Record that fact
408 m_retainedPageURLs.add(pageURL);
410 // If we read the iconURLs yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
411 // so we bail here and skip those steps
412 if (!m_iconURLImportComplete)
415 MutexLocker locker(m_pendingSyncLock);
416 // If this pageURL waiting to be sync'ed, update the sync record
417 // 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!
418 if (!m_privateBrowsingEnabled && m_pageURLsPendingSync.contains(pageURL)) {
419 LOG(IconDatabase, "Bringing %s back from the brink", pageURL.ascii().data());
420 m_pageURLsPendingSync.set(pageURL, record->snapshot());
425 void IconDatabase::releaseIconForPageURL(const String& pageURLOriginal)
427 ASSERT_NOT_SYNC_THREAD();
429 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first
431 if (!isEnabled() || pageURLOriginal.isEmpty())
434 MutexLocker locker(m_urlAndIconLock);
436 // Check if this pageURL is actually retained
437 if (!m_retainedPageURLs.contains(pageURLOriginal)) {
438 LOG_ERROR("Attempting to release icon for URL %s which is not retained", urlForLogging(pageURLOriginal).ascii().data());
442 // Get its retain count - if it's retained, we'd better have a PageURLRecord for it
443 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
445 LOG(IconDatabase, "Releasing pageURL %s to a retain count of %i", urlForLogging(pageURLOriginal).ascii().data(), pageRecord->retainCount() - 1);
446 ASSERT(pageRecord->retainCount() > 0);
448 // If it still has a positive retain count, store the new count and bail
449 if (pageRecord->release())
452 // This pageRecord has now been fully released. Do the appropriate cleanup
453 LOG(IconDatabase, "No more retainers for PageURL %s", urlForLogging(pageURLOriginal).ascii().data());
454 m_pageURLToRecordMap.remove(pageURLOriginal);
455 m_retainedPageURLs.remove(pageURLOriginal);
457 // Grab the iconRecord for later use (and do a sanity check on it for kicks)
458 IconRecord* iconRecord = pageRecord->iconRecord();
460 ASSERT(!iconRecord || (iconRecord && m_iconURLToRecordMap.get(iconRecord->iconURL()) == iconRecord));
463 MutexLocker locker(m_pendingReadingLock);
465 // Since this pageURL is going away, there's no reason anyone would ever be interested in its read results
466 if (!m_iconURLImportComplete)
467 m_pageURLsPendingImport.remove(pageURLOriginal);
468 m_pageURLsInterestedInIcons.remove(pageURLOriginal);
470 // If this icon is down to it's last retainer, we don't care about reading it in from disk anymore
471 if (iconRecord && iconRecord->hasOneRef()) {
472 m_iconURLToRecordMap.remove(iconRecord->iconURL());
473 m_iconsPendingReading.remove(iconRecord);
477 // Mark stuff for deletion from the database only if we're not in private browsing
478 if (!m_privateBrowsingEnabled) {
479 MutexLocker locker(m_pendingSyncLock);
480 m_pageURLsPendingSync.set(pageURLOriginal.copy(), pageRecord->snapshot(true));
482 // If this page is the last page to refer to a particular IconRecord, that IconRecord needs to
483 // be marked for deletion
484 if (iconRecord && iconRecord->hasOneRef())
485 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
491 scheduleOrDeferSyncTimer();
494 void IconDatabase::setIconDataForIconURL(PassRefPtr<SharedBuffer> dataOriginal, const String& iconURLOriginal)
496 ASSERT_NOT_SYNC_THREAD();
498 // Cannot do anything with dataOriginal or iconURLOriginal that would end up storing them without deep copying first
500 if (!isOpen() || iconURLOriginal.isEmpty())
503 RefPtr<SharedBuffer> data = dataOriginal ? dataOriginal->copy() : 0;
504 String iconURL = iconURLOriginal.copy();
506 Vector<String> pageURLs;
508 MutexLocker locker(m_urlAndIconLock);
510 // If this icon was pending a read, remove it from that set because this new data should override what is on disk
511 IconRecord* icon = m_iconURLToRecordMap.get(iconURL);
513 MutexLocker locker(m_pendingReadingLock);
514 m_iconsPendingReading.remove(icon);
516 icon = getOrCreateIconRecord(iconURL);
518 // Update the data and set the time stamp
519 icon->setImageData(data);
520 icon->setTimestamp((int)currentTime());
522 // Copy the current retaining pageURLs - if any - to notify them of the change
523 pageURLs.appendRange(icon->retainingPageURLs().begin(), icon->retainingPageURLs().end());
525 // Mark the IconRecord as requiring an update to the database only if private browsing is disabled
526 if (!m_privateBrowsingEnabled) {
527 MutexLocker locker(m_pendingSyncLock);
528 m_iconsPendingSync.set(iconURL, icon->snapshot());
532 // Send notification out regarding all PageURLs that retain this icon
533 // But not if we're on the sync thread because that implies this mapping
534 // comes from the initial import which we don't want notifications for
535 if (!IS_ICON_SYNC_THREAD()) {
536 // Start the timer to commit this change - or further delay the timer if it was already started
537 scheduleOrDeferSyncTimer();
539 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
540 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
541 AutodrainedPool pool(25);
543 for (unsigned i = 0; i < pageURLs.size(); ++i) {
544 LOG(IconDatabase, "Dispatching notification that retaining pageURL %s has a new icon", urlForLogging(pageURLs[i]).ascii().data());
545 m_client->dispatchDidAddIconForPageURL(pageURLs[i]);
552 void IconDatabase::setIconURLForPageURL(const String& iconURLOriginal, const String& pageURLOriginal)
554 ASSERT_NOT_SYNC_THREAD();
556 // Cannot do anything with iconURLOriginal or pageURLOriginal that would end up storing them without deep copying first
558 ASSERT(!iconURLOriginal.isEmpty());
560 if (!isOpen() || pageURLOriginal.isEmpty())
563 String iconURL, pageURL;
566 MutexLocker locker(m_urlAndIconLock);
568 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal);
570 // If the urls already map to each other, bail.
571 // This happens surprisingly often, and seems to cream iBench performance
572 if (pageRecord && pageRecord->iconRecord() && pageRecord->iconRecord()->iconURL() == iconURLOriginal)
575 pageURL = pageURLOriginal.copy();
576 iconURL = iconURLOriginal.copy();
579 pageRecord = new PageURLRecord(pageURL);
580 m_pageURLToRecordMap.set(pageURL, pageRecord);
583 RefPtr<IconRecord> iconRecord = pageRecord->iconRecord();
585 // Otherwise, set the new icon record for this page
586 IconRecord* newIconRecord = getOrCreateIconRecord(iconURL);
587 pageRecord->setIconRecord(newIconRecord);
589 // If the current icon has only a single ref left, it is about to get wiped out.
590 // Remove it from the in-memory records and don't bother reading it in from disk anymore
591 if (iconRecord && iconRecord->hasOneRef()) {
592 ASSERT(iconRecord->retainingPageURLs().size() == 0);
593 LOG(IconDatabase, "Icon for icon url %s is about to be destroyed - removing mapping for it", urlForLogging(iconRecord->iconURL()).ascii().data());
594 m_iconURLToRecordMap.remove(iconRecord->iconURL());
595 MutexLocker locker(m_pendingReadingLock);
596 m_iconsPendingReading.remove(iconRecord.get());
599 // And mark this mapping to be added to the database
600 if (!m_privateBrowsingEnabled) {
601 MutexLocker locker(m_pendingSyncLock);
602 m_pageURLsPendingSync.set(pageURL, pageRecord->snapshot());
604 // If the icon is on it's last ref, mark it for deletion
605 if (iconRecord && iconRecord->hasOneRef())
606 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
610 // Since this mapping is new, send the notification out - but not if we're on the sync thread because that implies this mapping
611 // comes from the initial import which we don't want notifications for
612 if (!IS_ICON_SYNC_THREAD()) {
613 // Start the timer to commit this change - or further delay the timer if it was already started
614 scheduleOrDeferSyncTimer();
616 LOG(IconDatabase, "Dispatching notification that we changed an icon mapping for url %s", urlForLogging(pageURL).ascii().data());
617 AutodrainedPool pool;
618 m_client->dispatchDidAddIconForPageURL(pageURL);
622 IconLoadDecision IconDatabase::loadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader)
624 ASSERT_NOT_SYNC_THREAD();
626 if (!isOpen() || iconURL.isEmpty())
629 // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord:
630 // 1 - When we read the icon urls from disk, getting the timeStamp at the same time
631 // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time
633 MutexLocker locker(m_urlAndIconLock);
634 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) {
635 LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord");
636 return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo;
640 // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now
641 MutexLocker readingLocker(m_pendingReadingLock);
642 if (m_iconURLImportComplete)
645 // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says
646 // "You might be asked to load this later, so flag that"
647 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);
648 m_loadersPendingDecision.add(notificationDocumentLoader);
650 return IconLoadUnknown;
653 bool IconDatabase::iconDataKnownForIconURL(const String& iconURL)
655 ASSERT_NOT_SYNC_THREAD();
657 MutexLocker locker(m_urlAndIconLock);
658 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
659 return icon->imageDataStatus() != ImageDataStatusUnknown;
664 void IconDatabase::setEnabled(bool enabled)
666 ASSERT_NOT_SYNC_THREAD();
668 if (!enabled && isOpen())
670 m_isEnabled = enabled;
673 bool IconDatabase::isEnabled() const
675 ASSERT_NOT_SYNC_THREAD();
680 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
682 m_privateBrowsingEnabled = flag;
685 bool IconDatabase::isPrivateBrowsingEnabled() const
687 return m_privateBrowsingEnabled;
690 void IconDatabase::delayDatabaseCleanup()
692 ++databaseCleanupCounter;
693 if (databaseCleanupCounter == 1)
694 LOG(IconDatabase, "Database cleanup is now DISABLED");
697 void IconDatabase::allowDatabaseCleanup()
699 if (--databaseCleanupCounter < 0)
700 databaseCleanupCounter = 0;
701 if (databaseCleanupCounter == 0)
702 LOG(IconDatabase, "Database cleanup is now ENABLED");
705 void IconDatabase::checkIntegrityBeforeOpening()
707 checkIntegrityOnOpen = true;
710 size_t IconDatabase::pageURLMappingCount()
712 MutexLocker locker(m_urlAndIconLock);
713 return m_pageURLToRecordMap.size();
716 size_t IconDatabase::retainedPageURLCount()
718 MutexLocker locker(m_urlAndIconLock);
719 return m_retainedPageURLs.size();
722 size_t IconDatabase::iconRecordCount()
724 MutexLocker locker(m_urlAndIconLock);
725 return m_iconURLToRecordMap.size();
728 size_t IconDatabase::iconRecordCountWithData()
730 MutexLocker locker(m_urlAndIconLock);
733 HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin();
734 HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end();
736 for (; i != end; ++i)
737 result += ((*i).second->imageDataStatus() == ImageDataStatusPresent);
742 IconDatabase::IconDatabase()
743 : m_syncThreadRunning(false)
744 , m_defaultIconRecord(0)
746 , m_privateBrowsingEnabled(false)
747 , m_threadTerminationRequested(false)
748 , m_removeIconsRequested(false)
749 , m_iconURLImportComplete(false)
750 , m_initialPruningComplete(false)
751 , m_client(defaultClient())
753 , m_isImportedSet(false)
756 ASSERT(pthread_main_np());
760 IconDatabase::~IconDatabase()
762 ASSERT_NOT_REACHED();
765 void IconDatabase::notifyPendingLoadDecisions()
767 iconDatabase()->notifyPendingLoadDecisionsInternal();
770 void IconDatabase::notifyPendingLoadDecisionsInternal()
772 ASSERT_NOT_SYNC_THREAD();
774 // This method should only be called upon completion of the initial url import from the database
775 ASSERT(m_iconURLImportComplete);
776 LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons");
778 HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin();
779 HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end();
781 for (; i != end; ++i)
782 if ((*i)->refCount() > 1)
783 (*i)->iconLoadDecisionAvailable();
785 m_loadersPendingDecision.clear();
788 void IconDatabase::wakeSyncThread()
790 MutexLocker locker(m_syncLock);
791 m_syncCondition.signal();
794 void IconDatabase::scheduleOrDeferSyncTimer()
796 ASSERT_NOT_SYNC_THREAD();
798 m_syncTimer.set(new Timer<IconDatabase>(this, &IconDatabase::syncTimerFired));
800 m_syncTimer->startOneShot(updateTimerDelay);
803 void IconDatabase::syncTimerFired(Timer<IconDatabase>*)
805 ASSERT_NOT_SYNC_THREAD();
809 // ******************
810 // *** Any Thread ***
811 // ******************
813 bool IconDatabase::isOpen() const
815 MutexLocker locker(m_syncLock);
816 return m_syncDB.isOpen();
819 String IconDatabase::databasePath() const
821 MutexLocker locker(m_syncLock);
822 return m_completeDatabasePath.copy();
825 String IconDatabase::defaultDatabaseFilename()
827 static String defaultDatabaseFilename = "WebpageIcons.db";
828 return defaultDatabaseFilename.copy();
831 // Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import"
832 IconRecord* IconDatabase::getOrCreateIconRecord(const String& iconURL)
834 // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method
835 ASSERT(!m_urlAndIconLock.tryLock());
837 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL))
840 IconRecord* newIcon = new IconRecord(iconURL);
841 m_iconURLToRecordMap.set(iconURL, newIcon);
846 // This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification
847 PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL)
849 // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method
850 ASSERT(!m_urlAndIconLock.tryLock());
852 if (pageURL.isEmpty())
855 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
857 MutexLocker locker(m_pendingReadingLock);
858 if (!m_iconURLImportComplete) {
859 // 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
861 LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data());
862 pageRecord = new PageURLRecord(pageURL);
863 m_pageURLToRecordMap.set(pageURL, pageRecord);
866 // 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
867 // Mark the URL as "interested in the result of the import" then bail
868 if (!pageRecord->iconRecord()) {
869 m_pageURLsPendingImport.add(pageURL);
874 // We've done the initial import of all URLs known in the database. If this record doesn't exist now, it never will
879 // ************************
880 // *** Sync Thread Only ***
881 // ************************
883 void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL)
885 ASSERT_ICON_SYNC_THREAD();
887 // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty
888 ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty());
890 setIconURLForPageURLInSQLDatabase(iconURL, pageURL);
893 void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL)
895 ASSERT_ICON_SYNC_THREAD();
897 ASSERT(!iconURL.isEmpty());
899 writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get()));
902 bool IconDatabase::shouldStopThreadActivity() const
904 ASSERT_ICON_SYNC_THREAD();
906 return m_threadTerminationRequested || m_removeIconsRequested;
909 void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase)
911 IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase);
913 return iconDB->iconDatabaseSyncThread();
916 void* IconDatabase::iconDatabaseSyncThread()
918 // 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
919 // to our thread structure hasn't been filled in yet.
920 // 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
921 // prevent us from running before that call completes
925 ASSERT_ICON_SYNC_THREAD();
927 LOG(IconDatabase, "(THREAD) IconDatabase sync thread started");
930 double startTime = currentTime();
933 // Need to create the database path if it doesn't already exist
934 makeAllDirectories(m_databaseDirectory);
936 // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies
937 // us to do an integrity check
938 String journalFilename = m_completeDatabasePath + "-journal";
939 if (!checkIntegrityOnOpen) {
940 AutodrainedPool pool;
941 checkIntegrityOnOpen = fileExists(journalFilename);
945 MutexLocker locker(m_syncLock);
946 if (!m_syncDB.open(m_completeDatabasePath)) {
947 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
952 if (shouldStopThreadActivity())
953 return syncThreadMainLoop();
956 double timeStamp = currentTime();
957 LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime);
960 performOpenInitialization();
961 if (shouldStopThreadActivity())
962 return syncThreadMainLoop();
965 double newStamp = currentTime();
966 LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
967 timeStamp = newStamp;
971 LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure");
972 SQLiteTransaction importTransaction(m_syncDB);
973 importTransaction.begin();
975 // Commit the transaction only if the import completes (the import should be atomic)
976 if (m_client->performImport()) {
978 importTransaction.commit();
980 LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled");
981 importTransaction.rollback();
984 if (shouldStopThreadActivity())
985 return syncThreadMainLoop();
988 newStamp = currentTime();
989 LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
990 timeStamp = newStamp;
994 // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories)
995 // while (currentTime() - timeStamp < 10);
997 // Read in URL mappings from the database
998 LOG(IconDatabase, "(THREAD) Starting iconURL import");
1001 if (shouldStopThreadActivity())
1002 return syncThreadMainLoop();
1005 newStamp = currentTime();
1006 LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds. Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime);
1009 LOG(IconDatabase, "(THREAD) Beginning sync");
1010 return syncThreadMainLoop();
1013 static int databaseVersionNumber(SQLiteDatabase& db)
1015 return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0);
1018 static bool isValidDatabase(SQLiteDatabase& db)
1021 // These four tables should always exist in a valid db
1022 if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
1025 if (databaseVersionNumber(db) < currentDatabaseVersion) {
1026 LOG(IconDatabase, "DB version is not found or below expected valid version");
1033 static void createDatabaseTables(SQLiteDatabase& db)
1035 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
1036 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1040 if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) {
1041 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1045 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);")) {
1046 LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1050 if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) {
1051 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1055 if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) {
1056 LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1060 if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) {
1061 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1065 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
1066 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
1070 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
1071 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
1077 void IconDatabase::performOpenInitialization()
1079 ASSERT_ICON_SYNC_THREAD();
1084 if (checkIntegrityOnOpen) {
1085 checkIntegrityOnOpen = false;
1086 if (!checkIntegrity()) {
1087 LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase");
1092 MutexLocker locker(m_syncLock);
1093 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future;
1094 deleteFile(m_completeDatabasePath + "-journal");
1095 deleteFile(m_completeDatabasePath);
1098 // Reopen the write database, creating it from scratch
1099 if (!m_syncDB.open(m_completeDatabasePath)) {
1100 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg());
1106 int version = databaseVersionNumber(m_syncDB);
1108 if (version > currentDatabaseVersion) {
1109 LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion);
1111 m_threadTerminationRequested = true;
1115 if (!isValidDatabase(m_syncDB)) {
1116 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_syncDB.path().ascii().data());
1117 m_syncDB.clearAllTables();
1118 createDatabaseTables(m_syncDB);
1121 // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill
1122 if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand())
1123 LOG_ERROR("SQLite database could not set cache_size");
1126 bool IconDatabase::checkIntegrity()
1128 ASSERT_ICON_SYNC_THREAD();
1130 SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;");
1131 if (integrity.prepare() != SQLResultOk) {
1132 LOG_ERROR("checkIntegrity failed to execute");
1136 int resultCode = integrity.step();
1137 if (resultCode == SQLResultOk)
1140 if (resultCode != SQLResultRow)
1143 int columns = integrity.columnCount();
1145 LOG_ERROR("Received %i columns performing integrity check, should be 1", columns);
1149 String resultText = integrity.getColumnText16(0);
1151 // A successful, no-error integrity check will be "ok" - all other strings imply failure
1152 if (resultText == "ok")
1155 LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data());
1159 void IconDatabase::performURLImport()
1161 ASSERT_ICON_SYNC_THREAD();
1163 SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;");
1165 if (query.prepare() != SQLResultOk) {
1166 LOG_ERROR("Unable to prepare icon url import query");
1170 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1171 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1172 AutodrainedPool pool(25);
1174 int result = query.step();
1175 while (result == SQLResultRow) {
1176 String pageURL = query.getColumnText16(0);
1177 String iconURL = query.getColumnText16(1);
1180 MutexLocker locker(m_urlAndIconLock);
1182 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL);
1184 // If the pageRecord doesn't exist in this map, then no one has retained this pageURL
1185 // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner,
1186 // so go ahead and actually create a pageURLRecord for this url even though it's not retained.
1187 // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested
1188 // in - we'll prune it later instead!
1189 if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) {
1190 pageRecord = new PageURLRecord(pageURL);
1191 m_pageURLToRecordMap.set(pageURL, pageRecord);
1195 IconRecord* currentIcon = pageRecord->iconRecord();
1197 if (!currentIcon || currentIcon->iconURL() != iconURL) {
1198 currentIcon = getOrCreateIconRecord(iconURL);
1199 pageRecord->setIconRecord(currentIcon);
1202 // 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
1203 // so we marked the timestamp as "now", but it's really much older
1204 currentIcon->setTimestamp(query.getColumnInt(2));
1208 // 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
1209 // 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 -
1210 // one for the URL and one for the Image itself
1211 // Note that WebIconDatabase is not neccessarily API so we might be able to make this change
1213 MutexLocker locker(m_pendingReadingLock);
1214 if (m_pageURLsPendingImport.contains(pageURL)) {
1215 m_client->dispatchDidAddIconForPageURL(pageURL);
1216 m_pageURLsPendingImport.remove(pageURL);
1222 // Stop the import at any time of the thread has been asked to shutdown
1223 if (shouldStopThreadActivity()) {
1224 LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()");
1228 result = query.step();
1231 if (result != SQLResultDone)
1232 LOG(IconDatabase, "Error reading page->icon url mappings from database");
1234 // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not,
1235 // but after m_iconURLImportComplete is set to true, we don't care about this set anymore
1236 Vector<String> urls;
1238 MutexLocker locker(m_pendingReadingLock);
1240 urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end());
1241 m_pageURLsPendingImport.clear();
1242 m_iconURLImportComplete = true;
1245 Vector<String> urlsToNotify;
1247 // Loop through the urls pending import
1248 // Remove unretained ones if database cleanup is allowed
1249 // Keep a set of ones that are retained and pending notification
1252 MutexLocker locker(m_urlAndIconLock);
1254 for (unsigned i = 0; i < urls.size(); ++i) {
1255 if (!m_retainedPageURLs.contains(urls[i])) {
1256 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]);
1257 if (record && !databaseCleanupCounter) {
1258 m_pageURLToRecordMap.remove(urls[i]);
1259 IconRecord* iconRecord = record->iconRecord();
1261 // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother
1262 // reading anything related to it
1263 if (iconRecord && iconRecord->hasOneRef()) {
1264 m_iconURLToRecordMap.remove(iconRecord->iconURL());
1267 MutexLocker locker(m_pendingReadingLock);
1268 m_pageURLsInterestedInIcons.remove(urls[i]);
1269 m_iconsPendingReading.remove(iconRecord);
1272 MutexLocker locker(m_pendingSyncLock);
1273 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true));
1280 urlsToNotify.append(urls[i]);
1285 LOG(IconDatabase, "Notifying %i interested page URLs that their icon URL is known due to the import", urlsToNotify.size());
1286 // Now that we don't hold any locks, perform the actual notifications
1287 for (unsigned i = 0; i < urlsToNotify.size(); ++i) {
1288 LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data());
1289 m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]);
1290 if (shouldStopThreadActivity())
1296 // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread
1297 callOnMainThread(notifyPendingLoadDecisions);
1300 void* IconDatabase::syncThreadMainLoop()
1302 ASSERT_ICON_SYNC_THREAD();
1303 static bool prunedUnretainedIcons = false;
1307 // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup
1308 while (!m_threadTerminationRequested) {
1309 m_syncLock.unlock();
1312 double timeStamp = currentTime();
1314 LOG(IconDatabase, "(THREAD) Main work loop starting");
1316 // 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
1317 if (m_removeIconsRequested) {
1318 removeAllIconsOnThread();
1319 m_removeIconsRequested = false;
1322 // Then, if the thread should be quitting, quit now!
1323 if (m_threadTerminationRequested)
1326 bool didAnyWork = true;
1327 while (didAnyWork) {
1328 didAnyWork = readFromDatabase();
1329 if (shouldStopThreadActivity())
1332 bool didWrite = writeToDatabase();
1333 if (shouldStopThreadActivity())
1336 // Prune unretained icons after the first time we sync anything out to the database
1337 // This way, pruning won't be the only operation we perform to the database by itself
1338 // We also don't want to bother doing this if the thread should be terminating (the user is quitting)
1339 // or if private browsing is enabled
1340 // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone
1341 // has asked to delay pruning
1342 if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) {
1344 double time = currentTime();
1346 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()");
1348 pruneUnretainedIcons();
1350 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time);
1352 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay
1353 // to mark prunedUnretainedIcons true because we're about to terminate anyway
1354 prunedUnretainedIcons = true;
1357 didAnyWork = didAnyWork || didWrite;
1358 if (shouldStopThreadActivity())
1363 double newstamp = currentTime();
1364 LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not");
1369 // There is some condition that is asking us to stop what we're doing now and handle a special case
1370 // This is either removing all icons, or shutting down the thread to quit the app
1371 // We handle those at the top of this main loop so continue to jump back up there
1372 if (shouldStopThreadActivity())
1375 m_syncCondition.wait(m_syncLock);
1377 m_syncLock.unlock();
1379 // Thread is terminating at this point
1380 return cleanupSyncThread();
1383 bool IconDatabase::readFromDatabase()
1385 ASSERT_ICON_SYNC_THREAD();
1388 double timeStamp = currentTime();
1391 bool didAnyWork = false;
1393 // 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
1394 // This way we won't hold the lock for a long period of time
1395 Vector<IconRecord*> icons;
1397 MutexLocker locker(m_pendingReadingLock);
1398 icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end());
1401 // Keep track of icons we actually read to notify them of the new icon
1402 HashSet<String> urlsToNotify;
1404 for (unsigned i = 0; i < icons.size(); ++i) {
1406 RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL());
1408 // Verify this icon still wants to be read from disk
1410 MutexLocker urlLocker(m_urlAndIconLock);
1412 MutexLocker readLocker(m_pendingReadingLock);
1414 if (m_iconsPendingReading.contains(icons[i])) {
1416 icons[i]->setImageData(imageData.get());
1418 // Remove this icon from the set that needs to be read
1419 m_iconsPendingReading.remove(icons[i]);
1421 // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon
1422 // We want to find the intersection of these two sets to notify them
1423 // Check the sizes of these two sets to minimize the number of iterations
1424 const HashSet<String>* outerHash;
1425 const HashSet<String>* innerHash;
1427 if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) {
1428 outerHash = &m_pageURLsInterestedInIcons;
1429 innerHash = &(icons[i]->retainingPageURLs());
1431 innerHash = &m_pageURLsInterestedInIcons;
1432 outerHash = &(icons[i]->retainingPageURLs());
1435 HashSet<String>::const_iterator iter = outerHash->begin();
1436 HashSet<String>::const_iterator end = outerHash->end();
1437 for (; iter != end; ++iter) {
1438 if (innerHash->contains(*iter)) {
1439 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());
1440 urlsToNotify.add(*iter);
1443 // If we ever get to the point were we've seen every url interested in this icon, break early
1444 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1448 // 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
1449 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size())
1450 m_pageURLsInterestedInIcons.clear();
1452 iter = urlsToNotify.begin();
1453 end = urlsToNotify.end();
1454 for (; iter != end; ++iter)
1455 m_pageURLsInterestedInIcons.remove(*iter);
1461 if (shouldStopThreadActivity())
1464 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go
1465 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up
1466 AutodrainedPool pool(25);
1468 // Now that we don't hold any locks, perform the actual notifications
1469 HashSet<String>::iterator iter = urlsToNotify.begin();
1470 HashSet<String>::iterator end = urlsToNotify.end();
1471 for (unsigned iteration = 0; iter != end; ++iter, ++iteration) {
1472 LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data());
1473 m_client->dispatchDidAddIconForPageURL(*iter);
1474 if (shouldStopThreadActivity())
1480 LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size());
1481 urlsToNotify.clear();
1483 if (shouldStopThreadActivity())
1487 LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp);
1492 bool IconDatabase::writeToDatabase()
1494 ASSERT_ICON_SYNC_THREAD();
1497 double timeStamp = currentTime();
1500 bool didAnyWork = false;
1502 // We can copy the current work queue then clear it out - If any new work comes in while we're writing out,
1503 // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes
1504 // asked for by the database on the main thread
1505 Vector<IconSnapshot> iconSnapshots;
1506 Vector<PageURLSnapshot> pageSnapshots;
1508 MutexLocker locker(m_pendingSyncLock);
1510 iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values());
1511 m_iconsPendingSync.clear();
1513 pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values());
1514 m_pageURLsPendingSync.clear();
1517 if (iconSnapshots.size() || pageSnapshots.size())
1520 SQLiteTransaction syncTransaction(m_syncDB);
1521 syncTransaction.begin();
1523 for (unsigned i = 0; i < iconSnapshots.size(); ++i) {
1524 writeIconSnapshotToSQLDatabase(iconSnapshots[i]);
1525 LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %li to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp);
1528 for (unsigned i = 0; i < pageSnapshots.size(); ++i) {
1529 String iconURL = pageSnapshots[i].iconURL;
1531 // If the icon URL is empty, this page is meant to be deleted
1532 // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't
1533 if (pageSnapshots[i].iconURL.isEmpty())
1534 removePageURLFromSQLDatabase(pageSnapshots[i].pageURL);
1536 setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL);
1538 LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data());
1541 syncTransaction.commit();
1543 // 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
1544 checkForDanglingPageURLs(false);
1546 LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp);
1551 void IconDatabase::pruneUnretainedIcons()
1553 ASSERT_ICON_SYNC_THREAD();
1558 // This method should only be called once per run
1559 ASSERT(!m_initialPruningComplete);
1561 // This method relies on having read in all page URLs from the database earlier.
1562 ASSERT(m_iconURLImportComplete);
1564 // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set.
1565 Vector<int64_t> pageIDsToDelete;
1567 SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;");
1571 while ((result = pageSQL.step()) == SQLResultRow) {
1572 MutexLocker locker(m_urlAndIconLock);
1573 if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText16(1)))
1574 pageIDsToDelete.append(pageSQL.getColumnInt64(0));
1577 if (result != SQLResultDone)
1578 LOG_ERROR("Error reading PageURL table from on-disk DB");
1581 // Delete page URLs that were in the table, but not in our retain count set.
1582 size_t numToDelete = pageIDsToDelete.size();
1584 SQLiteTransaction pruningTransaction(m_syncDB);
1585 pruningTransaction.begin();
1587 SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);");
1588 pageDeleteSQL.prepare();
1589 for (size_t i = 0; i < numToDelete; ++i) {
1590 LOG(IconDatabase, "Pruning page with rowid %lli from disk", pageIDsToDelete[i]);
1591 pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]);
1592 int result = pageDeleteSQL.step();
1593 if (result != SQLResultDone)
1594 LOG_ERROR("Unabled to delete page with id %lli from disk", pageIDsToDelete[i]);
1595 pageDeleteSQL.reset();
1597 // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can
1598 // finish the rest later (hopefully)
1599 if (shouldStopThreadActivity()) {
1600 pruningTransaction.commit();
1604 pruningTransaction.commit();
1605 pageDeleteSQL.finalize();
1608 // Deleting unreferenced icons from the Icon tables has to be atomic -
1609 // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue
1610 // A user on a network home directory with a wildly inconsistent database might see quite a pause...
1612 SQLiteTransaction pruningTransaction(m_syncDB);
1613 pruningTransaction.begin();
1615 // Wipe Icons that aren't retained
1616 if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1617 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table");
1618 if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);"))
1619 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table");
1621 pruningTransaction.commit();
1623 checkForDanglingPageURLs(true);
1625 m_initialPruningComplete = true;
1628 void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound)
1630 ASSERT_ICON_SYNC_THREAD();
1632 // We don't want to keep performing this check and reporting this error if it has already found danglers so we keep track
1633 static bool danglersFound = false;
1635 // However, if the caller wants us to prune the danglers, we will reset this flag and prune every time
1637 danglersFound = false;
1639 if (!danglersFound && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) {
1640 danglersFound = true;
1641 LOG_ERROR("Dangling PageURL entries found");
1642 if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);"))
1643 LOG_ERROR("Unable to prune dangling PageURLs");
1647 void IconDatabase::removeAllIconsOnThread()
1649 ASSERT_ICON_SYNC_THREAD();
1651 LOG(IconDatabase, "Removing all icons on the sync thread");
1653 // Delete all the prepared statements so they can start over
1654 deleteAllPreparedStatements();
1656 // To reset the on-disk database, we'll wipe all its tables then vacuum it
1657 // This is easier and safer than closing it, deleting the file, and recreating from scratch
1658 m_syncDB.clearAllTables();
1659 m_syncDB.runVacuumCommand();
1660 createDatabaseTables(m_syncDB);
1662 LOG(IconDatabase, "Dispatching notification that we removed all icons");
1663 m_client->dispatchDidRemoveAllIcons();
1666 void IconDatabase::deleteAllPreparedStatements()
1668 ASSERT_ICON_SYNC_THREAD();
1670 m_setIconIDForPageURLStatement.set(0);
1671 m_removePageURLStatement.set(0);
1672 m_getIconIDForIconURLStatement.set(0);
1673 m_getImageDataForIconURLStatement.set(0);
1674 m_addIconToIconInfoStatement.set(0);
1675 m_addIconToIconDataStatement.set(0);
1676 m_getImageDataStatement.set(0);
1677 m_deletePageURLsForIconURLStatement.set(0);
1678 m_deleteIconFromIconInfoStatement.set(0);
1679 m_deleteIconFromIconDataStatement.set(0);
1680 m_updateIconInfoStatement.set(0);
1681 m_updateIconDataStatement.set(0);
1682 m_setIconInfoStatement.set(0);
1683 m_setIconDataStatement.set(0);
1686 void* IconDatabase::cleanupSyncThread()
1688 ASSERT_ICON_SYNC_THREAD();
1691 double timeStamp = currentTime();
1694 // If the removeIcons flag is set, remove all icons from the db.
1695 if (m_removeIconsRequested)
1696 removeAllIconsOnThread();
1698 // Sync remaining icons out
1699 LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread");
1702 // Close the database
1703 MutexLocker locker(m_syncLock);
1705 m_databaseDirectory = String();
1706 m_completeDatabasePath = String();
1707 deleteAllPreparedStatements();
1711 LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp);
1714 m_syncThreadRunning = false;
1718 bool IconDatabase::imported()
1720 ASSERT_ICON_SYNC_THREAD();
1722 if (m_isImportedSet)
1725 SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";");
1726 if (query.prepare() != SQLResultOk) {
1727 LOG_ERROR("Unable to prepare imported statement");
1731 int result = query.step();
1732 if (result == SQLResultRow)
1733 result = query.getColumnInt(0);
1735 if (result != SQLResultDone)
1736 LOG_ERROR("imported statement failed");
1740 m_isImportedSet = true;
1741 return m_imported = result;
1744 void IconDatabase::setImported(bool import)
1746 ASSERT_ICON_SYNC_THREAD();
1748 m_imported = import;
1749 m_isImportedSet = true;
1751 String queryString = import ?
1752 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" :
1753 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);";
1755 SQLiteStatement query(m_syncDB, queryString);
1757 if (query.prepare() != SQLResultOk) {
1758 LOG_ERROR("Unable to prepare set imported statement");
1762 if (query.step() != SQLResultDone)
1763 LOG_ERROR("set imported statement failed");
1766 // readySQLiteStatement() handles two things
1767 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user
1768 // switches to and from private browsing
1769 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
1770 inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str)
1772 if (statement && (statement->database() != &db || statement->isExpired())) {
1773 if (statement->isExpired())
1774 LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data());
1778 statement.set(new SQLiteStatement(db, str));
1779 if (statement->prepare() != SQLResultOk)
1780 LOG_ERROR("Preparing statement %s failed", str.ascii().data());
1784 void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL)
1786 ASSERT_ICON_SYNC_THREAD();
1788 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1791 iconID = addIconURLToSQLDatabase(iconURL);
1794 LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data());
1799 setIconIDForPageURLInSQLDatabase(iconID, pageURL);
1802 void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL)
1804 ASSERT_ICON_SYNC_THREAD();
1806 readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
1807 m_setIconIDForPageURLStatement->bindText16(1, pageURL, false);
1808 m_setIconIDForPageURLStatement->bindInt64(2, iconID);
1810 int result = m_setIconIDForPageURLStatement->step();
1811 if (result != SQLResultDone) {
1813 LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data());
1816 m_setIconIDForPageURLStatement->reset();
1819 void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL)
1821 ASSERT_ICON_SYNC_THREAD();
1823 readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);");
1824 m_removePageURLStatement->bindText16(1, pageURL, false);
1826 if (m_removePageURLStatement->step() != SQLResultDone)
1827 LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data());
1829 m_removePageURLStatement->reset();
1833 int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL)
1835 ASSERT_ICON_SYNC_THREAD();
1837 readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);");
1838 m_getIconIDForIconURLStatement->bindText16(1, iconURL, false);
1840 int64_t result = m_getIconIDForIconURLStatement->step();
1841 if (result == SQLResultRow)
1842 result = m_getIconIDForIconURLStatement->getColumnInt64(0);
1844 if (result != SQLResultDone)
1845 LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1849 m_getIconIDForIconURLStatement->reset();
1853 int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL)
1855 ASSERT_ICON_SYNC_THREAD();
1857 // There would be a transaction here to make sure these two inserts are atomic
1858 // In practice the only caller of this method is always wrapped in a transaction itself so placing another
1859 // here is unnecessary
1861 readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);");
1862 m_addIconToIconInfoStatement->bindText16(1, iconURL);
1864 int result = m_addIconToIconInfoStatement->step();
1865 m_addIconToIconInfoStatement->reset();
1866 if (result != SQLResultDone) {
1867 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data());
1870 int64_t iconID = m_syncDB.lastInsertRowID();
1872 readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
1873 m_addIconToIconDataStatement->bindInt64(1, iconID);
1875 result = m_addIconToIconDataStatement->step();
1876 m_addIconToIconDataStatement->reset();
1877 if (result != SQLResultDone) {
1878 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data());
1885 PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL)
1887 ASSERT_ICON_SYNC_THREAD();
1889 RefPtr<SharedBuffer> imageData;
1891 readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));");
1892 m_getImageDataForIconURLStatement->bindText16(1, iconURL, false);
1894 int result = m_getImageDataForIconURLStatement->step();
1895 if (result == SQLResultRow) {
1897 m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data);
1898 imageData = new SharedBuffer;
1899 imageData->append(data.data(), data.size());
1900 } else if (result != SQLResultDone)
1901 LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data());
1903 m_getImageDataForIconURLStatement->reset();
1905 return imageData.release();
1908 void IconDatabase::removeIconFromSQLDatabase(const String& iconURL)
1910 ASSERT_ICON_SYNC_THREAD();
1912 if (iconURL.isEmpty())
1915 // There would be a transaction here to make sure these removals are atomic
1916 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1918 // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the
1919 // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return
1920 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL);
1924 readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);");
1925 m_deletePageURLsForIconURLStatement->bindInt64(1, iconID);
1927 if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone)
1928 LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1930 readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);");
1931 m_deleteIconFromIconInfoStatement->bindInt64(1, iconID);
1933 if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone)
1934 LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1936 readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);");
1937 m_deleteIconFromIconDataStatement->bindInt64(1, iconID);
1939 if (m_deleteIconFromIconDataStatement->step() != SQLResultDone)
1940 LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data());
1942 m_deletePageURLsForIconURLStatement->reset();
1943 m_deleteIconFromIconInfoStatement->reset();
1944 m_deleteIconFromIconDataStatement->reset();
1947 void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot)
1949 ASSERT_ICON_SYNC_THREAD();
1951 if (snapshot.iconURL.isEmpty())
1954 // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out
1955 if (!snapshot.timestamp && !snapshot.data) {
1956 LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data());
1957 removeIconFromSQLDatabase(snapshot.iconURL);
1961 // There would be a transaction here to make sure these removals are atomic
1962 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary
1964 // Get the iconID for this url
1965 int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL);
1967 // If there is already an iconID in place, update the database.
1968 // Otherwise, insert new records
1970 readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;");
1971 m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp);
1972 m_updateIconInfoStatement->bindText16(2, snapshot.iconURL);
1973 m_updateIconInfoStatement->bindInt64(3, iconID);
1975 if (m_updateIconInfoStatement->step() != SQLResultDone)
1976 LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
1978 m_updateIconInfoStatement->reset();
1980 readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;");
1981 m_updateIconDataStatement->bindInt64(2, iconID);
1983 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
1984 // signifying that this icon doesn't have any data
1985 if (snapshot.data && snapshot.data->size())
1986 m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size());
1988 m_updateIconDataStatement->bindNull(1);
1990 if (m_updateIconDataStatement->step() != SQLResultDone)
1991 LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
1993 m_updateIconDataStatement->reset();
1995 readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);");
1996 m_setIconInfoStatement->bindText16(1, snapshot.iconURL);
1997 m_setIconInfoStatement->bindInt64(2, snapshot.timestamp);
1999 if (m_setIconInfoStatement->step() != SQLResultDone)
2000 LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2002 m_setIconInfoStatement->reset();
2004 int64_t iconID = m_syncDB.lastInsertRowID();
2006 readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);");
2007 m_setIconDataStatement->bindInt64(1, iconID);
2009 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data,
2010 // signifying that this icon doesn't have any data
2011 if (snapshot.data && snapshot.data->size())
2012 m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size());
2014 m_setIconDataStatement->bindNull(2);
2016 if (m_setIconDataStatement->step() != SQLResultDone)
2017 LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data());
2019 m_setIconDataStatement->reset();
2023 } // namespace WebCore