IconDatabase.cpp revision cad810f21b803229eb11403f9209855525a25d57
1/* 2 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. 3 * Copyright (C) 2007 Justin Haygood (jhaygood@reaktix.com) 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include "config.h" 28#include "IconDatabase.h" 29 30#if ENABLE(ICONDATABASE) 31 32#include "AutodrainedPool.h" 33#include "DocumentLoader.h" 34#include "FileSystem.h" 35#include "IconDatabaseClient.h" 36#include "IconRecord.h" 37#include "IntSize.h" 38#include "Logging.h" 39#include "ScriptController.h" 40#include "SQLiteStatement.h" 41#include "SQLiteTransaction.h" 42#include "SuddenTermination.h" 43#include <wtf/CurrentTime.h> 44#include <wtf/MainThread.h> 45#include <wtf/StdLibExtras.h> 46#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 1060 // These four tables should always exist in a valid db 1061 if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo")) 1062 return false; 1063 1064 if (databaseVersionNumber(db) < currentDatabaseVersion) { 1065 LOG(IconDatabase, "DB version is not found or below expected valid version"); 1066 return false; 1067 } 1068 1069 return true; 1070} 1071 1072static void createDatabaseTables(SQLiteDatabase& db) 1073{ 1074 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) { 1075 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1076 db.close(); 1077 return; 1078 } 1079 if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) { 1080 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1081 db.close(); 1082 return; 1083 } 1084 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);")) { 1085 LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1086 db.close(); 1087 return; 1088 } 1089 if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) { 1090 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1091 db.close(); 1092 return; 1093 } 1094 if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) { 1095 LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1096 db.close(); 1097 return; 1098 } 1099 if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) { 1100 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1101 db.close(); 1102 return; 1103 } 1104 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { 1105 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1106 db.close(); 1107 return; 1108 } 1109 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) { 1110 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg()); 1111 db.close(); 1112 return; 1113 } 1114} 1115 1116void IconDatabase::performOpenInitialization() 1117{ 1118 ASSERT_ICON_SYNC_THREAD(); 1119 1120 if (!isOpen()) 1121 return; 1122 1123 if (checkIntegrityOnOpen) { 1124 checkIntegrityOnOpen = false; 1125 if (!checkIntegrity()) { 1126 LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase"); 1127 1128 m_syncDB.close(); 1129 1130 { 1131 MutexLocker locker(m_syncLock); 1132 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future; 1133 deleteFile(m_completeDatabasePath + "-journal"); 1134 deleteFile(m_completeDatabasePath); 1135 } 1136 1137 // Reopen the write database, creating it from scratch 1138 if (!m_syncDB.open(m_completeDatabasePath)) { 1139 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); 1140 return; 1141 } 1142 } 1143 } 1144 1145 int version = databaseVersionNumber(m_syncDB); 1146 1147 if (version > currentDatabaseVersion) { 1148 LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion); 1149 m_syncDB.close(); 1150 m_threadTerminationRequested = true; 1151 return; 1152 } 1153 1154 if (!isValidDatabase(m_syncDB)) { 1155 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data()); 1156 m_syncDB.clearAllTables(); 1157 createDatabaseTables(m_syncDB); 1158 } 1159 1160 // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill 1161 if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand()) 1162 LOG_ERROR("SQLite database could not set cache_size"); 1163} 1164 1165bool IconDatabase::checkIntegrity() 1166{ 1167 ASSERT_ICON_SYNC_THREAD(); 1168 1169 SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;"); 1170 if (integrity.prepare() != SQLResultOk) { 1171 LOG_ERROR("checkIntegrity failed to execute"); 1172 return false; 1173 } 1174 1175 int resultCode = integrity.step(); 1176 if (resultCode == SQLResultOk) 1177 return true; 1178 1179 if (resultCode != SQLResultRow) 1180 return false; 1181 1182 int columns = integrity.columnCount(); 1183 if (columns != 1) { 1184 LOG_ERROR("Received %i columns performing integrity check, should be 1", columns); 1185 return false; 1186 } 1187 1188 String resultText = integrity.getColumnText(0); 1189 1190 // A successful, no-error integrity check will be "ok" - all other strings imply failure 1191 if (resultText == "ok") 1192 return true; 1193 1194 LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data()); 1195 return false; 1196} 1197 1198void IconDatabase::performURLImport() 1199{ 1200 ASSERT_ICON_SYNC_THREAD(); 1201 1202 SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;"); 1203 1204 if (query.prepare() != SQLResultOk) { 1205 LOG_ERROR("Unable to prepare icon url import query"); 1206 return; 1207 } 1208 1209 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go 1210 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up 1211 AutodrainedPool pool(25); 1212 1213 int result = query.step(); 1214 while (result == SQLResultRow) { 1215 String pageURL = query.getColumnText(0); 1216 String iconURL = query.getColumnText(1); 1217 1218 { 1219 MutexLocker locker(m_urlAndIconLock); 1220 1221 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); 1222 1223 // If the pageRecord doesn't exist in this map, then no one has retained this pageURL 1224 // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner, 1225 // so go ahead and actually create a pageURLRecord for this url even though it's not retained. 1226 // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested 1227 // in - we'll prune it later instead! 1228 if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) { 1229 pageRecord = new PageURLRecord(pageURL); 1230 m_pageURLToRecordMap.set(pageURL, pageRecord); 1231 } 1232 1233 if (pageRecord) { 1234 IconRecord* currentIcon = pageRecord->iconRecord(); 1235 1236 if (!currentIcon || currentIcon->iconURL() != iconURL) { 1237 pageRecord->setIconRecord(getOrCreateIconRecord(iconURL)); 1238 currentIcon = pageRecord->iconRecord(); 1239 } 1240 1241 // 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 1242 // so we marked the timestamp as "now", but it's really much older 1243 currentIcon->setTimestamp(query.getColumnInt(2)); 1244 } 1245 } 1246 1247 // 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 1248 // 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 - 1249 // one for the URL and one for the Image itself 1250 // Note that WebIconDatabase is not neccessarily API so we might be able to make this change 1251 { 1252 MutexLocker locker(m_pendingReadingLock); 1253 if (m_pageURLsPendingImport.contains(pageURL)) { 1254 m_client->dispatchDidAddIconForPageURL(pageURL); 1255 m_pageURLsPendingImport.remove(pageURL); 1256 1257 pool.cycle(); 1258 } 1259 } 1260 1261 // Stop the import at any time of the thread has been asked to shutdown 1262 if (shouldStopThreadActivity()) { 1263 LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()"); 1264 return; 1265 } 1266 1267 result = query.step(); 1268 } 1269 1270 if (result != SQLResultDone) 1271 LOG(IconDatabase, "Error reading page->icon url mappings from database"); 1272 1273 // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not, 1274 // but after m_iconURLImportComplete is set to true, we don't care about this set anymore 1275 Vector<String> urls; 1276 { 1277 MutexLocker locker(m_pendingReadingLock); 1278 1279 urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end()); 1280 m_pageURLsPendingImport.clear(); 1281 m_iconURLImportComplete = true; 1282 } 1283 1284 Vector<String> urlsToNotify; 1285 1286 // Loop through the urls pending import 1287 // Remove unretained ones if database cleanup is allowed 1288 // Keep a set of ones that are retained and pending notification 1289 1290 { 1291 MutexLocker locker(m_urlAndIconLock); 1292 1293 for (unsigned i = 0; i < urls.size(); ++i) { 1294 if (!m_retainedPageURLs.contains(urls[i])) { 1295 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]); 1296 if (record && !databaseCleanupCounter) { 1297 m_pageURLToRecordMap.remove(urls[i]); 1298 IconRecord* iconRecord = record->iconRecord(); 1299 1300 // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother 1301 // reading anything related to it 1302 if (iconRecord && iconRecord->hasOneRef()) { 1303 m_iconURLToRecordMap.remove(iconRecord->iconURL()); 1304 1305 { 1306 MutexLocker locker(m_pendingReadingLock); 1307 m_pageURLsInterestedInIcons.remove(urls[i]); 1308 m_iconsPendingReading.remove(iconRecord); 1309 } 1310 { 1311 MutexLocker locker(m_pendingSyncLock); 1312 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true)); 1313 } 1314 } 1315 1316 delete record; 1317 } 1318 } else { 1319 urlsToNotify.append(urls[i]); 1320 } 1321 } 1322 } 1323 1324 LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size())); 1325 // Now that we don't hold any locks, perform the actual notifications 1326 for (unsigned i = 0; i < urlsToNotify.size(); ++i) { 1327 LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data()); 1328 m_client->dispatchDidAddIconForPageURL(urlsToNotify[i]); 1329 if (shouldStopThreadActivity()) 1330 return; 1331 1332 pool.cycle(); 1333 } 1334 1335 // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread 1336 callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this); 1337} 1338 1339void* IconDatabase::syncThreadMainLoop() 1340{ 1341 ASSERT_ICON_SYNC_THREAD(); 1342 1343 bool shouldReenableSuddenTermination = false; 1344 1345 m_syncLock.lock(); 1346 1347 // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup 1348 while (!m_threadTerminationRequested) { 1349 m_syncLock.unlock(); 1350 1351#ifndef NDEBUG 1352 double timeStamp = currentTime(); 1353#endif 1354 LOG(IconDatabase, "(THREAD) Main work loop starting"); 1355 1356 // 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 1357 if (m_removeIconsRequested) { 1358 removeAllIconsOnThread(); 1359 m_removeIconsRequested = false; 1360 } 1361 1362 // Then, if the thread should be quitting, quit now! 1363 if (m_threadTerminationRequested) 1364 break; 1365 1366 bool didAnyWork = true; 1367 while (didAnyWork) { 1368 bool didWrite = writeToDatabase(); 1369 if (shouldStopThreadActivity()) 1370 break; 1371 1372 didAnyWork = readFromDatabase(); 1373 if (shouldStopThreadActivity()) 1374 break; 1375 1376 // Prune unretained icons after the first time we sync anything out to the database 1377 // This way, pruning won't be the only operation we perform to the database by itself 1378 // We also don't want to bother doing this if the thread should be terminating (the user is quitting) 1379 // or if private browsing is enabled 1380 // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone 1381 // has asked to delay pruning 1382 static bool prunedUnretainedIcons = false; 1383 if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) { 1384#ifndef NDEBUG 1385 double time = currentTime(); 1386#endif 1387 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()"); 1388 1389 pruneUnretainedIcons(); 1390 1391 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time); 1392 1393 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay 1394 // to mark prunedUnretainedIcons true because we're about to terminate anyway 1395 prunedUnretainedIcons = true; 1396 } 1397 1398 didAnyWork = didAnyWork || didWrite; 1399 if (shouldStopThreadActivity()) 1400 break; 1401 } 1402 1403#ifndef NDEBUG 1404 double newstamp = currentTime(); 1405 LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not"); 1406#endif 1407 1408 m_syncLock.lock(); 1409 1410 // There is some condition that is asking us to stop what we're doing now and handle a special case 1411 // This is either removing all icons, or shutting down the thread to quit the app 1412 // We handle those at the top of this main loop so continue to jump back up there 1413 if (shouldStopThreadActivity()) 1414 continue; 1415 1416 if (shouldReenableSuddenTermination) { 1417 // The following is balanced by the call to disableSuddenTermination in the 1418 // wakeSyncThread function. Any time we wait on the condition, we also have 1419 // to enableSuddenTermation, after doing the next batch of work. 1420 ASSERT(m_disabledSuddenTerminationForSyncThread); 1421 enableSuddenTermination(); 1422 m_disabledSuddenTerminationForSyncThread = false; 1423 } 1424 1425 m_syncCondition.wait(m_syncLock); 1426 1427 shouldReenableSuddenTermination = true; 1428 } 1429 1430 m_syncLock.unlock(); 1431 1432 // Thread is terminating at this point 1433 cleanupSyncThread(); 1434 1435 if (shouldReenableSuddenTermination) { 1436 // The following is balanced by the call to disableSuddenTermination in the 1437 // wakeSyncThread function. Any time we wait on the condition, we also have 1438 // to enableSuddenTermation, after doing the next batch of work. 1439 ASSERT(m_disabledSuddenTerminationForSyncThread); 1440 enableSuddenTermination(); 1441 m_disabledSuddenTerminationForSyncThread = false; 1442 } 1443 1444 return 0; 1445} 1446 1447bool IconDatabase::readFromDatabase() 1448{ 1449 ASSERT_ICON_SYNC_THREAD(); 1450 1451#ifndef NDEBUG 1452 double timeStamp = currentTime(); 1453#endif 1454 1455 bool didAnyWork = false; 1456 1457 // 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 1458 // This way we won't hold the lock for a long period of time 1459 Vector<IconRecord*> icons; 1460 { 1461 MutexLocker locker(m_pendingReadingLock); 1462 icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end()); 1463 } 1464 1465 // Keep track of icons we actually read to notify them of the new icon 1466 HashSet<String> urlsToNotify; 1467 1468 for (unsigned i = 0; i < icons.size(); ++i) { 1469 didAnyWork = true; 1470 RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL()); 1471 1472 // Verify this icon still wants to be read from disk 1473 { 1474 MutexLocker urlLocker(m_urlAndIconLock); 1475 { 1476 MutexLocker readLocker(m_pendingReadingLock); 1477 1478 if (m_iconsPendingReading.contains(icons[i])) { 1479 // Set the new data 1480 icons[i]->setImageData(imageData.get()); 1481 1482 // Remove this icon from the set that needs to be read 1483 m_iconsPendingReading.remove(icons[i]); 1484 1485 // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon 1486 // We want to find the intersection of these two sets to notify them 1487 // Check the sizes of these two sets to minimize the number of iterations 1488 const HashSet<String>* outerHash; 1489 const HashSet<String>* innerHash; 1490 1491 if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) { 1492 outerHash = &m_pageURLsInterestedInIcons; 1493 innerHash = &(icons[i]->retainingPageURLs()); 1494 } else { 1495 innerHash = &m_pageURLsInterestedInIcons; 1496 outerHash = &(icons[i]->retainingPageURLs()); 1497 } 1498 1499 HashSet<String>::const_iterator iter = outerHash->begin(); 1500 HashSet<String>::const_iterator end = outerHash->end(); 1501 for (; iter != end; ++iter) { 1502 if (innerHash->contains(*iter)) { 1503 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()); 1504 urlsToNotify.add(*iter); 1505 } 1506 1507 // If we ever get to the point were we've seen every url interested in this icon, break early 1508 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) 1509 break; 1510 } 1511 1512 // 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 1513 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) 1514 m_pageURLsInterestedInIcons.clear(); 1515 else { 1516 iter = urlsToNotify.begin(); 1517 end = urlsToNotify.end(); 1518 for (; iter != end; ++iter) 1519 m_pageURLsInterestedInIcons.remove(*iter); 1520 } 1521 } 1522 } 1523 } 1524 1525 if (shouldStopThreadActivity()) 1526 return didAnyWork; 1527 1528 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go 1529 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up 1530 AutodrainedPool pool(25); 1531 1532 // Now that we don't hold any locks, perform the actual notifications 1533 HashSet<String>::iterator iter = urlsToNotify.begin(); 1534 HashSet<String>::iterator end = urlsToNotify.end(); 1535 for (unsigned iteration = 0; iter != end; ++iter, ++iteration) { 1536 LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data()); 1537 m_client->dispatchDidAddIconForPageURL(*iter); 1538 if (shouldStopThreadActivity()) 1539 return didAnyWork; 1540 1541 pool.cycle(); 1542 } 1543 1544 LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size()); 1545 urlsToNotify.clear(); 1546 1547 if (shouldStopThreadActivity()) 1548 return didAnyWork; 1549 } 1550 1551 LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp); 1552 1553 return didAnyWork; 1554} 1555 1556bool IconDatabase::writeToDatabase() 1557{ 1558 ASSERT_ICON_SYNC_THREAD(); 1559 1560#ifndef NDEBUG 1561 double timeStamp = currentTime(); 1562#endif 1563 1564 bool didAnyWork = false; 1565 1566 // We can copy the current work queue then clear it out - If any new work comes in while we're writing out, 1567 // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes 1568 // asked for by the database on the main thread 1569 Vector<IconSnapshot> iconSnapshots; 1570 Vector<PageURLSnapshot> pageSnapshots; 1571 { 1572 MutexLocker locker(m_pendingSyncLock); 1573 1574 iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values()); 1575 m_iconsPendingSync.clear(); 1576 1577 pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values()); 1578 m_pageURLsPendingSync.clear(); 1579 } 1580 1581 if (iconSnapshots.size() || pageSnapshots.size()) 1582 didAnyWork = true; 1583 1584 SQLiteTransaction syncTransaction(m_syncDB); 1585 syncTransaction.begin(); 1586 1587 for (unsigned i = 0; i < iconSnapshots.size(); ++i) { 1588 writeIconSnapshotToSQLDatabase(iconSnapshots[i]); 1589 LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp); 1590 } 1591 1592 for (unsigned i = 0; i < pageSnapshots.size(); ++i) { 1593 // If the icon URL is empty, this page is meant to be deleted 1594 // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't 1595 if (pageSnapshots[i].iconURL.isEmpty()) 1596 removePageURLFromSQLDatabase(pageSnapshots[i].pageURL); 1597 else 1598 setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL); 1599 LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data()); 1600 } 1601 1602 syncTransaction.commit(); 1603 1604 // 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 1605 if (didAnyWork) 1606 checkForDanglingPageURLs(false); 1607 1608 LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp); 1609 1610 return didAnyWork; 1611} 1612 1613void IconDatabase::pruneUnretainedIcons() 1614{ 1615 ASSERT_ICON_SYNC_THREAD(); 1616 1617 if (!isOpen()) 1618 return; 1619 1620 // This method should only be called once per run 1621 ASSERT(!m_initialPruningComplete); 1622 1623 // This method relies on having read in all page URLs from the database earlier. 1624 ASSERT(m_iconURLImportComplete); 1625 1626 // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set. 1627 Vector<int64_t> pageIDsToDelete; 1628 1629 SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;"); 1630 pageSQL.prepare(); 1631 1632 int result; 1633 while ((result = pageSQL.step()) == SQLResultRow) { 1634 MutexLocker locker(m_urlAndIconLock); 1635 if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1))) 1636 pageIDsToDelete.append(pageSQL.getColumnInt64(0)); 1637 } 1638 1639 if (result != SQLResultDone) 1640 LOG_ERROR("Error reading PageURL table from on-disk DB"); 1641 pageSQL.finalize(); 1642 1643 // Delete page URLs that were in the table, but not in our retain count set. 1644 size_t numToDelete = pageIDsToDelete.size(); 1645 if (numToDelete) { 1646 SQLiteTransaction pruningTransaction(m_syncDB); 1647 pruningTransaction.begin(); 1648 1649 SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);"); 1650 pageDeleteSQL.prepare(); 1651 for (size_t i = 0; i < numToDelete; ++i) { 1652#if OS(WINDOWS) 1653 LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); 1654#else 1655 LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i])); 1656#endif 1657 pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]); 1658 int result = pageDeleteSQL.step(); 1659 if (result != SQLResultDone) 1660#if OS(WINDOWS) 1661 LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); 1662#else 1663 LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i])); 1664#endif 1665 pageDeleteSQL.reset(); 1666 1667 // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can 1668 // finish the rest later (hopefully) 1669 if (shouldStopThreadActivity()) { 1670 pruningTransaction.commit(); 1671 return; 1672 } 1673 } 1674 pruningTransaction.commit(); 1675 pageDeleteSQL.finalize(); 1676 } 1677 1678 // Deleting unreferenced icons from the Icon tables has to be atomic - 1679 // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue 1680 // A user on a network home directory with a wildly inconsistent database might see quite a pause... 1681 1682 SQLiteTransaction pruningTransaction(m_syncDB); 1683 pruningTransaction.begin(); 1684 1685 // Wipe Icons that aren't retained 1686 if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) 1687 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table"); 1688 if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) 1689 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table"); 1690 1691 pruningTransaction.commit(); 1692 1693 checkForDanglingPageURLs(true); 1694 1695 m_initialPruningComplete = true; 1696} 1697 1698void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound) 1699{ 1700 ASSERT_ICON_SYNC_THREAD(); 1701 1702 // 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 1703 // entries. We also don't want to keep performing this check and reporting this error if it has already found danglers before so we 1704 // keep track of whether we've found any. We skip the check in the release build pretending to have already found danglers already. 1705#ifndef NDEBUG 1706 static bool danglersFound = true; 1707#else 1708 static bool danglersFound = false; 1709#endif 1710 1711 if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) { 1712 danglersFound = true; 1713 LOG(IconDatabase, "Dangling PageURL entries found"); 1714 if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);")) 1715 LOG(IconDatabase, "Unable to prune dangling PageURLs"); 1716 } 1717} 1718 1719void IconDatabase::removeAllIconsOnThread() 1720{ 1721 ASSERT_ICON_SYNC_THREAD(); 1722 1723 LOG(IconDatabase, "Removing all icons on the sync thread"); 1724 1725 // Delete all the prepared statements so they can start over 1726 deleteAllPreparedStatements(); 1727 1728 // To reset the on-disk database, we'll wipe all its tables then vacuum it 1729 // This is easier and safer than closing it, deleting the file, and recreating from scratch 1730 m_syncDB.clearAllTables(); 1731 m_syncDB.runVacuumCommand(); 1732 createDatabaseTables(m_syncDB); 1733 1734 LOG(IconDatabase, "Dispatching notification that we removed all icons"); 1735 m_client->dispatchDidRemoveAllIcons(); 1736} 1737 1738void IconDatabase::deleteAllPreparedStatements() 1739{ 1740 ASSERT_ICON_SYNC_THREAD(); 1741 1742 m_setIconIDForPageURLStatement.clear(); 1743 m_removePageURLStatement.clear(); 1744 m_getIconIDForIconURLStatement.clear(); 1745 m_getImageDataForIconURLStatement.clear(); 1746 m_addIconToIconInfoStatement.clear(); 1747 m_addIconToIconDataStatement.clear(); 1748 m_getImageDataStatement.clear(); 1749 m_deletePageURLsForIconURLStatement.clear(); 1750 m_deleteIconFromIconInfoStatement.clear(); 1751 m_deleteIconFromIconDataStatement.clear(); 1752 m_updateIconInfoStatement.clear(); 1753 m_updateIconDataStatement.clear(); 1754 m_setIconInfoStatement.clear(); 1755 m_setIconDataStatement.clear(); 1756} 1757 1758void* IconDatabase::cleanupSyncThread() 1759{ 1760 ASSERT_ICON_SYNC_THREAD(); 1761 1762#ifndef NDEBUG 1763 double timeStamp = currentTime(); 1764#endif 1765 1766 // If the removeIcons flag is set, remove all icons from the db. 1767 if (m_removeIconsRequested) 1768 removeAllIconsOnThread(); 1769 1770 // Sync remaining icons out 1771 LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread"); 1772 writeToDatabase(); 1773 1774 // Close the database 1775 MutexLocker locker(m_syncLock); 1776 1777 m_databaseDirectory = String(); 1778 m_completeDatabasePath = String(); 1779 deleteAllPreparedStatements(); 1780 m_syncDB.close(); 1781 1782#ifndef NDEBUG 1783 LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp); 1784#endif 1785 1786 m_syncThreadRunning = false; 1787 return 0; 1788} 1789 1790bool IconDatabase::imported() 1791{ 1792 ASSERT_ICON_SYNC_THREAD(); 1793 1794 if (m_isImportedSet) 1795 return m_imported; 1796 1797 SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";"); 1798 if (query.prepare() != SQLResultOk) { 1799 LOG_ERROR("Unable to prepare imported statement"); 1800 return false; 1801 } 1802 1803 int result = query.step(); 1804 if (result == SQLResultRow) 1805 result = query.getColumnInt(0); 1806 else { 1807 if (result != SQLResultDone) 1808 LOG_ERROR("imported statement failed"); 1809 result = 0; 1810 } 1811 1812 m_isImportedSet = true; 1813 return m_imported = result; 1814} 1815 1816void IconDatabase::setImported(bool import) 1817{ 1818 ASSERT_ICON_SYNC_THREAD(); 1819 1820 m_imported = import; 1821 m_isImportedSet = true; 1822 1823 String queryString = import ? 1824 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" : 1825 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);"; 1826 1827 SQLiteStatement query(m_syncDB, queryString); 1828 1829 if (query.prepare() != SQLResultOk) { 1830 LOG_ERROR("Unable to prepare set imported statement"); 1831 return; 1832 } 1833 1834 if (query.step() != SQLResultDone) 1835 LOG_ERROR("set imported statement failed"); 1836} 1837 1838// readySQLiteStatement() handles two things 1839// 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user 1840// switches to and from private browsing 1841// 2 - Lazy construction of the Statement in the first place, in case we've never made this query before 1842inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str) 1843{ 1844 if (statement && (statement->database() != &db || statement->isExpired())) { 1845 if (statement->isExpired()) 1846 LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data()); 1847 statement.set(0); 1848 } 1849 if (!statement) { 1850 statement = adoptPtr(new SQLiteStatement(db, str)); 1851 if (statement->prepare() != SQLResultOk) 1852 LOG_ERROR("Preparing statement %s failed", str.ascii().data()); 1853 } 1854} 1855 1856void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL) 1857{ 1858 ASSERT_ICON_SYNC_THREAD(); 1859 1860 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); 1861 1862 if (!iconID) 1863 iconID = addIconURLToSQLDatabase(iconURL); 1864 1865 if (!iconID) { 1866 LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data()); 1867 ASSERT(false); 1868 return; 1869 } 1870 1871 setIconIDForPageURLInSQLDatabase(iconID, pageURL); 1872} 1873 1874void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL) 1875{ 1876 ASSERT_ICON_SYNC_THREAD(); 1877 1878 readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);"); 1879 m_setIconIDForPageURLStatement->bindText(1, pageURL); 1880 m_setIconIDForPageURLStatement->bindInt64(2, iconID); 1881 1882 int result = m_setIconIDForPageURLStatement->step(); 1883 if (result != SQLResultDone) { 1884 ASSERT(false); 1885 LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data()); 1886 } 1887 1888 m_setIconIDForPageURLStatement->reset(); 1889} 1890 1891void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL) 1892{ 1893 ASSERT_ICON_SYNC_THREAD(); 1894 1895 readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);"); 1896 m_removePageURLStatement->bindText(1, pageURL); 1897 1898 if (m_removePageURLStatement->step() != SQLResultDone) 1899 LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data()); 1900 1901 m_removePageURLStatement->reset(); 1902} 1903 1904 1905int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL) 1906{ 1907 ASSERT_ICON_SYNC_THREAD(); 1908 1909 readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);"); 1910 m_getIconIDForIconURLStatement->bindText(1, iconURL); 1911 1912 int64_t result = m_getIconIDForIconURLStatement->step(); 1913 if (result == SQLResultRow) 1914 result = m_getIconIDForIconURLStatement->getColumnInt64(0); 1915 else { 1916 if (result != SQLResultDone) 1917 LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); 1918 result = 0; 1919 } 1920 1921 m_getIconIDForIconURLStatement->reset(); 1922 return result; 1923} 1924 1925int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL) 1926{ 1927 ASSERT_ICON_SYNC_THREAD(); 1928 1929 // There would be a transaction here to make sure these two inserts are atomic 1930 // In practice the only caller of this method is always wrapped in a transaction itself so placing another 1931 // here is unnecessary 1932 1933 readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);"); 1934 m_addIconToIconInfoStatement->bindText(1, iconURL); 1935 1936 int result = m_addIconToIconInfoStatement->step(); 1937 m_addIconToIconInfoStatement->reset(); 1938 if (result != SQLResultDone) { 1939 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data()); 1940 return 0; 1941 } 1942 int64_t iconID = m_syncDB.lastInsertRowID(); 1943 1944 readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); 1945 m_addIconToIconDataStatement->bindInt64(1, iconID); 1946 1947 result = m_addIconToIconDataStatement->step(); 1948 m_addIconToIconDataStatement->reset(); 1949 if (result != SQLResultDone) { 1950 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data()); 1951 return 0; 1952 } 1953 1954 return iconID; 1955} 1956 1957PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL) 1958{ 1959 ASSERT_ICON_SYNC_THREAD(); 1960 1961 RefPtr<SharedBuffer> imageData; 1962 1963 readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));"); 1964 m_getImageDataForIconURLStatement->bindText(1, iconURL); 1965 1966 int result = m_getImageDataForIconURLStatement->step(); 1967 if (result == SQLResultRow) { 1968 Vector<char> data; 1969 m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data); 1970 imageData = SharedBuffer::create(data.data(), data.size()); 1971 } else if (result != SQLResultDone) 1972 LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); 1973 1974 m_getImageDataForIconURLStatement->reset(); 1975 1976 return imageData.release(); 1977} 1978 1979void IconDatabase::removeIconFromSQLDatabase(const String& iconURL) 1980{ 1981 ASSERT_ICON_SYNC_THREAD(); 1982 1983 if (iconURL.isEmpty()) 1984 return; 1985 1986 // There would be a transaction here to make sure these removals are atomic 1987 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary 1988 1989 // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the 1990 // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return 1991 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); 1992 if (!iconID) 1993 return; 1994 1995 readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);"); 1996 m_deletePageURLsForIconURLStatement->bindInt64(1, iconID); 1997 1998 if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone) 1999 LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2000 2001 readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);"); 2002 m_deleteIconFromIconInfoStatement->bindInt64(1, iconID); 2003 2004 if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone) 2005 LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2006 2007 readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);"); 2008 m_deleteIconFromIconDataStatement->bindInt64(1, iconID); 2009 2010 if (m_deleteIconFromIconDataStatement->step() != SQLResultDone) 2011 LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2012 2013 m_deletePageURLsForIconURLStatement->reset(); 2014 m_deleteIconFromIconInfoStatement->reset(); 2015 m_deleteIconFromIconDataStatement->reset(); 2016} 2017 2018void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot) 2019{ 2020 ASSERT_ICON_SYNC_THREAD(); 2021 2022 if (snapshot.iconURL.isEmpty()) 2023 return; 2024 2025 // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out 2026 if (!snapshot.timestamp && !snapshot.data) { 2027 LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data()); 2028 removeIconFromSQLDatabase(snapshot.iconURL); 2029 return; 2030 } 2031 2032 // There would be a transaction here to make sure these removals are atomic 2033 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary 2034 2035 // Get the iconID for this url 2036 int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL); 2037 2038 // If there is already an iconID in place, update the database. 2039 // Otherwise, insert new records 2040 if (iconID) { 2041 readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;"); 2042 m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp); 2043 m_updateIconInfoStatement->bindText(2, snapshot.iconURL); 2044 m_updateIconInfoStatement->bindInt64(3, iconID); 2045 2046 if (m_updateIconInfoStatement->step() != SQLResultDone) 2047 LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2048 2049 m_updateIconInfoStatement->reset(); 2050 2051 readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;"); 2052 m_updateIconDataStatement->bindInt64(2, iconID); 2053 2054 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 2055 // signifying that this icon doesn't have any data 2056 if (snapshot.data && snapshot.data->size()) 2057 m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size()); 2058 else 2059 m_updateIconDataStatement->bindNull(1); 2060 2061 if (m_updateIconDataStatement->step() != SQLResultDone) 2062 LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2063 2064 m_updateIconDataStatement->reset(); 2065 } else { 2066 readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);"); 2067 m_setIconInfoStatement->bindText(1, snapshot.iconURL); 2068 m_setIconInfoStatement->bindInt64(2, snapshot.timestamp); 2069 2070 if (m_setIconInfoStatement->step() != SQLResultDone) 2071 LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2072 2073 m_setIconInfoStatement->reset(); 2074 2075 int64_t iconID = m_syncDB.lastInsertRowID(); 2076 2077 readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); 2078 m_setIconDataStatement->bindInt64(1, iconID); 2079 2080 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 2081 // signifying that this icon doesn't have any data 2082 if (snapshot.data && snapshot.data->size()) 2083 m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size()); 2084 else 2085 m_setIconDataStatement->bindNull(2); 2086 2087 if (m_setIconDataStatement->step() != SQLResultDone) 2088 LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2089 2090 m_setIconDataStatement->reset(); 2091 } 2092} 2093 2094} // namespace WebCore 2095 2096#endif // ENABLE(ICONDATABASE) 2097