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