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