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