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