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