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