IconDatabase.cpp revision 81bc750723a18f21cd17d1b173cd2a4dda9cea6e
1/* 2 * Copyright (C) 2006, 2007, 2008, 2009, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com) 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28#include "IconDatabase.h" 29 30#if ENABLE(ICONDATABASE) 31 32#include "AutodrainedPool.h" 33#include "DocumentLoader.h" 34#include "FileSystem.h" 35#include "IconDatabaseClient.h" 36#include "IconRecord.h" 37#include "IntSize.h" 38#include "Logging.h" 39#include "ScriptController.h" 40#include "SQLiteStatement.h" 41#include "SQLiteTransaction.h" 42#include "SuddenTermination.h" 43#include <wtf/CurrentTime.h> 44#include <wtf/MainThread.h> 45#include <wtf/StdLibExtras.h> 46#include <wtf/text/CString.h> 47 48// For methods that are meant to support API from the main thread - should not be called internally 49#define ASSERT_NOT_SYNC_THREAD() ASSERT(!m_syncThreadRunning || !IS_ICON_SYNC_THREAD()) 50 51// For methods that are meant to support the sync thread ONLY 52#define IS_ICON_SYNC_THREAD() (m_syncThread == currentThread()) 53#define ASSERT_ICON_SYNC_THREAD() ASSERT(IS_ICON_SYNC_THREAD()) 54 55#if PLATFORM(QT) || PLATFORM(GTK) 56#define CAN_THEME_URL_ICON 57#endif 58 59namespace WebCore { 60 61static IconDatabase* sharedIconDatabase = 0; 62static int databaseCleanupCounter = 0; 63 64// This version number is in the DB and marks the current generation of the schema 65// Currently, a mismatched schema causes the DB to be wiped and reset. This isn't 66// so bad during development but in the future, we would need to write a conversion 67// function to advance older released schemas to "current" 68static const int currentDatabaseVersion = 6; 69 70// Icons expire once every 4 days 71static const int iconExpirationTime = 60*60*24*4; 72 73static const int updateTimerDelay = 5; 74 75static bool checkIntegrityOnOpen = false; 76 77#ifndef NDEBUG 78static String urlForLogging(const String& url) 79{ 80 static unsigned urlTruncationLength = 120; 81 82 if (url.length() < urlTruncationLength) 83 return url; 84 return url.substring(0, urlTruncationLength) + "..."; 85} 86#endif 87 88static IconDatabaseClient* defaultClient() 89{ 90 static IconDatabaseClient* defaultClient = new IconDatabaseClient(); 91 return defaultClient; 92} 93 94IconDatabase& iconDatabase() 95{ 96 if (!sharedIconDatabase) { 97 ScriptController::initializeThreading(); 98 sharedIconDatabase = new IconDatabase; 99 } 100 return *sharedIconDatabase; 101} 102 103// ************************ 104// *** Main Thread Only *** 105// ************************ 106 107void 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 120bool IconDatabase::open(const String& databasePath) 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 = databasePath.crossThreadString(); 133 134 // Formulate the full path for the database file 135 m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, defaultDatabaseFilename()); 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 148void 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 176void 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 220Image* IconDatabase::iconForPageURL(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 299void 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 iconForPageURL(pageURL, IntSize(0,0)); 305} 306 307String IconDatabase::iconURLForPageURL(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 334static inline void loadDefaultIconRecord(IconRecord* defaultIconRecord) 335{ 336 defaultIconRecord->loadImageFromResource("urlIcon"); 337} 338#else 339static 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 384Image* 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 398void 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 442void 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 511void 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); 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->dispatchDidAddIconForPageURL(pageURLs[i]); 569 570 pool.cycle(); 571 } 572 } 573} 574 575void 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->dispatchDidAddIconForPageURL(pageURL); 641 } 642} 643 644IconLoadDecision IconDatabase::loadDecisionForIconURL(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 m_loadersPendingDecision.add(notificationDocumentLoader); 671 672 return IconLoadUnknown; 673} 674 675bool IconDatabase::iconDataKnownForIconURL(const String& iconURL) 676{ 677 ASSERT_NOT_SYNC_THREAD(); 678 679 MutexLocker locker(m_urlAndIconLock); 680 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) 681 return icon->imageDataStatus() != ImageDataStatusUnknown; 682 683 return false; 684} 685 686void IconDatabase::setEnabled(bool enabled) 687{ 688 ASSERT_NOT_SYNC_THREAD(); 689 690 if (!enabled && isOpen()) 691 close(); 692 m_isEnabled = enabled; 693} 694 695bool IconDatabase::isEnabled() const 696{ 697 ASSERT_NOT_SYNC_THREAD(); 698 699 return m_isEnabled; 700} 701 702void IconDatabase::setPrivateBrowsingEnabled(bool flag) 703{ 704 m_privateBrowsingEnabled = flag; 705} 706 707bool IconDatabase::isPrivateBrowsingEnabled() const 708{ 709 return m_privateBrowsingEnabled; 710} 711 712void IconDatabase::delayDatabaseCleanup() 713{ 714 ++databaseCleanupCounter; 715 if (databaseCleanupCounter == 1) 716 LOG(IconDatabase, "Database cleanup is now DISABLED"); 717} 718 719void IconDatabase::allowDatabaseCleanup() 720{ 721 if (--databaseCleanupCounter < 0) 722 databaseCleanupCounter = 0; 723 if (databaseCleanupCounter == 0) 724 LOG(IconDatabase, "Database cleanup is now ENABLED"); 725} 726 727void IconDatabase::checkIntegrityBeforeOpening() 728{ 729 checkIntegrityOnOpen = true; 730} 731 732size_t IconDatabase::pageURLMappingCount() 733{ 734 MutexLocker locker(m_urlAndIconLock); 735 return m_pageURLToRecordMap.size(); 736} 737 738size_t IconDatabase::retainedPageURLCount() 739{ 740 MutexLocker locker(m_urlAndIconLock); 741 return m_retainedPageURLs.size(); 742} 743 744size_t IconDatabase::iconRecordCount() 745{ 746 MutexLocker locker(m_urlAndIconLock); 747 return m_iconURLToRecordMap.size(); 748} 749 750size_t IconDatabase::iconRecordCountWithData() 751{ 752 MutexLocker locker(m_urlAndIconLock); 753 size_t result = 0; 754 755 HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin(); 756 HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end(); 757 758 for (; i != end; ++i) 759 result += ((*i).second->imageDataStatus() == ImageDataStatusPresent); 760 761 return result; 762} 763 764IconDatabase::IconDatabase() 765 : m_syncTimer(this, &IconDatabase::syncTimerFired) 766 , m_syncThreadRunning(false) 767 , m_isEnabled(false) 768 , m_privateBrowsingEnabled(false) 769 , m_threadTerminationRequested(false) 770 , m_removeIconsRequested(false) 771 , m_iconURLImportComplete(false) 772 , m_disabledSuddenTerminationForSyncThread(false) 773 , m_initialPruningComplete(false) 774 , m_client(defaultClient()) 775 , m_imported(false) 776 , m_isImportedSet(false) 777{ 778 ASSERT(isMainThread()); 779} 780 781IconDatabase::~IconDatabase() 782{ 783 ASSERT_NOT_REACHED(); 784} 785 786void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context) 787{ 788 static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions(); 789} 790 791void IconDatabase::notifyPendingLoadDecisions() 792{ 793 ASSERT_NOT_SYNC_THREAD(); 794 795 // This method should only be called upon completion of the initial url import from the database 796 ASSERT(m_iconURLImportComplete); 797 LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons"); 798 799 HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin(); 800 HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end(); 801 802 for (; i != end; ++i) 803 if ((*i)->refCount() > 1) 804 (*i)->iconLoadDecisionAvailable(); 805 806 m_loadersPendingDecision.clear(); 807} 808 809void IconDatabase::wakeSyncThread() 810{ 811 MutexLocker locker(m_syncLock); 812 813 if (!m_disabledSuddenTerminationForSyncThread) { 814 m_disabledSuddenTerminationForSyncThread = true; 815 // The following is balanced by the call to enableSuddenTermination in the 816 // syncThreadMainLoop function. 817 // FIXME: It would be better to only disable sudden termination if we have 818 // something to write, not just if we have something to read. 819 disableSuddenTermination(); 820 } 821 822 m_syncCondition.signal(); 823} 824 825void IconDatabase::scheduleOrDeferSyncTimer() 826{ 827 ASSERT_NOT_SYNC_THREAD(); 828 829 if (!m_syncTimer.isActive()) { 830 // The following is balanced by the call to enableSuddenTermination in the 831 // syncTimerFired function. 832 disableSuddenTermination(); 833 } 834 835 m_syncTimer.startOneShot(updateTimerDelay); 836} 837 838void IconDatabase::syncTimerFired(Timer<IconDatabase>*) 839{ 840 ASSERT_NOT_SYNC_THREAD(); 841 wakeSyncThread(); 842 843 // The following is balanced by the call to disableSuddenTermination in the 844 // scheduleOrDeferSyncTimer function. 845 enableSuddenTermination(); 846} 847 848// ****************** 849// *** Any Thread *** 850// ****************** 851 852bool IconDatabase::isOpen() const 853{ 854 MutexLocker locker(m_syncLock); 855 return m_syncDB.isOpen(); 856} 857 858String IconDatabase::databasePath() const 859{ 860 MutexLocker locker(m_syncLock); 861 return m_completeDatabasePath.threadsafeCopy(); 862} 863 864String IconDatabase::defaultDatabaseFilename() 865{ 866 DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db")); 867 return defaultDatabaseFilename.threadsafeCopy(); 868} 869 870// Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import" 871PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL) 872{ 873 // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method 874 ASSERT(!m_urlAndIconLock.tryLock()); 875 876 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) 877 return icon; 878 879 RefPtr<IconRecord> newIcon = IconRecord::create(iconURL); 880 m_iconURLToRecordMap.set(iconURL, newIcon.get()); 881 882 return newIcon.release(); 883} 884 885// This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification 886PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL) 887{ 888 // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method 889 ASSERT(!m_urlAndIconLock.tryLock()); 890 891 if (pageURL.isEmpty()) 892 return 0; 893 894 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); 895 896 MutexLocker locker(m_pendingReadingLock); 897 if (!m_iconURLImportComplete) { 898 // 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 899 if (!pageRecord) { 900 LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data()); 901 pageRecord = new PageURLRecord(pageURL); 902 m_pageURLToRecordMap.set(pageURL, pageRecord); 903 } 904 905 // 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 906 // Mark the URL as "interested in the result of the import" then bail 907 if (!pageRecord->iconRecord()) { 908 m_pageURLsPendingImport.add(pageURL); 909 return 0; 910 } 911 } 912 913 // We've done the initial import of all URLs known in the database. If this record doesn't exist now, it never will 914 return pageRecord; 915} 916 917 918// ************************ 919// *** Sync Thread Only *** 920// ************************ 921 922void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL) 923{ 924 ASSERT_ICON_SYNC_THREAD(); 925 926 // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty 927 ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty()); 928 929 setIconURLForPageURLInSQLDatabase(iconURL, pageURL); 930} 931 932void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL) 933{ 934 ASSERT_ICON_SYNC_THREAD(); 935 936 ASSERT(!iconURL.isEmpty()); 937 938 writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get())); 939} 940 941bool IconDatabase::shouldStopThreadActivity() const 942{ 943 ASSERT_ICON_SYNC_THREAD(); 944 945 return m_threadTerminationRequested || m_removeIconsRequested; 946} 947 948void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase) 949{ 950 IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase); 951 952 return iconDB->iconDatabaseSyncThread(); 953} 954 955void* IconDatabase::iconDatabaseSyncThread() 956{ 957 // 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 958 // to our thread structure hasn't been filled in yet. 959 // 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 960 // prevent us from running before that call completes 961 m_syncLock.lock(); 962 m_syncLock.unlock(); 963 964 ASSERT_ICON_SYNC_THREAD(); 965 966 LOG(IconDatabase, "(THREAD) IconDatabase sync thread started"); 967 968#ifndef NDEBUG 969 double startTime = currentTime(); 970#endif 971 972 // Need to create the database path if it doesn't already exist 973 makeAllDirectories(m_databaseDirectory); 974 975 // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies 976 // us to do an integrity check 977 String journalFilename = m_completeDatabasePath + "-journal"; 978 if (!checkIntegrityOnOpen) { 979 AutodrainedPool pool; 980 checkIntegrityOnOpen = fileExists(journalFilename); 981 } 982 983 { 984 MutexLocker locker(m_syncLock); 985 if (!m_syncDB.open(m_completeDatabasePath)) { 986 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); 987 return 0; 988 } 989 } 990 991 if (shouldStopThreadActivity()) 992 return syncThreadMainLoop(); 993 994#ifndef NDEBUG 995 double timeStamp = currentTime(); 996 LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime); 997#endif 998 999 performOpenInitialization(); 1000 if (shouldStopThreadActivity()) 1001 return syncThreadMainLoop(); 1002 1003#ifndef NDEBUG 1004 double newStamp = currentTime(); 1005 LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); 1006 timeStamp = newStamp; 1007#endif 1008 1009 if (!imported()) { 1010 LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure"); 1011 SQLiteTransaction importTransaction(m_syncDB); 1012 importTransaction.begin(); 1013 1014 // Commit the transaction only if the import completes (the import should be atomic) 1015 if (m_client->performImport()) { 1016 setImported(true); 1017 importTransaction.commit(); 1018 } else { 1019 LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled"); 1020 importTransaction.rollback(); 1021 } 1022 1023 if (shouldStopThreadActivity()) 1024 return syncThreadMainLoop(); 1025 1026#ifndef NDEBUG 1027 newStamp = currentTime(); 1028 LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); 1029 timeStamp = newStamp; 1030#endif 1031 } 1032 1033 // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories) 1034 // while (currentTime() - timeStamp < 10); 1035 1036 // Read in URL mappings from the database 1037 LOG(IconDatabase, "(THREAD) Starting iconURL import"); 1038 performURLImport(); 1039 1040 if (shouldStopThreadActivity()) 1041 return syncThreadMainLoop(); 1042 1043#ifndef NDEBUG 1044 newStamp = currentTime(); 1045 LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds. Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); 1046#endif 1047 1048 LOG(IconDatabase, "(THREAD) Beginning sync"); 1049 return syncThreadMainLoop(); 1050} 1051 1052static int databaseVersionNumber(SQLiteDatabase& db) 1053{ 1054 return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0); 1055} 1056 1057static bool isValidDatabase(SQLiteDatabase& db) 1058{ 1059 // These four tables should always exist in a valid db 1060 if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo")) 1061 return false; 1062 1063 if (databaseVersionNumber(db) < currentDatabaseVersion) { 1064 LOG(IconDatabase, "DB version is not found or below expected valid version"); 1065 return false; 1066 } 1067 1068 return true; 1069} 1070 1071static void createDatabaseTables(SQLiteDatabase& db) 1072{ 1073 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) { 1074 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1075 db.close(); 1076 return; 1077 } 1078 if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) { 1079 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1080 db.close(); 1081 return; 1082 } 1083 if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) { 1084 LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1085 db.close(); 1086 return; 1087 } 1088 if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) { 1089 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1090 db.close(); 1091 return; 1092 } 1093 if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) { 1094 LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1095 db.close(); 1096 return; 1097 } 1098 if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) { 1099 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1100 db.close(); 1101 return; 1102 } 1103 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { 1104 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1105 db.close(); 1106 return; 1107 } 1108 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) { 1109 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg()); 1110 db.close(); 1111 return; 1112 } 1113} 1114 1115void IconDatabase::performOpenInitialization() 1116{ 1117 ASSERT_ICON_SYNC_THREAD(); 1118 1119 if (!isOpen()) 1120 return; 1121 1122 if (checkIntegrityOnOpen) { 1123 checkIntegrityOnOpen = false; 1124 if (!checkIntegrity()) { 1125 LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase"); 1126 1127 m_syncDB.close(); 1128 1129 { 1130 MutexLocker locker(m_syncLock); 1131 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future; 1132 deleteFile(m_completeDatabasePath + "-journal"); 1133 deleteFile(m_completeDatabasePath); 1134 } 1135 1136 // Reopen the write database, creating it from scratch 1137 if (!m_syncDB.open(m_completeDatabasePath)) { 1138 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); 1139 return; 1140 } 1141 } 1142 } 1143 1144 int version = databaseVersionNumber(m_syncDB); 1145 1146 if (version > currentDatabaseVersion) { 1147 LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion); 1148 m_syncDB.close(); 1149 m_threadTerminationRequested = true; 1150 return; 1151 } 1152 1153 if (!isValidDatabase(m_syncDB)) { 1154 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data()); 1155 m_syncDB.clearAllTables(); 1156 createDatabaseTables(m_syncDB); 1157 } 1158 1159 // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill 1160 if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand()) 1161 LOG_ERROR("SQLite database could not set cache_size"); 1162 1163 // Tell backup software (i.e., Time Machine) to never back up the icon database, because 1164 // it's a large file that changes frequently, thus using a lot of backup disk space, and 1165 // it's unlikely that many users would be upset about it not being backed up. We could 1166 // make this configurable on a per-client basis some day if some clients don't want this. 1167 if (canExcludeFromBackup() && !wasExcludedFromBackup() && excludeFromBackup(m_completeDatabasePath)) 1168 setWasExcludedFromBackup(); 1169} 1170 1171bool IconDatabase::checkIntegrity() 1172{ 1173 ASSERT_ICON_SYNC_THREAD(); 1174 1175 SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;"); 1176 if (integrity.prepare() != SQLResultOk) { 1177 LOG_ERROR("checkIntegrity failed to execute"); 1178 return false; 1179 } 1180 1181 int resultCode = integrity.step(); 1182 if (resultCode == SQLResultOk) 1183 return true; 1184 1185 if (resultCode != SQLResultRow) 1186 return false; 1187 1188 int columns = integrity.columnCount(); 1189 if (columns != 1) { 1190 LOG_ERROR("Received %i columns performing integrity check, should be 1", columns); 1191 return false; 1192 } 1193 1194 String resultText = integrity.getColumnText(0); 1195 1196 // A successful, no-error integrity check will be "ok" - all other strings imply failure 1197 if (resultText == "ok") 1198 return true; 1199 1200 LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data()); 1201 return false; 1202} 1203 1204void IconDatabase::performURLImport() 1205{ 1206 ASSERT_ICON_SYNC_THREAD(); 1207 1208 SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;"); 1209 1210 if (query.prepare() != SQLResultOk) { 1211 LOG_ERROR("Unable to prepare icon url import query"); 1212 return; 1213 } 1214 1215 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go 1216 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up 1217 AutodrainedPool pool(25); 1218 1219 int result = query.step(); 1220 while (result == SQLResultRow) { 1221 String pageURL = query.getColumnText(0); 1222 String iconURL = query.getColumnText(1); 1223 1224 { 1225 MutexLocker locker(m_urlAndIconLock); 1226 1227 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); 1228 1229 // If the pageRecord doesn't exist in this map, then no one has retained this pageURL 1230 // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner, 1231 // so go ahead and actually create a pageURLRecord for this url even though it's not retained. 1232 // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested 1233 // in - we'll prune it later instead! 1234 if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) { 1235 pageRecord = new PageURLRecord(pageURL); 1236 m_pageURLToRecordMap.set(pageURL, pageRecord); 1237 } 1238 1239 if (pageRecord) { 1240 IconRecord* currentIcon = pageRecord->iconRecord(); 1241 1242 if (!currentIcon || currentIcon->iconURL() != iconURL) { 1243 pageRecord->setIconRecord(getOrCreateIconRecord(iconURL)); 1244 currentIcon = pageRecord->iconRecord(); 1245 } 1246 1247 // 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 1248 // so we marked the timestamp as "now", but it's really much older 1249 currentIcon->setTimestamp(query.getColumnInt(2)); 1250 } 1251 } 1252 1253 // 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 1254 // 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 - 1255 // one for the URL and one for the Image itself 1256 // Note that WebIconDatabase is not neccessarily API so we might be able to make this change 1257 { 1258 MutexLocker locker(m_pendingReadingLock); 1259 if (m_pageURLsPendingImport.contains(pageURL)) { 1260 m_client->dispatchDidAddIconForPageURL(pageURL); 1261 m_pageURLsPendingImport.remove(pageURL); 1262 1263 pool.cycle(); 1264 } 1265 } 1266 1267 // Stop the import at any time of the thread has been asked to shutdown 1268 if (shouldStopThreadActivity()) { 1269 LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()"); 1270 return; 1271 } 1272 1273 result = query.step(); 1274 } 1275 1276 if (result != SQLResultDone) 1277 LOG(IconDatabase, "Error reading page->icon url mappings from database"); 1278 1279 // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not, 1280 // but after m_iconURLImportComplete is set to true, we don't care about this set anymore 1281 Vector<String> urls; 1282 { 1283 MutexLocker locker(m_pendingReadingLock); 1284 1285 urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end()); 1286 m_pageURLsPendingImport.clear(); 1287 m_iconURLImportComplete = true; 1288 } 1289 1290 Vector<String> urlsToNotify; 1291 1292 // Loop through the urls pending import 1293 // Remove unretained ones if database cleanup is allowed 1294 // Keep a set of ones that are retained and pending notification 1295 1296 { 1297 MutexLocker locker(m_urlAndIconLock); 1298 1299 for (unsigned i = 0; i < urls.size(); ++i) { 1300 if (!m_retainedPageURLs.contains(urls[i])) { 1301 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]); 1302 if (record && !databaseCleanupCounter) { 1303 m_pageURLToRecordMap.remove(urls[i]); 1304 IconRecord* iconRecord = record->iconRecord(); 1305 1306 // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother 1307 // reading anything related to it 1308 if (iconRecord && iconRecord->hasOneRef()) { 1309 m_iconURLToRecordMap.remove(iconRecord->iconURL()); 1310 1311 { 1312 MutexLocker locker(m_pendingReadingLock); 1313 m_pageURLsInterestedInIcons.remove(urls[i]); 1314 m_iconsPendingReading.remove(iconRecord); 1315 } 1316 { 1317 MutexLocker locker(m_pendingSyncLock); 1318 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true)); 1319 } 1320 } 1321 1322 delete record; 1323 } 1324 } else { 1325 urlsToNotify.append(urls[i]); 1326 } 1327 } 1328 } 1329 1330 LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size())); 1331 // Now that we don't hold any locks, perform the actual notifications 1332 for (unsigned i = 0; i < urlsToNotify.size(); ++i) { 1333 LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data()); 1334 m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]); 1335 if (shouldStopThreadActivity()) 1336 return; 1337 1338 pool.cycle(); 1339 } 1340 1341 // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread 1342 callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this); 1343} 1344 1345void* IconDatabase::syncThreadMainLoop() 1346{ 1347 ASSERT_ICON_SYNC_THREAD(); 1348 1349 bool shouldReenableSuddenTermination = false; 1350 1351 m_syncLock.lock(); 1352 1353 // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup 1354 while (!m_threadTerminationRequested) { 1355 m_syncLock.unlock(); 1356 1357#ifndef NDEBUG 1358 double timeStamp = currentTime(); 1359#endif 1360 LOG(IconDatabase, "(THREAD) Main work loop starting"); 1361 1362 // 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 1363 if (m_removeIconsRequested) { 1364 removeAllIconsOnThread(); 1365 m_removeIconsRequested = false; 1366 } 1367 1368 // Then, if the thread should be quitting, quit now! 1369 if (m_threadTerminationRequested) 1370 break; 1371 1372 bool didAnyWork = true; 1373 while (didAnyWork) { 1374 bool didWrite = writeToDatabase(); 1375 if (shouldStopThreadActivity()) 1376 break; 1377 1378 didAnyWork = readFromDatabase(); 1379 if (shouldStopThreadActivity()) 1380 break; 1381 1382 // Prune unretained icons after the first time we sync anything out to the database 1383 // This way, pruning won't be the only operation we perform to the database by itself 1384 // We also don't want to bother doing this if the thread should be terminating (the user is quitting) 1385 // or if private browsing is enabled 1386 // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone 1387 // has asked to delay pruning 1388 static bool prunedUnretainedIcons = false; 1389 if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) { 1390#ifndef NDEBUG 1391 double time = currentTime(); 1392#endif 1393 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()"); 1394 1395 pruneUnretainedIcons(); 1396 1397 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time); 1398 1399 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay 1400 // to mark prunedUnretainedIcons true because we're about to terminate anyway 1401 prunedUnretainedIcons = true; 1402 } 1403 1404 didAnyWork = didAnyWork || didWrite; 1405 if (shouldStopThreadActivity()) 1406 break; 1407 } 1408 1409#ifndef NDEBUG 1410 double newstamp = currentTime(); 1411 LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not"); 1412#endif 1413 1414 m_syncLock.lock(); 1415 1416 // There is some condition that is asking us to stop what we're doing now and handle a special case 1417 // This is either removing all icons, or shutting down the thread to quit the app 1418 // We handle those at the top of this main loop so continue to jump back up there 1419 if (shouldStopThreadActivity()) 1420 continue; 1421 1422 if (shouldReenableSuddenTermination) { 1423 // The following is balanced by the call to disableSuddenTermination in the 1424 // wakeSyncThread function. Any time we wait on the condition, we also have 1425 // to enableSuddenTermation, after doing the next batch of work. 1426 ASSERT(m_disabledSuddenTerminationForSyncThread); 1427 enableSuddenTermination(); 1428 m_disabledSuddenTerminationForSyncThread = false; 1429 } 1430 1431 m_syncCondition.wait(m_syncLock); 1432 1433 shouldReenableSuddenTermination = true; 1434 } 1435 1436 m_syncLock.unlock(); 1437 1438 // Thread is terminating at this point 1439 cleanupSyncThread(); 1440 1441 if (shouldReenableSuddenTermination) { 1442 // The following is balanced by the call to disableSuddenTermination in the 1443 // wakeSyncThread function. Any time we wait on the condition, we also have 1444 // to enableSuddenTermation, after doing the next batch of work. 1445 ASSERT(m_disabledSuddenTerminationForSyncThread); 1446 enableSuddenTermination(); 1447 m_disabledSuddenTerminationForSyncThread = false; 1448 } 1449 1450 return 0; 1451} 1452 1453bool IconDatabase::readFromDatabase() 1454{ 1455 ASSERT_ICON_SYNC_THREAD(); 1456 1457#ifndef NDEBUG 1458 double timeStamp = currentTime(); 1459#endif 1460 1461 bool didAnyWork = false; 1462 1463 // 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 1464 // This way we won't hold the lock for a long period of time 1465 Vector<IconRecord*> icons; 1466 { 1467 MutexLocker locker(m_pendingReadingLock); 1468 icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end()); 1469 } 1470 1471 // Keep track of icons we actually read to notify them of the new icon 1472 HashSet<String> urlsToNotify; 1473 1474 for (unsigned i = 0; i < icons.size(); ++i) { 1475 didAnyWork = true; 1476 RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL()); 1477 1478 // Verify this icon still wants to be read from disk 1479 { 1480 MutexLocker urlLocker(m_urlAndIconLock); 1481 { 1482 MutexLocker readLocker(m_pendingReadingLock); 1483 1484 if (m_iconsPendingReading.contains(icons[i])) { 1485 // Set the new data 1486 icons[i]->setImageData(imageData.get()); 1487 1488 // Remove this icon from the set that needs to be read 1489 m_iconsPendingReading.remove(icons[i]); 1490 1491 // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon 1492 // We want to find the intersection of these two sets to notify them 1493 // Check the sizes of these two sets to minimize the number of iterations 1494 const HashSet<String>* outerHash; 1495 const HashSet<String>* innerHash; 1496 1497 if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) { 1498 outerHash = &m_pageURLsInterestedInIcons; 1499 innerHash = &(icons[i]->retainingPageURLs()); 1500 } else { 1501 innerHash = &m_pageURLsInterestedInIcons; 1502 outerHash = &(icons[i]->retainingPageURLs()); 1503 } 1504 1505 HashSet<String>::const_iterator iter = outerHash->begin(); 1506 HashSet<String>::const_iterator end = outerHash->end(); 1507 for (; iter != end; ++iter) { 1508 if (innerHash->contains(*iter)) { 1509 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()); 1510 urlsToNotify.add(*iter); 1511 } 1512 1513 // If we ever get to the point were we've seen every url interested in this icon, break early 1514 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) 1515 break; 1516 } 1517 1518 // 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 1519 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) 1520 m_pageURLsInterestedInIcons.clear(); 1521 else { 1522 iter = urlsToNotify.begin(); 1523 end = urlsToNotify.end(); 1524 for (; iter != end; ++iter) 1525 m_pageURLsInterestedInIcons.remove(*iter); 1526 } 1527 } 1528 } 1529 } 1530 1531 if (shouldStopThreadActivity()) 1532 return didAnyWork; 1533 1534 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go 1535 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up 1536 AutodrainedPool pool(25); 1537 1538 // Now that we don't hold any locks, perform the actual notifications 1539 HashSet<String>::iterator iter = urlsToNotify.begin(); 1540 HashSet<String>::iterator end = urlsToNotify.end(); 1541 for (unsigned iteration = 0; iter != end; ++iter, ++iteration) { 1542 LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data()); 1543 m_client->dispatchDidAddIconForPageURL(*iter); 1544 if (shouldStopThreadActivity()) 1545 return didAnyWork; 1546 1547 pool.cycle(); 1548 } 1549 1550 LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size()); 1551 urlsToNotify.clear(); 1552 1553 if (shouldStopThreadActivity()) 1554 return didAnyWork; 1555 } 1556 1557 LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp); 1558 1559 return didAnyWork; 1560} 1561 1562bool IconDatabase::writeToDatabase() 1563{ 1564 ASSERT_ICON_SYNC_THREAD(); 1565 1566#ifndef NDEBUG 1567 double timeStamp = currentTime(); 1568#endif 1569 1570 bool didAnyWork = false; 1571 1572 // We can copy the current work queue then clear it out - If any new work comes in while we're writing out, 1573 // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes 1574 // asked for by the database on the main thread 1575 Vector<IconSnapshot> iconSnapshots; 1576 Vector<PageURLSnapshot> pageSnapshots; 1577 { 1578 MutexLocker locker(m_pendingSyncLock); 1579 1580 iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values()); 1581 m_iconsPendingSync.clear(); 1582 1583 pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values()); 1584 m_pageURLsPendingSync.clear(); 1585 } 1586 1587 if (iconSnapshots.size() || pageSnapshots.size()) 1588 didAnyWork = true; 1589 1590 SQLiteTransaction syncTransaction(m_syncDB); 1591 syncTransaction.begin(); 1592 1593 for (unsigned i = 0; i < iconSnapshots.size(); ++i) { 1594 writeIconSnapshotToSQLDatabase(iconSnapshots[i]); 1595 LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp); 1596 } 1597 1598 for (unsigned i = 0; i < pageSnapshots.size(); ++i) { 1599 // If the icon URL is empty, this page is meant to be deleted 1600 // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't 1601 if (pageSnapshots[i].iconURL.isEmpty()) 1602 removePageURLFromSQLDatabase(pageSnapshots[i].pageURL); 1603 else 1604 setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL); 1605 LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data()); 1606 } 1607 1608 syncTransaction.commit(); 1609 1610 // 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 1611 if (didAnyWork) 1612 checkForDanglingPageURLs(false); 1613 1614 LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp); 1615 1616 return didAnyWork; 1617} 1618 1619void IconDatabase::pruneUnretainedIcons() 1620{ 1621 ASSERT_ICON_SYNC_THREAD(); 1622 1623 if (!isOpen()) 1624 return; 1625 1626 // This method should only be called once per run 1627 ASSERT(!m_initialPruningComplete); 1628 1629 // This method relies on having read in all page URLs from the database earlier. 1630 ASSERT(m_iconURLImportComplete); 1631 1632 // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set. 1633 Vector<int64_t> pageIDsToDelete; 1634 1635 SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;"); 1636 pageSQL.prepare(); 1637 1638 int result; 1639 while ((result = pageSQL.step()) == SQLResultRow) { 1640 MutexLocker locker(m_urlAndIconLock); 1641 if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1))) 1642 pageIDsToDelete.append(pageSQL.getColumnInt64(0)); 1643 } 1644 1645 if (result != SQLResultDone) 1646 LOG_ERROR("Error reading PageURL table from on-disk DB"); 1647 pageSQL.finalize(); 1648 1649 // Delete page URLs that were in the table, but not in our retain count set. 1650 size_t numToDelete = pageIDsToDelete.size(); 1651 if (numToDelete) { 1652 SQLiteTransaction pruningTransaction(m_syncDB); 1653 pruningTransaction.begin(); 1654 1655 SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);"); 1656 pageDeleteSQL.prepare(); 1657 for (size_t i = 0; i < numToDelete; ++i) { 1658#if OS(WINDOWS) 1659 LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); 1660#else 1661 LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i])); 1662#endif 1663 pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]); 1664 int result = pageDeleteSQL.step(); 1665 if (result != SQLResultDone) 1666#if OS(WINDOWS) 1667 LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); 1668#else 1669 LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i])); 1670#endif 1671 pageDeleteSQL.reset(); 1672 1673 // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can 1674 // finish the rest later (hopefully) 1675 if (shouldStopThreadActivity()) { 1676 pruningTransaction.commit(); 1677 return; 1678 } 1679 } 1680 pruningTransaction.commit(); 1681 pageDeleteSQL.finalize(); 1682 } 1683 1684 // Deleting unreferenced icons from the Icon tables has to be atomic - 1685 // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue 1686 // A user on a network home directory with a wildly inconsistent database might see quite a pause... 1687 1688 SQLiteTransaction pruningTransaction(m_syncDB); 1689 pruningTransaction.begin(); 1690 1691 // Wipe Icons that aren't retained 1692 if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) 1693 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table"); 1694 if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) 1695 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table"); 1696 1697 pruningTransaction.commit(); 1698 1699 checkForDanglingPageURLs(true); 1700 1701 m_initialPruningComplete = true; 1702} 1703 1704void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound) 1705{ 1706 ASSERT_ICON_SYNC_THREAD(); 1707 1708 // 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 1709 // entries. We also don't want to keep performing this check and reporting this error if it has already found danglers before so we 1710 // keep track of whether we've found any. We skip the check in the release build pretending to have already found danglers already. 1711#ifndef NDEBUG 1712 static bool danglersFound = true; 1713#else 1714 static bool danglersFound = false; 1715#endif 1716 1717 if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) { 1718 danglersFound = true; 1719 LOG(IconDatabase, "Dangling PageURL entries found"); 1720 if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);")) 1721 LOG(IconDatabase, "Unable to prune dangling PageURLs"); 1722 } 1723} 1724 1725void IconDatabase::removeAllIconsOnThread() 1726{ 1727 ASSERT_ICON_SYNC_THREAD(); 1728 1729 LOG(IconDatabase, "Removing all icons on the sync thread"); 1730 1731 // Delete all the prepared statements so they can start over 1732 deleteAllPreparedStatements(); 1733 1734 // To reset the on-disk database, we'll wipe all its tables then vacuum it 1735 // This is easier and safer than closing it, deleting the file, and recreating from scratch 1736 m_syncDB.clearAllTables(); 1737 m_syncDB.runVacuumCommand(); 1738 createDatabaseTables(m_syncDB); 1739 1740 LOG(IconDatabase, "Dispatching notification that we removed all icons"); 1741 m_client->dispatchDidRemoveAllIcons(); 1742} 1743 1744void IconDatabase::deleteAllPreparedStatements() 1745{ 1746 ASSERT_ICON_SYNC_THREAD(); 1747 1748 m_setIconIDForPageURLStatement.clear(); 1749 m_removePageURLStatement.clear(); 1750 m_getIconIDForIconURLStatement.clear(); 1751 m_getImageDataForIconURLStatement.clear(); 1752 m_addIconToIconInfoStatement.clear(); 1753 m_addIconToIconDataStatement.clear(); 1754 m_getImageDataStatement.clear(); 1755 m_deletePageURLsForIconURLStatement.clear(); 1756 m_deleteIconFromIconInfoStatement.clear(); 1757 m_deleteIconFromIconDataStatement.clear(); 1758 m_updateIconInfoStatement.clear(); 1759 m_updateIconDataStatement.clear(); 1760 m_setIconInfoStatement.clear(); 1761 m_setIconDataStatement.clear(); 1762} 1763 1764void* IconDatabase::cleanupSyncThread() 1765{ 1766 ASSERT_ICON_SYNC_THREAD(); 1767 1768#ifndef NDEBUG 1769 double timeStamp = currentTime(); 1770#endif 1771 1772 // If the removeIcons flag is set, remove all icons from the db. 1773 if (m_removeIconsRequested) 1774 removeAllIconsOnThread(); 1775 1776 // Sync remaining icons out 1777 LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread"); 1778 writeToDatabase(); 1779 1780 // Close the database 1781 MutexLocker locker(m_syncLock); 1782 1783 m_databaseDirectory = String(); 1784 m_completeDatabasePath = String(); 1785 deleteAllPreparedStatements(); 1786 m_syncDB.close(); 1787 1788#ifndef NDEBUG 1789 LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp); 1790#endif 1791 1792 m_syncThreadRunning = false; 1793 return 0; 1794} 1795 1796bool IconDatabase::imported() 1797{ 1798 ASSERT_ICON_SYNC_THREAD(); 1799 1800 if (m_isImportedSet) 1801 return m_imported; 1802 1803 SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";"); 1804 if (query.prepare() != SQLResultOk) { 1805 LOG_ERROR("Unable to prepare imported statement"); 1806 return false; 1807 } 1808 1809 int result = query.step(); 1810 if (result == SQLResultRow) 1811 result = query.getColumnInt(0); 1812 else { 1813 if (result != SQLResultDone) 1814 LOG_ERROR("imported statement failed"); 1815 result = 0; 1816 } 1817 1818 m_isImportedSet = true; 1819 return m_imported = result; 1820} 1821 1822void IconDatabase::setImported(bool import) 1823{ 1824 ASSERT_ICON_SYNC_THREAD(); 1825 1826 m_imported = import; 1827 m_isImportedSet = true; 1828 1829 String queryString = import ? 1830 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" : 1831 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);"; 1832 1833 SQLiteStatement query(m_syncDB, queryString); 1834 1835 if (query.prepare() != SQLResultOk) { 1836 LOG_ERROR("Unable to prepare set imported statement"); 1837 return; 1838 } 1839 1840 if (query.step() != SQLResultDone) 1841 LOG_ERROR("set imported statement failed"); 1842} 1843 1844// readySQLiteStatement() handles two things 1845// 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user 1846// switches to and from private browsing 1847// 2 - Lazy construction of the Statement in the first place, in case we've never made this query before 1848inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str) 1849{ 1850 if (statement && (statement->database() != &db || statement->isExpired())) { 1851 if (statement->isExpired()) 1852 LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data()); 1853 statement.set(0); 1854 } 1855 if (!statement) { 1856 statement = adoptPtr(new SQLiteStatement(db, str)); 1857 if (statement->prepare() != SQLResultOk) 1858 LOG_ERROR("Preparing statement %s failed", str.ascii().data()); 1859 } 1860} 1861 1862void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL) 1863{ 1864 ASSERT_ICON_SYNC_THREAD(); 1865 1866 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); 1867 1868 if (!iconID) 1869 iconID = addIconURLToSQLDatabase(iconURL); 1870 1871 if (!iconID) { 1872 LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data()); 1873 ASSERT(false); 1874 return; 1875 } 1876 1877 setIconIDForPageURLInSQLDatabase(iconID, pageURL); 1878} 1879 1880void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL) 1881{ 1882 ASSERT_ICON_SYNC_THREAD(); 1883 1884 readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);"); 1885 m_setIconIDForPageURLStatement->bindText(1, pageURL); 1886 m_setIconIDForPageURLStatement->bindInt64(2, iconID); 1887 1888 int result = m_setIconIDForPageURLStatement->step(); 1889 if (result != SQLResultDone) { 1890 ASSERT(false); 1891 LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data()); 1892 } 1893 1894 m_setIconIDForPageURLStatement->reset(); 1895} 1896 1897void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL) 1898{ 1899 ASSERT_ICON_SYNC_THREAD(); 1900 1901 readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);"); 1902 m_removePageURLStatement->bindText(1, pageURL); 1903 1904 if (m_removePageURLStatement->step() != SQLResultDone) 1905 LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data()); 1906 1907 m_removePageURLStatement->reset(); 1908} 1909 1910 1911int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL) 1912{ 1913 ASSERT_ICON_SYNC_THREAD(); 1914 1915 readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);"); 1916 m_getIconIDForIconURLStatement->bindText(1, iconURL); 1917 1918 int64_t result = m_getIconIDForIconURLStatement->step(); 1919 if (result == SQLResultRow) 1920 result = m_getIconIDForIconURLStatement->getColumnInt64(0); 1921 else { 1922 if (result != SQLResultDone) 1923 LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); 1924 result = 0; 1925 } 1926 1927 m_getIconIDForIconURLStatement->reset(); 1928 return result; 1929} 1930 1931int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL) 1932{ 1933 ASSERT_ICON_SYNC_THREAD(); 1934 1935 // There would be a transaction here to make sure these two inserts are atomic 1936 // In practice the only caller of this method is always wrapped in a transaction itself so placing another 1937 // here is unnecessary 1938 1939 readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);"); 1940 m_addIconToIconInfoStatement->bindText(1, iconURL); 1941 1942 int result = m_addIconToIconInfoStatement->step(); 1943 m_addIconToIconInfoStatement->reset(); 1944 if (result != SQLResultDone) { 1945 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data()); 1946 return 0; 1947 } 1948 int64_t iconID = m_syncDB.lastInsertRowID(); 1949 1950 readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); 1951 m_addIconToIconDataStatement->bindInt64(1, iconID); 1952 1953 result = m_addIconToIconDataStatement->step(); 1954 m_addIconToIconDataStatement->reset(); 1955 if (result != SQLResultDone) { 1956 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data()); 1957 return 0; 1958 } 1959 1960 return iconID; 1961} 1962 1963PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL) 1964{ 1965 ASSERT_ICON_SYNC_THREAD(); 1966 1967 RefPtr<SharedBuffer> imageData; 1968 1969 readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));"); 1970 m_getImageDataForIconURLStatement->bindText(1, iconURL); 1971 1972 int result = m_getImageDataForIconURLStatement->step(); 1973 if (result == SQLResultRow) { 1974 Vector<char> data; 1975 m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data); 1976 imageData = SharedBuffer::create(data.data(), data.size()); 1977 } else if (result != SQLResultDone) 1978 LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); 1979 1980 m_getImageDataForIconURLStatement->reset(); 1981 1982 return imageData.release(); 1983} 1984 1985void IconDatabase::removeIconFromSQLDatabase(const String& iconURL) 1986{ 1987 ASSERT_ICON_SYNC_THREAD(); 1988 1989 if (iconURL.isEmpty()) 1990 return; 1991 1992 // There would be a transaction here to make sure these removals are atomic 1993 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary 1994 1995 // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the 1996 // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return 1997 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); 1998 if (!iconID) 1999 return; 2000 2001 readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);"); 2002 m_deletePageURLsForIconURLStatement->bindInt64(1, iconID); 2003 2004 if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone) 2005 LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2006 2007 readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);"); 2008 m_deleteIconFromIconInfoStatement->bindInt64(1, iconID); 2009 2010 if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone) 2011 LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2012 2013 readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);"); 2014 m_deleteIconFromIconDataStatement->bindInt64(1, iconID); 2015 2016 if (m_deleteIconFromIconDataStatement->step() != SQLResultDone) 2017 LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2018 2019 m_deletePageURLsForIconURLStatement->reset(); 2020 m_deleteIconFromIconInfoStatement->reset(); 2021 m_deleteIconFromIconDataStatement->reset(); 2022} 2023 2024void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot) 2025{ 2026 ASSERT_ICON_SYNC_THREAD(); 2027 2028 if (snapshot.iconURL.isEmpty()) 2029 return; 2030 2031 // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out 2032 if (!snapshot.timestamp && !snapshot.data) { 2033 LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data()); 2034 removeIconFromSQLDatabase(snapshot.iconURL); 2035 return; 2036 } 2037 2038 // There would be a transaction here to make sure these removals are atomic 2039 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary 2040 2041 // Get the iconID for this url 2042 int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL); 2043 2044 // If there is already an iconID in place, update the database. 2045 // Otherwise, insert new records 2046 if (iconID) { 2047 readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;"); 2048 m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp); 2049 m_updateIconInfoStatement->bindText(2, snapshot.iconURL); 2050 m_updateIconInfoStatement->bindInt64(3, iconID); 2051 2052 if (m_updateIconInfoStatement->step() != SQLResultDone) 2053 LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2054 2055 m_updateIconInfoStatement->reset(); 2056 2057 readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;"); 2058 m_updateIconDataStatement->bindInt64(2, iconID); 2059 2060 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 2061 // signifying that this icon doesn't have any data 2062 if (snapshot.data && snapshot.data->size()) 2063 m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size()); 2064 else 2065 m_updateIconDataStatement->bindNull(1); 2066 2067 if (m_updateIconDataStatement->step() != SQLResultDone) 2068 LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2069 2070 m_updateIconDataStatement->reset(); 2071 } else { 2072 readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);"); 2073 m_setIconInfoStatement->bindText(1, snapshot.iconURL); 2074 m_setIconInfoStatement->bindInt64(2, snapshot.timestamp); 2075 2076 if (m_setIconInfoStatement->step() != SQLResultDone) 2077 LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2078 2079 m_setIconInfoStatement->reset(); 2080 2081 int64_t iconID = m_syncDB.lastInsertRowID(); 2082 2083 readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); 2084 m_setIconDataStatement->bindInt64(1, iconID); 2085 2086 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 2087 // signifying that this icon doesn't have any data 2088 if (snapshot.data && snapshot.data->size()) 2089 m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size()); 2090 else 2091 m_setIconDataStatement->bindNull(2); 2092 2093 if (m_setIconDataStatement->step() != SQLResultDone) 2094 LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2095 2096 m_setIconDataStatement->reset(); 2097 } 2098} 2099 2100bool IconDatabase::wasExcludedFromBackup() 2101{ 2102 ASSERT_ICON_SYNC_THREAD(); 2103 2104 return SQLiteStatement(m_syncDB, "SELECT value FROM IconDatabaseInfo WHERE key = 'ExcludedFromBackup';").getColumnInt(0); 2105} 2106 2107void IconDatabase::setWasExcludedFromBackup() 2108{ 2109 ASSERT_ICON_SYNC_THREAD(); 2110 2111 SQLiteStatement(m_syncDB, "INSERT INTO IconDatabaseInfo (key, value) VALUES ('ExcludedFromBackup', 1)").executeCommand(); 2112} 2113 2114} // namespace WebCore 2115 2116#endif // ENABLE(ICONDATABASE) 2117