IconDatabase.cpp revision 2bde8e466a4451c7319e3a072d118917957d6554
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 int databaseCleanupCounter = 0; 62 63// This version number is in the DB and marks the current generation of the schema 64// Currently, a mismatched schema causes the DB to be wiped and reset. This isn't 65// so bad during development but in the future, we would need to write a conversion 66// function to advance older released schemas to "current" 67static const int currentDatabaseVersion = 6; 68 69// Icons expire once every 4 days 70static const int iconExpirationTime = 60*60*24*4; 71 72static const int updateTimerDelay = 5; 73 74static bool checkIntegrityOnOpen = false; 75 76#ifndef NDEBUG 77static String urlForLogging(const String& url) 78{ 79 static unsigned urlTruncationLength = 120; 80 81 if (url.length() < urlTruncationLength) 82 return url; 83 return url.substring(0, urlTruncationLength) + "..."; 84} 85#endif 86 87class DefaultIconDatabaseClient : public IconDatabaseClient { 88public: 89 virtual bool performImport() { return true; } 90 virtual void didImportIconURLForPageURL(const String&) { } 91 virtual void didImportIconDataForPageURL(const String&) { } 92 virtual void didChangeIconForPageURL(const String&) { } 93 virtual void didRemoveAllIcons() { } 94 virtual void didFinishURLImport() { } 95}; 96 97static IconDatabaseClient* defaultClient() 98{ 99 static IconDatabaseClient* defaultClient = new DefaultIconDatabaseClient(); 100 return defaultClient; 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& directory, const String& filename) 121{ 122 ASSERT_NOT_SYNC_THREAD(); 123 124 if (!m_isEnabled) 125 return false; 126 127 if (isOpen()) { 128 LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first."); 129 return false; 130 } 131 132 m_databaseDirectory = directory.crossThreadString(); 133 134 // Formulate the full path for the database file 135 m_completeDatabasePath = pathByAppendingComponent(m_databaseDirectory, filename); 136 137 // Lock here as well as first thing in the thread so the thread doesn't actually commence until the createThread() call 138 // completes and m_syncThreadRunning is properly set 139 m_syncLock.lock(); 140 m_syncThread = createThread(IconDatabase::iconDatabaseSyncThreadStart, this, "WebCore: IconDatabase"); 141 m_syncThreadRunning = m_syncThread; 142 m_syncLock.unlock(); 143 if (!m_syncThread) 144 return false; 145 return true; 146} 147 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::synchronousIconForPageURL(const String& pageURLOriginal, const IntSize& size) 221{ 222 ASSERT_NOT_SYNC_THREAD(); 223 224 // pageURLOriginal cannot be stored without being deep copied first. 225 // We should go our of our way to only copy it if we have to store it 226 227 if (!isOpen() || pageURLOriginal.isEmpty()) 228 return defaultIcon(size); 229 230 MutexLocker locker(m_urlAndIconLock); 231 232 String pageURLCopy; // Creates a null string for easy testing 233 234 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal); 235 if (!pageRecord) { 236 pageURLCopy = pageURLOriginal.crossThreadString(); 237 pageRecord = getOrCreatePageURLRecord(pageURLCopy); 238 } 239 240 // If pageRecord is NULL, one of two things is true - 241 // 1 - The initial url import is incomplete and this pageURL was marked to be notified once it is complete if an iconURL exists 242 // 2 - The initial url import IS complete and this pageURL has no icon 243 if (!pageRecord) { 244 MutexLocker locker(m_pendingReadingLock); 245 246 // Import is ongoing, there might be an icon. In this case, register to be notified when the icon comes in 247 // If we ever reach this condition, we know we've already made the pageURL copy 248 if (!m_iconURLImportComplete) 249 m_pageURLsInterestedInIcons.add(pageURLCopy); 250 251 return 0; 252 } 253 254 IconRecord* iconRecord = pageRecord->iconRecord(); 255 256 // If the initial URL import isn't complete, it's possible to have a PageURL record without an associated icon 257 // In this case, the pageURL is already in the set to alert the client when the iconURL mapping is complete so 258 // we can just bail now 259 if (!m_iconURLImportComplete && !iconRecord) 260 return 0; 261 262 // The only way we should *not* have an icon record is if this pageURL is retained but has no icon yet - make sure of that 263 ASSERT(iconRecord || m_retainedPageURLs.contains(pageURLOriginal)); 264 265 if (!iconRecord) 266 return 0; 267 268 // If it's a new IconRecord object that doesn't have its imageData set yet, 269 // mark it to be read by the background thread 270 if (iconRecord->imageDataStatus() == ImageDataStatusUnknown) { 271 if (pageURLCopy.isNull()) 272 pageURLCopy = pageURLOriginal.crossThreadString(); 273 274 MutexLocker locker(m_pendingReadingLock); 275 m_pageURLsInterestedInIcons.add(pageURLCopy); 276 m_iconsPendingReading.add(iconRecord); 277 wakeSyncThread(); 278 return 0; 279 } 280 281 // If the size parameter was (0, 0) that means the caller of this method just wanted the read from disk to be kicked off 282 // and isn't actually interested in the image return value 283 if (size == IntSize(0, 0)) 284 return 0; 285 286 // PARANOID DISCUSSION: This method makes some assumptions. It returns a WebCore::image which the icon database might dispose of at anytime in the future, 287 // and Images aren't ref counted. So there is no way for the client to guarantee continued existence of the image. 288 // This has *always* been the case, but in practice clients would always create some other platform specific representation of the image 289 // and drop the raw Image*. On Mac an NSImage, and on windows drawing into an HBITMAP. 290 // The async aspect adds a huge question - what if the image is deleted before the platform specific API has a chance to create its own 291 // representation out of it? 292 // If an image is read in from the icondatabase, we do *not* overwrite any image data that exists in the in-memory cache. 293 // This is because we make the assumption that anything in memory is newer than whatever is in the database. 294 // So the only time the data will be set from the second thread is when it is INITIALLY being read in from the database, but we would never 295 // delete the image on the secondary thread if the image already exists. 296 return iconRecord->image(size); 297} 298 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 synchronousIconForPageURL(pageURL, IntSize(0, 0)); 305} 306 307String IconDatabase::synchronousIconURLForPageURL(const String& pageURLOriginal) 308{ 309 ASSERT_NOT_SYNC_THREAD(); 310 311 // Cannot do anything with pageURLOriginal that would end up storing it without deep copying first 312 // Also, in the case we have a real answer for the caller, we must deep copy that as well 313 314 if (!isOpen() || pageURLOriginal.isEmpty()) 315 return String(); 316 317 MutexLocker locker(m_urlAndIconLock); 318 319 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURLOriginal); 320 if (!pageRecord) 321 pageRecord = getOrCreatePageURLRecord(pageURLOriginal.crossThreadString()); 322 323 // If pageRecord is NULL, one of two things is true - 324 // 1 - The initial url import is incomplete and this pageURL has already been marked to be notified once it is complete if an iconURL exists 325 // 2 - The initial url import IS complete and this pageURL has no icon 326 if (!pageRecord) 327 return String(); 328 329 // Possible the pageRecord is around because it's a retained pageURL with no iconURL, so we have to check 330 return pageRecord->iconRecord() ? pageRecord->iconRecord()->iconURL().threadsafeCopy() : String(); 331} 332 333#ifdef CAN_THEME_URL_ICON 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->didChangeIconForPageURL(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->didChangeIconForPageURL(pageURL); 641 } 642} 643 644IconLoadDecision IconDatabase::synchronousLoadDecisionForIconURL(const String& iconURL, DocumentLoader* notificationDocumentLoader) 645{ 646 ASSERT_NOT_SYNC_THREAD(); 647 648 if (!isOpen() || iconURL.isEmpty()) 649 return IconLoadNo; 650 651 // If we have a IconRecord, it should also have its timeStamp marked because there is only two times when we create the IconRecord: 652 // 1 - When we read the icon urls from disk, getting the timeStamp at the same time 653 // 2 - When we get a new icon from the loader, in which case the timestamp is set at that time 654 { 655 MutexLocker locker(m_urlAndIconLock); 656 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) { 657 LOG(IconDatabase, "Found expiration time on a present icon based on existing IconRecord"); 658 return (int)currentTime() - icon->getTimestamp() > iconExpirationTime ? IconLoadYes : IconLoadNo; 659 } 660 } 661 662 // If we don't have a record for it, but we *have* imported all iconURLs from disk, then we should load it now 663 MutexLocker readingLocker(m_pendingReadingLock); 664 if (m_iconURLImportComplete) 665 return IconLoadYes; 666 667 // Otherwise - since we refuse to perform I/O on the main thread to find out for sure - we return the answer that says 668 // "You might be asked to load this later, so flag that" 669 LOG(IconDatabase, "Don't know if we should load %s or not - adding %p to the set of document loaders waiting on a decision", iconURL.ascii().data(), notificationDocumentLoader); 670 if (notificationDocumentLoader) 671 m_loadersPendingDecision.add(notificationDocumentLoader); 672 673 return IconLoadUnknown; 674} 675 676bool IconDatabase::synchronousIconDataKnownForIconURL(const String& iconURL) 677{ 678 ASSERT_NOT_SYNC_THREAD(); 679 680 MutexLocker locker(m_urlAndIconLock); 681 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) 682 return icon->imageDataStatus() != ImageDataStatusUnknown; 683 684 return false; 685} 686 687void IconDatabase::setEnabled(bool enabled) 688{ 689 ASSERT_NOT_SYNC_THREAD(); 690 691 if (!enabled && isOpen()) 692 close(); 693 m_isEnabled = enabled; 694} 695 696bool IconDatabase::isEnabled() const 697{ 698 ASSERT_NOT_SYNC_THREAD(); 699 700 return m_isEnabled; 701} 702 703void IconDatabase::setPrivateBrowsingEnabled(bool flag) 704{ 705 m_privateBrowsingEnabled = flag; 706} 707 708bool IconDatabase::isPrivateBrowsingEnabled() const 709{ 710 return m_privateBrowsingEnabled; 711} 712 713void IconDatabase::delayDatabaseCleanup() 714{ 715 ++databaseCleanupCounter; 716 if (databaseCleanupCounter == 1) 717 LOG(IconDatabase, "Database cleanup is now DISABLED"); 718} 719 720void IconDatabase::allowDatabaseCleanup() 721{ 722 if (--databaseCleanupCounter < 0) 723 databaseCleanupCounter = 0; 724 if (databaseCleanupCounter == 0) 725 LOG(IconDatabase, "Database cleanup is now ENABLED"); 726} 727 728void IconDatabase::checkIntegrityBeforeOpening() 729{ 730 checkIntegrityOnOpen = true; 731} 732 733size_t IconDatabase::pageURLMappingCount() 734{ 735 MutexLocker locker(m_urlAndIconLock); 736 return m_pageURLToRecordMap.size(); 737} 738 739size_t IconDatabase::retainedPageURLCount() 740{ 741 MutexLocker locker(m_urlAndIconLock); 742 return m_retainedPageURLs.size(); 743} 744 745size_t IconDatabase::iconRecordCount() 746{ 747 MutexLocker locker(m_urlAndIconLock); 748 return m_iconURLToRecordMap.size(); 749} 750 751size_t IconDatabase::iconRecordCountWithData() 752{ 753 MutexLocker locker(m_urlAndIconLock); 754 size_t result = 0; 755 756 HashMap<String, IconRecord*>::iterator i = m_iconURLToRecordMap.begin(); 757 HashMap<String, IconRecord*>::iterator end = m_iconURLToRecordMap.end(); 758 759 for (; i != end; ++i) 760 result += ((*i).second->imageDataStatus() == ImageDataStatusPresent); 761 762 return result; 763} 764 765IconDatabase::IconDatabase() 766 : m_syncTimer(this, &IconDatabase::syncTimerFired) 767 , m_syncThreadRunning(false) 768 , m_isEnabled(false) 769 , m_privateBrowsingEnabled(false) 770 , m_threadTerminationRequested(false) 771 , m_removeIconsRequested(false) 772 , m_iconURLImportComplete(false) 773 , m_disabledSuddenTerminationForSyncThread(false) 774 , m_initialPruningComplete(false) 775 , m_client(defaultClient()) 776 , m_imported(false) 777 , m_isImportedSet(false) 778{ 779 LOG(IconDatabase, "Creating IconDatabase %p", this); 780 ASSERT(isMainThread()); 781} 782 783IconDatabase::~IconDatabase() 784{ 785 ASSERT_NOT_REACHED(); 786} 787 788void IconDatabase::notifyPendingLoadDecisionsOnMainThread(void* context) 789{ 790 static_cast<IconDatabase*>(context)->notifyPendingLoadDecisions(); 791} 792 793void IconDatabase::notifyPendingLoadDecisions() 794{ 795 ASSERT_NOT_SYNC_THREAD(); 796 797 // This method should only be called upon completion of the initial url import from the database 798 ASSERT(m_iconURLImportComplete); 799 LOG(IconDatabase, "Notifying all DocumentLoaders that were waiting on a load decision for thier icons"); 800 801 HashSet<RefPtr<DocumentLoader> >::iterator i = m_loadersPendingDecision.begin(); 802 HashSet<RefPtr<DocumentLoader> >::iterator end = m_loadersPendingDecision.end(); 803 804 for (; i != end; ++i) 805 if ((*i)->refCount() > 1) 806 (*i)->iconLoadDecisionAvailable(); 807 808 m_loadersPendingDecision.clear(); 809} 810 811void IconDatabase::wakeSyncThread() 812{ 813 MutexLocker locker(m_syncLock); 814 815 if (!m_disabledSuddenTerminationForSyncThread) { 816 m_disabledSuddenTerminationForSyncThread = true; 817 // The following is balanced by the call to enableSuddenTermination in the 818 // syncThreadMainLoop function. 819 // FIXME: It would be better to only disable sudden termination if we have 820 // something to write, not just if we have something to read. 821 disableSuddenTermination(); 822 } 823 824 m_syncCondition.signal(); 825} 826 827void IconDatabase::scheduleOrDeferSyncTimer() 828{ 829 ASSERT_NOT_SYNC_THREAD(); 830 831 if (!m_syncTimer.isActive()) { 832 // The following is balanced by the call to enableSuddenTermination in the 833 // syncTimerFired function. 834 disableSuddenTermination(); 835 } 836 837 m_syncTimer.startOneShot(updateTimerDelay); 838} 839 840void IconDatabase::syncTimerFired(Timer<IconDatabase>*) 841{ 842 ASSERT_NOT_SYNC_THREAD(); 843 wakeSyncThread(); 844 845 // The following is balanced by the call to disableSuddenTermination in the 846 // scheduleOrDeferSyncTimer function. 847 enableSuddenTermination(); 848} 849 850// ****************** 851// *** Any Thread *** 852// ****************** 853 854bool IconDatabase::isOpen() const 855{ 856 MutexLocker locker(m_syncLock); 857 return m_syncDB.isOpen(); 858} 859 860String IconDatabase::databasePath() const 861{ 862 MutexLocker locker(m_syncLock); 863 return m_completeDatabasePath.threadsafeCopy(); 864} 865 866String IconDatabase::defaultDatabaseFilename() 867{ 868 DEFINE_STATIC_LOCAL(String, defaultDatabaseFilename, ("WebpageIcons.db")); 869 return defaultDatabaseFilename.threadsafeCopy(); 870} 871 872// Unlike getOrCreatePageURLRecord(), getOrCreateIconRecord() does not mark the icon as "interested in import" 873PassRefPtr<IconRecord> IconDatabase::getOrCreateIconRecord(const String& iconURL) 874{ 875 // Clients of getOrCreateIconRecord() are required to acquire the m_urlAndIconLock before calling this method 876 ASSERT(!m_urlAndIconLock.tryLock()); 877 878 if (IconRecord* icon = m_iconURLToRecordMap.get(iconURL)) 879 return icon; 880 881 RefPtr<IconRecord> newIcon = IconRecord::create(iconURL); 882 m_iconURLToRecordMap.set(iconURL, newIcon.get()); 883 884 return newIcon.release(); 885} 886 887// This method retrieves the existing PageURLRecord, or creates a new one and marks it as "interested in the import" for later notification 888PageURLRecord* IconDatabase::getOrCreatePageURLRecord(const String& pageURL) 889{ 890 // Clients of getOrCreatePageURLRecord() are required to acquire the m_urlAndIconLock before calling this method 891 ASSERT(!m_urlAndIconLock.tryLock()); 892 893 if (pageURL.isEmpty()) 894 return 0; 895 896 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); 897 898 MutexLocker locker(m_pendingReadingLock); 899 if (!m_iconURLImportComplete) { 900 // If the initial import of all URLs hasn't completed and we have no page record, we assume we *might* know about this later and create a record for it 901 if (!pageRecord) { 902 LOG(IconDatabase, "Creating new PageURLRecord for pageURL %s", urlForLogging(pageURL).ascii().data()); 903 pageRecord = new PageURLRecord(pageURL); 904 m_pageURLToRecordMap.set(pageURL, pageRecord); 905 } 906 907 // If the pageRecord for this page does not have an iconRecord attached to it, then it is a new pageRecord still awaiting the initial import 908 // Mark the URL as "interested in the result of the import" then bail 909 if (!pageRecord->iconRecord()) { 910 m_pageURLsPendingImport.add(pageURL); 911 return 0; 912 } 913 } 914 915 // We've done the initial import of all URLs known in the database. If this record doesn't exist now, it never will 916 return pageRecord; 917} 918 919 920// ************************ 921// *** Sync Thread Only *** 922// ************************ 923 924void IconDatabase::importIconURLForPageURL(const String& iconURL, const String& pageURL) 925{ 926 ASSERT_ICON_SYNC_THREAD(); 927 928 // This function is only for setting actual existing url mappings so assert that neither of these URLs are empty 929 ASSERT(!iconURL.isEmpty() && !pageURL.isEmpty()); 930 931 setIconURLForPageURLInSQLDatabase(iconURL, pageURL); 932} 933 934void IconDatabase::importIconDataForIconURL(PassRefPtr<SharedBuffer> data, const String& iconURL) 935{ 936 ASSERT_ICON_SYNC_THREAD(); 937 938 ASSERT(!iconURL.isEmpty()); 939 940 writeIconSnapshotToSQLDatabase(IconSnapshot(iconURL, (int)currentTime(), data.get())); 941} 942 943bool IconDatabase::shouldStopThreadActivity() const 944{ 945 ASSERT_ICON_SYNC_THREAD(); 946 947 return m_threadTerminationRequested || m_removeIconsRequested; 948} 949 950void* IconDatabase::iconDatabaseSyncThreadStart(void* vIconDatabase) 951{ 952 IconDatabase* iconDB = static_cast<IconDatabase*>(vIconDatabase); 953 954 return iconDB->iconDatabaseSyncThread(); 955} 956 957void* IconDatabase::iconDatabaseSyncThread() 958{ 959 // The call to create this thread might not complete before the thread actually starts, so we might fail this ASSERT_ICON_SYNC_THREAD() because the pointer 960 // to our thread structure hasn't been filled in yet. 961 // To fix this, the main thread acquires this lock before creating us, then releases the lock after creation is complete. A quick lock/unlock cycle here will 962 // prevent us from running before that call completes 963 m_syncLock.lock(); 964 m_syncLock.unlock(); 965 966 ASSERT_ICON_SYNC_THREAD(); 967 968 LOG(IconDatabase, "(THREAD) IconDatabase sync thread started"); 969 970#ifndef NDEBUG 971 double startTime = currentTime(); 972#endif 973 974 // Need to create the database path if it doesn't already exist 975 makeAllDirectories(m_databaseDirectory); 976 977 // Existence of a journal file is evidence of a previous crash/force quit and automatically qualifies 978 // us to do an integrity check 979 String journalFilename = m_completeDatabasePath + "-journal"; 980 if (!checkIntegrityOnOpen) { 981 AutodrainedPool pool; 982 checkIntegrityOnOpen = fileExists(journalFilename); 983 } 984 985 { 986 MutexLocker locker(m_syncLock); 987 if (!m_syncDB.open(m_completeDatabasePath)) { 988 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); 989 return 0; 990 } 991 } 992 993 if (shouldStopThreadActivity()) 994 return syncThreadMainLoop(); 995 996#ifndef NDEBUG 997 double timeStamp = currentTime(); 998 LOG(IconDatabase, "(THREAD) Open took %.4f seconds", timeStamp - startTime); 999#endif 1000 1001 performOpenInitialization(); 1002 if (shouldStopThreadActivity()) 1003 return syncThreadMainLoop(); 1004 1005#ifndef NDEBUG 1006 double newStamp = currentTime(); 1007 LOG(IconDatabase, "(THREAD) performOpenInitialization() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); 1008 timeStamp = newStamp; 1009#endif 1010 1011 if (!imported()) { 1012 LOG(IconDatabase, "(THREAD) Performing Safari2 import procedure"); 1013 SQLiteTransaction importTransaction(m_syncDB); 1014 importTransaction.begin(); 1015 1016 // Commit the transaction only if the import completes (the import should be atomic) 1017 if (m_client->performImport()) { 1018 setImported(true); 1019 importTransaction.commit(); 1020 } else { 1021 LOG(IconDatabase, "(THREAD) Safari 2 import was cancelled"); 1022 importTransaction.rollback(); 1023 } 1024 1025 if (shouldStopThreadActivity()) 1026 return syncThreadMainLoop(); 1027 1028#ifndef NDEBUG 1029 newStamp = currentTime(); 1030 LOG(IconDatabase, "(THREAD) performImport() took %.4f seconds, now %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); 1031 timeStamp = newStamp; 1032#endif 1033 } 1034 1035 // Uncomment the following line to simulate a long lasting URL import (*HUGE* icon databases, or network home directories) 1036 // while (currentTime() - timeStamp < 10); 1037 1038 // Read in URL mappings from the database 1039 LOG(IconDatabase, "(THREAD) Starting iconURL import"); 1040 performURLImport(); 1041 1042 if (shouldStopThreadActivity()) 1043 return syncThreadMainLoop(); 1044 1045#ifndef NDEBUG 1046 newStamp = currentTime(); 1047 LOG(IconDatabase, "(THREAD) performURLImport() took %.4f seconds. Entering main loop %.4f seconds from thread start", newStamp - timeStamp, newStamp - startTime); 1048#endif 1049 1050 LOG(IconDatabase, "(THREAD) Beginning sync"); 1051 return syncThreadMainLoop(); 1052} 1053 1054static int databaseVersionNumber(SQLiteDatabase& db) 1055{ 1056 return SQLiteStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0); 1057} 1058 1059static bool isValidDatabase(SQLiteDatabase& db) 1060{ 1061 // These four tables should always exist in a valid db 1062 if (!db.tableExists("IconInfo") || !db.tableExists("IconData") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo")) 1063 return false; 1064 1065 if (databaseVersionNumber(db) < currentDatabaseVersion) { 1066 LOG(IconDatabase, "DB version is not found or below expected valid version"); 1067 return false; 1068 } 1069 1070 return true; 1071} 1072 1073static void createDatabaseTables(SQLiteDatabase& db) 1074{ 1075 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) { 1076 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1077 db.close(); 1078 return; 1079 } 1080 if (!db.executeCommand("CREATE INDEX PageURLIndex ON PageURL (url);")) { 1081 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1082 db.close(); 1083 return; 1084 } 1085 if (!db.executeCommand("CREATE TABLE IconInfo (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT FAIL, stamp INTEGER);")) { 1086 LOG_ERROR("Could not create IconInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1087 db.close(); 1088 return; 1089 } 1090 if (!db.executeCommand("CREATE INDEX IconInfoIndex ON IconInfo (url, iconID);")) { 1091 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1092 db.close(); 1093 return; 1094 } 1095 if (!db.executeCommand("CREATE TABLE IconData (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, data BLOB);")) { 1096 LOG_ERROR("Could not create IconData table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1097 db.close(); 1098 return; 1099 } 1100 if (!db.executeCommand("CREATE INDEX IconDataIndex ON IconData (iconID);")) { 1101 LOG_ERROR("Could not create PageURL index in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1102 db.close(); 1103 return; 1104 } 1105 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) { 1106 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg()); 1107 db.close(); 1108 return; 1109 } 1110 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) { 1111 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg()); 1112 db.close(); 1113 return; 1114 } 1115} 1116 1117void IconDatabase::performOpenInitialization() 1118{ 1119 ASSERT_ICON_SYNC_THREAD(); 1120 1121 if (!isOpen()) 1122 return; 1123 1124 if (checkIntegrityOnOpen) { 1125 checkIntegrityOnOpen = false; 1126 if (!checkIntegrity()) { 1127 LOG(IconDatabase, "Integrity check was bad - dumping IconDatabase"); 1128 1129 m_syncDB.close(); 1130 1131 { 1132 MutexLocker locker(m_syncLock); 1133 // Should've been consumed by SQLite, delete just to make sure we don't see it again in the future; 1134 deleteFile(m_completeDatabasePath + "-journal"); 1135 deleteFile(m_completeDatabasePath); 1136 } 1137 1138 // Reopen the write database, creating it from scratch 1139 if (!m_syncDB.open(m_completeDatabasePath)) { 1140 LOG_ERROR("Unable to open icon database at path %s - %s", m_completeDatabasePath.ascii().data(), m_syncDB.lastErrorMsg()); 1141 return; 1142 } 1143 } 1144 } 1145 1146 int version = databaseVersionNumber(m_syncDB); 1147 1148 if (version > currentDatabaseVersion) { 1149 LOG(IconDatabase, "Database version number %i is greater than our current version number %i - closing the database to prevent overwriting newer versions", version, currentDatabaseVersion); 1150 m_syncDB.close(); 1151 m_threadTerminationRequested = true; 1152 return; 1153 } 1154 1155 if (!isValidDatabase(m_syncDB)) { 1156 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", m_completeDatabasePath.ascii().data()); 1157 m_syncDB.clearAllTables(); 1158 createDatabaseTables(m_syncDB); 1159 } 1160 1161 // Reduce sqlite RAM cache size from default 2000 pages (~1.5kB per page). 3MB of cache for icon database is overkill 1162 if (!SQLiteStatement(m_syncDB, "PRAGMA cache_size = 200;").executeCommand()) 1163 LOG_ERROR("SQLite database could not set cache_size"); 1164 1165 // Tell backup software (i.e., Time Machine) to never back up the icon database, because 1166 // it's a large file that changes frequently, thus using a lot of backup disk space, and 1167 // it's unlikely that many users would be upset about it not being backed up. We could 1168 // make this configurable on a per-client basis some day if some clients don't want this. 1169 if (canExcludeFromBackup() && !wasExcludedFromBackup() && excludeFromBackup(m_completeDatabasePath)) 1170 setWasExcludedFromBackup(); 1171} 1172 1173bool IconDatabase::checkIntegrity() 1174{ 1175 ASSERT_ICON_SYNC_THREAD(); 1176 1177 SQLiteStatement integrity(m_syncDB, "PRAGMA integrity_check;"); 1178 if (integrity.prepare() != SQLResultOk) { 1179 LOG_ERROR("checkIntegrity failed to execute"); 1180 return false; 1181 } 1182 1183 int resultCode = integrity.step(); 1184 if (resultCode == SQLResultOk) 1185 return true; 1186 1187 if (resultCode != SQLResultRow) 1188 return false; 1189 1190 int columns = integrity.columnCount(); 1191 if (columns != 1) { 1192 LOG_ERROR("Received %i columns performing integrity check, should be 1", columns); 1193 return false; 1194 } 1195 1196 String resultText = integrity.getColumnText(0); 1197 1198 // A successful, no-error integrity check will be "ok" - all other strings imply failure 1199 if (resultText == "ok") 1200 return true; 1201 1202 LOG_ERROR("Icon database integrity check failed - \n%s", resultText.ascii().data()); 1203 return false; 1204} 1205 1206void IconDatabase::performURLImport() 1207{ 1208 ASSERT_ICON_SYNC_THREAD(); 1209 1210 SQLiteStatement query(m_syncDB, "SELECT PageURL.url, IconInfo.url, IconInfo.stamp FROM PageURL INNER JOIN IconInfo ON PageURL.iconID=IconInfo.iconID;"); 1211 1212 if (query.prepare() != SQLResultOk) { 1213 LOG_ERROR("Unable to prepare icon url import query"); 1214 return; 1215 } 1216 1217 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go 1218 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up 1219 AutodrainedPool pool(25); 1220 1221 int result = query.step(); 1222 while (result == SQLResultRow) { 1223 String pageURL = query.getColumnText(0); 1224 String iconURL = query.getColumnText(1); 1225 1226 { 1227 MutexLocker locker(m_urlAndIconLock); 1228 1229 PageURLRecord* pageRecord = m_pageURLToRecordMap.get(pageURL); 1230 1231 // If the pageRecord doesn't exist in this map, then no one has retained this pageURL 1232 // If the s_databaseCleanupCounter count is non-zero, then we're not supposed to be pruning the database in any manner, 1233 // so go ahead and actually create a pageURLRecord for this url even though it's not retained. 1234 // If database cleanup *is* allowed, we don't want to bother pulling in a page url from disk that noone is actually interested 1235 // in - we'll prune it later instead! 1236 if (!pageRecord && databaseCleanupCounter && !pageURL.isEmpty()) { 1237 pageRecord = new PageURLRecord(pageURL); 1238 m_pageURLToRecordMap.set(pageURL, pageRecord); 1239 } 1240 1241 if (pageRecord) { 1242 IconRecord* currentIcon = pageRecord->iconRecord(); 1243 1244 if (!currentIcon || currentIcon->iconURL() != iconURL) { 1245 pageRecord->setIconRecord(getOrCreateIconRecord(iconURL)); 1246 currentIcon = pageRecord->iconRecord(); 1247 } 1248 1249 // Regardless, the time stamp from disk still takes precedence. Until we read this icon from disk, we didn't think we'd seen it before 1250 // so we marked the timestamp as "now", but it's really much older 1251 currentIcon->setTimestamp(query.getColumnInt(2)); 1252 } 1253 } 1254 1255 // FIXME: Currently the WebKit API supports 1 type of notification that is sent whenever we get an Icon URL for a Page URL. We might want to re-purpose it to work for 1256 // getting the actually icon itself also (so each pageurl would get this notification twice) or we might want to add a second type of notification - 1257 // one for the URL and one for the Image itself 1258 // Note that WebIconDatabase is not neccessarily API so we might be able to make this change 1259 { 1260 MutexLocker locker(m_pendingReadingLock); 1261 if (m_pageURLsPendingImport.contains(pageURL)) { 1262 dispatchDidImportIconURLForPageURLOnMainThread(pageURL); 1263 m_pageURLsPendingImport.remove(pageURL); 1264 1265 pool.cycle(); 1266 } 1267 } 1268 1269 // Stop the import at any time of the thread has been asked to shutdown 1270 if (shouldStopThreadActivity()) { 1271 LOG(IconDatabase, "IconDatabase asked to terminate during performURLImport()"); 1272 return; 1273 } 1274 1275 result = query.step(); 1276 } 1277 1278 if (result != SQLResultDone) 1279 LOG(IconDatabase, "Error reading page->icon url mappings from database"); 1280 1281 // Clear the m_pageURLsPendingImport set - either the page URLs ended up with an iconURL (that we'll notify about) or not, 1282 // but after m_iconURLImportComplete is set to true, we don't care about this set anymore 1283 Vector<String> urls; 1284 { 1285 MutexLocker locker(m_pendingReadingLock); 1286 1287 urls.appendRange(m_pageURLsPendingImport.begin(), m_pageURLsPendingImport.end()); 1288 m_pageURLsPendingImport.clear(); 1289 m_iconURLImportComplete = true; 1290 } 1291 1292 Vector<String> urlsToNotify; 1293 1294 // Loop through the urls pending import 1295 // Remove unretained ones if database cleanup is allowed 1296 // Keep a set of ones that are retained and pending notification 1297 { 1298 MutexLocker locker(m_urlAndIconLock); 1299 1300 for (unsigned i = 0; i < urls.size(); ++i) { 1301 if (!m_retainedPageURLs.contains(urls[i])) { 1302 PageURLRecord* record = m_pageURLToRecordMap.get(urls[i]); 1303 if (record && !databaseCleanupCounter) { 1304 m_pageURLToRecordMap.remove(urls[i]); 1305 IconRecord* iconRecord = record->iconRecord(); 1306 1307 // If this page is the only remaining retainer of its icon, mark that icon for deletion and don't bother 1308 // reading anything related to it 1309 if (iconRecord && iconRecord->hasOneRef()) { 1310 m_iconURLToRecordMap.remove(iconRecord->iconURL()); 1311 1312 { 1313 MutexLocker locker(m_pendingReadingLock); 1314 m_pageURLsInterestedInIcons.remove(urls[i]); 1315 m_iconsPendingReading.remove(iconRecord); 1316 } 1317 { 1318 MutexLocker locker(m_pendingSyncLock); 1319 m_iconsPendingSync.set(iconRecord->iconURL(), iconRecord->snapshot(true)); 1320 } 1321 } 1322 1323 delete record; 1324 } 1325 } else { 1326 urlsToNotify.append(urls[i]); 1327 } 1328 } 1329 } 1330 1331 LOG(IconDatabase, "Notifying %lu interested page URLs that their icon URL is known due to the import", static_cast<unsigned long>(urlsToNotify.size())); 1332 // Now that we don't hold any locks, perform the actual notifications 1333 for (unsigned i = 0; i < urlsToNotify.size(); ++i) { 1334 LOG(IconDatabase, "Notifying icon info known for pageURL %s", urlsToNotify[i].ascii().data()); 1335 dispatchDidImportIconURLForPageURLOnMainThread(urlsToNotify[i]); 1336 if (shouldStopThreadActivity()) 1337 return; 1338 1339 pool.cycle(); 1340 } 1341 1342 // Notify the client that the URL import is complete in case it's managing its own pending notifications. 1343 dispatchDidFinishURLImportOnMainThread(); 1344 1345 // Notify all DocumentLoaders that were waiting for an icon load decision on the main thread 1346 callOnMainThread(notifyPendingLoadDecisionsOnMainThread, this); 1347} 1348 1349void* IconDatabase::syncThreadMainLoop() 1350{ 1351 ASSERT_ICON_SYNC_THREAD(); 1352 1353 bool shouldReenableSuddenTermination = false; 1354 1355 m_syncLock.lock(); 1356 1357 // It's possible thread termination is requested before the main loop even starts - in that case, just skip straight to cleanup 1358 while (!m_threadTerminationRequested) { 1359 m_syncLock.unlock(); 1360 1361#ifndef NDEBUG 1362 double timeStamp = currentTime(); 1363#endif 1364 LOG(IconDatabase, "(THREAD) Main work loop starting"); 1365 1366 // If we should remove all icons, do it now. This is an uninteruptible procedure that we will always do before quitting if it is requested 1367 if (m_removeIconsRequested) { 1368 removeAllIconsOnThread(); 1369 m_removeIconsRequested = false; 1370 } 1371 1372 // Then, if the thread should be quitting, quit now! 1373 if (m_threadTerminationRequested) 1374 break; 1375 1376 bool didAnyWork = true; 1377 while (didAnyWork) { 1378 bool didWrite = writeToDatabase(); 1379 if (shouldStopThreadActivity()) 1380 break; 1381 1382 didAnyWork = readFromDatabase(); 1383 if (shouldStopThreadActivity()) 1384 break; 1385 1386 // Prune unretained icons after the first time we sync anything out to the database 1387 // This way, pruning won't be the only operation we perform to the database by itself 1388 // We also don't want to bother doing this if the thread should be terminating (the user is quitting) 1389 // or if private browsing is enabled 1390 // We also don't want to prune if the m_databaseCleanupCounter count is non-zero - that means someone 1391 // has asked to delay pruning 1392 static bool prunedUnretainedIcons = false; 1393 if (didWrite && !m_privateBrowsingEnabled && !prunedUnretainedIcons && !databaseCleanupCounter) { 1394#ifndef NDEBUG 1395 double time = currentTime(); 1396#endif 1397 LOG(IconDatabase, "(THREAD) Starting pruneUnretainedIcons()"); 1398 1399 pruneUnretainedIcons(); 1400 1401 LOG(IconDatabase, "(THREAD) pruneUnretainedIcons() took %.4f seconds", currentTime() - time); 1402 1403 // If pruneUnretainedIcons() returned early due to requested thread termination, its still okay 1404 // to mark prunedUnretainedIcons true because we're about to terminate anyway 1405 prunedUnretainedIcons = true; 1406 } 1407 1408 didAnyWork = didAnyWork || didWrite; 1409 if (shouldStopThreadActivity()) 1410 break; 1411 } 1412 1413#ifndef NDEBUG 1414 double newstamp = currentTime(); 1415 LOG(IconDatabase, "(THREAD) Main work loop ran for %.4f seconds, %s requested to terminate", newstamp - timeStamp, shouldStopThreadActivity() ? "was" : "was not"); 1416#endif 1417 1418 m_syncLock.lock(); 1419 1420 // There is some condition that is asking us to stop what we're doing now and handle a special case 1421 // This is either removing all icons, or shutting down the thread to quit the app 1422 // We handle those at the top of this main loop so continue to jump back up there 1423 if (shouldStopThreadActivity()) 1424 continue; 1425 1426 if (shouldReenableSuddenTermination) { 1427 // The following is balanced by the call to disableSuddenTermination in the 1428 // wakeSyncThread function. Any time we wait on the condition, we also have 1429 // to enableSuddenTermation, after doing the next batch of work. 1430 ASSERT(m_disabledSuddenTerminationForSyncThread); 1431 enableSuddenTermination(); 1432 m_disabledSuddenTerminationForSyncThread = false; 1433 } 1434 1435 m_syncCondition.wait(m_syncLock); 1436 1437 shouldReenableSuddenTermination = true; 1438 } 1439 1440 m_syncLock.unlock(); 1441 1442 // Thread is terminating at this point 1443 cleanupSyncThread(); 1444 1445 if (shouldReenableSuddenTermination) { 1446 // The following is balanced by the call to disableSuddenTermination in the 1447 // wakeSyncThread function. Any time we wait on the condition, we also have 1448 // to enableSuddenTermation, after doing the next batch of work. 1449 ASSERT(m_disabledSuddenTerminationForSyncThread); 1450 enableSuddenTermination(); 1451 m_disabledSuddenTerminationForSyncThread = false; 1452 } 1453 1454 return 0; 1455} 1456 1457bool IconDatabase::readFromDatabase() 1458{ 1459 ASSERT_ICON_SYNC_THREAD(); 1460 1461#ifndef NDEBUG 1462 double timeStamp = currentTime(); 1463#endif 1464 1465 bool didAnyWork = false; 1466 1467 // We'll make a copy of the sets of things that need to be read. Then we'll verify at the time of updating the record that it still wants to be updated 1468 // This way we won't hold the lock for a long period of time 1469 Vector<IconRecord*> icons; 1470 { 1471 MutexLocker locker(m_pendingReadingLock); 1472 icons.appendRange(m_iconsPendingReading.begin(), m_iconsPendingReading.end()); 1473 } 1474 1475 // Keep track of icons we actually read to notify them of the new icon 1476 HashSet<String> urlsToNotify; 1477 1478 for (unsigned i = 0; i < icons.size(); ++i) { 1479 didAnyWork = true; 1480 RefPtr<SharedBuffer> imageData = getImageDataForIconURLFromSQLDatabase(icons[i]->iconURL()); 1481 1482 // Verify this icon still wants to be read from disk 1483 { 1484 MutexLocker urlLocker(m_urlAndIconLock); 1485 { 1486 MutexLocker readLocker(m_pendingReadingLock); 1487 1488 if (m_iconsPendingReading.contains(icons[i])) { 1489 // Set the new data 1490 icons[i]->setImageData(imageData.get()); 1491 1492 // Remove this icon from the set that needs to be read 1493 m_iconsPendingReading.remove(icons[i]); 1494 1495 // We have a set of all Page URLs that retain this icon as well as all PageURLs waiting for an icon 1496 // We want to find the intersection of these two sets to notify them 1497 // Check the sizes of these two sets to minimize the number of iterations 1498 const HashSet<String>* outerHash; 1499 const HashSet<String>* innerHash; 1500 1501 if (icons[i]->retainingPageURLs().size() > m_pageURLsInterestedInIcons.size()) { 1502 outerHash = &m_pageURLsInterestedInIcons; 1503 innerHash = &(icons[i]->retainingPageURLs()); 1504 } else { 1505 innerHash = &m_pageURLsInterestedInIcons; 1506 outerHash = &(icons[i]->retainingPageURLs()); 1507 } 1508 1509 HashSet<String>::const_iterator iter = outerHash->begin(); 1510 HashSet<String>::const_iterator end = outerHash->end(); 1511 for (; iter != end; ++iter) { 1512 if (innerHash->contains(*iter)) { 1513 LOG(IconDatabase, "%s is interesting in the icon we just read. Adding it to the list and removing it from the interested set", urlForLogging(*iter).ascii().data()); 1514 urlsToNotify.add(*iter); 1515 } 1516 1517 // If we ever get to the point were we've seen every url interested in this icon, break early 1518 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) 1519 break; 1520 } 1521 1522 // We don't need to notify a PageURL twice, so all the ones we're about to notify can be removed from the interested set 1523 if (urlsToNotify.size() == m_pageURLsInterestedInIcons.size()) 1524 m_pageURLsInterestedInIcons.clear(); 1525 else { 1526 iter = urlsToNotify.begin(); 1527 end = urlsToNotify.end(); 1528 for (; iter != end; ++iter) 1529 m_pageURLsInterestedInIcons.remove(*iter); 1530 } 1531 } 1532 } 1533 } 1534 1535 if (shouldStopThreadActivity()) 1536 return didAnyWork; 1537 1538 // Informal testing shows that draining the autorelease pool every 25 iterations is about as low as we can go 1539 // before performance starts to drop off, but we don't want to increase this number because then accumulated memory usage will go up 1540 AutodrainedPool pool(25); 1541 1542 // Now that we don't hold any locks, perform the actual notifications 1543 HashSet<String>::iterator iter = urlsToNotify.begin(); 1544 HashSet<String>::iterator end = urlsToNotify.end(); 1545 for (unsigned iteration = 0; iter != end; ++iter, ++iteration) { 1546 LOG(IconDatabase, "Notifying icon received for pageURL %s", urlForLogging(*iter).ascii().data()); 1547 dispatchDidImportIconDataForPageURLOnMainThread(*iter); 1548 if (shouldStopThreadActivity()) 1549 return didAnyWork; 1550 1551 pool.cycle(); 1552 } 1553 1554 LOG(IconDatabase, "Done notifying %i pageURLs who just received their icons", urlsToNotify.size()); 1555 urlsToNotify.clear(); 1556 1557 if (shouldStopThreadActivity()) 1558 return didAnyWork; 1559 } 1560 1561 LOG(IconDatabase, "Reading from database took %.4f seconds", currentTime() - timeStamp); 1562 1563 return didAnyWork; 1564} 1565 1566bool IconDatabase::writeToDatabase() 1567{ 1568 ASSERT_ICON_SYNC_THREAD(); 1569 1570#ifndef NDEBUG 1571 double timeStamp = currentTime(); 1572#endif 1573 1574 bool didAnyWork = false; 1575 1576 // We can copy the current work queue then clear it out - If any new work comes in while we're writing out, 1577 // we'll pick it up on the next pass. This greatly simplifies the locking strategy for this method and remains cohesive with changes 1578 // asked for by the database on the main thread 1579 Vector<IconSnapshot> iconSnapshots; 1580 Vector<PageURLSnapshot> pageSnapshots; 1581 { 1582 MutexLocker locker(m_pendingSyncLock); 1583 1584 iconSnapshots.appendRange(m_iconsPendingSync.begin().values(), m_iconsPendingSync.end().values()); 1585 m_iconsPendingSync.clear(); 1586 1587 pageSnapshots.appendRange(m_pageURLsPendingSync.begin().values(), m_pageURLsPendingSync.end().values()); 1588 m_pageURLsPendingSync.clear(); 1589 } 1590 1591 if (iconSnapshots.size() || pageSnapshots.size()) 1592 didAnyWork = true; 1593 1594 SQLiteTransaction syncTransaction(m_syncDB); 1595 syncTransaction.begin(); 1596 1597 for (unsigned i = 0; i < iconSnapshots.size(); ++i) { 1598 writeIconSnapshotToSQLDatabase(iconSnapshots[i]); 1599 LOG(IconDatabase, "Wrote IconRecord for IconURL %s with timeStamp of %i to the DB", urlForLogging(iconSnapshots[i].iconURL).ascii().data(), iconSnapshots[i].timestamp); 1600 } 1601 1602 for (unsigned i = 0; i < pageSnapshots.size(); ++i) { 1603 // If the icon URL is empty, this page is meant to be deleted 1604 // ASSERTs are sanity checks to make sure the mappings exist if they should and don't if they shouldn't 1605 if (pageSnapshots[i].iconURL.isEmpty()) 1606 removePageURLFromSQLDatabase(pageSnapshots[i].pageURL); 1607 else 1608 setIconURLForPageURLInSQLDatabase(pageSnapshots[i].iconURL, pageSnapshots[i].pageURL); 1609 LOG(IconDatabase, "Committed IconURL for PageURL %s to database", urlForLogging(pageSnapshots[i].pageURL).ascii().data()); 1610 } 1611 1612 syncTransaction.commit(); 1613 1614 // 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 1615 if (didAnyWork) 1616 checkForDanglingPageURLs(false); 1617 1618 LOG(IconDatabase, "Updating the database took %.4f seconds", currentTime() - timeStamp); 1619 1620 return didAnyWork; 1621} 1622 1623void IconDatabase::pruneUnretainedIcons() 1624{ 1625 ASSERT_ICON_SYNC_THREAD(); 1626 1627 if (!isOpen()) 1628 return; 1629 1630 // This method should only be called once per run 1631 ASSERT(!m_initialPruningComplete); 1632 1633 // This method relies on having read in all page URLs from the database earlier. 1634 ASSERT(m_iconURLImportComplete); 1635 1636 // Get the known PageURLs from the db, and record the ID of any that are not in the retain count set. 1637 Vector<int64_t> pageIDsToDelete; 1638 1639 SQLiteStatement pageSQL(m_syncDB, "SELECT rowid, url FROM PageURL;"); 1640 pageSQL.prepare(); 1641 1642 int result; 1643 while ((result = pageSQL.step()) == SQLResultRow) { 1644 MutexLocker locker(m_urlAndIconLock); 1645 if (!m_pageURLToRecordMap.contains(pageSQL.getColumnText(1))) 1646 pageIDsToDelete.append(pageSQL.getColumnInt64(0)); 1647 } 1648 1649 if (result != SQLResultDone) 1650 LOG_ERROR("Error reading PageURL table from on-disk DB"); 1651 pageSQL.finalize(); 1652 1653 // Delete page URLs that were in the table, but not in our retain count set. 1654 size_t numToDelete = pageIDsToDelete.size(); 1655 if (numToDelete) { 1656 SQLiteTransaction pruningTransaction(m_syncDB); 1657 pruningTransaction.begin(); 1658 1659 SQLiteStatement pageDeleteSQL(m_syncDB, "DELETE FROM PageURL WHERE rowid = (?);"); 1660 pageDeleteSQL.prepare(); 1661 for (size_t i = 0; i < numToDelete; ++i) { 1662#if OS(WINDOWS) 1663 LOG(IconDatabase, "Pruning page with rowid %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); 1664#else 1665 LOG(IconDatabase, "Pruning page with rowid %lli from disk", static_cast<long long>(pageIDsToDelete[i])); 1666#endif 1667 pageDeleteSQL.bindInt64(1, pageIDsToDelete[i]); 1668 int result = pageDeleteSQL.step(); 1669 if (result != SQLResultDone) 1670#if OS(WINDOWS) 1671 LOG_ERROR("Unabled to delete page with id %I64i from disk", static_cast<long long>(pageIDsToDelete[i])); 1672#else 1673 LOG_ERROR("Unabled to delete page with id %lli from disk", static_cast<long long>(pageIDsToDelete[i])); 1674#endif 1675 pageDeleteSQL.reset(); 1676 1677 // If the thread was asked to terminate, we should commit what pruning we've done so far, figuring we can 1678 // finish the rest later (hopefully) 1679 if (shouldStopThreadActivity()) { 1680 pruningTransaction.commit(); 1681 return; 1682 } 1683 } 1684 pruningTransaction.commit(); 1685 pageDeleteSQL.finalize(); 1686 } 1687 1688 // Deleting unreferenced icons from the Icon tables has to be atomic - 1689 // If the user quits while these are taking place, they might have to wait. Thankfully this will rarely be an issue 1690 // A user on a network home directory with a wildly inconsistent database might see quite a pause... 1691 1692 SQLiteTransaction pruningTransaction(m_syncDB); 1693 pruningTransaction.begin(); 1694 1695 // Wipe Icons that aren't retained 1696 if (!m_syncDB.executeCommand("DELETE FROM IconData WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) 1697 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconData table"); 1698 if (!m_syncDB.executeCommand("DELETE FROM IconInfo WHERE iconID NOT IN (SELECT iconID FROM PageURL);")) 1699 LOG_ERROR("Failed to execute SQL to prune unretained icons from the on-disk IconInfo table"); 1700 1701 pruningTransaction.commit(); 1702 1703 checkForDanglingPageURLs(true); 1704 1705 m_initialPruningComplete = true; 1706} 1707 1708void IconDatabase::checkForDanglingPageURLs(bool pruneIfFound) 1709{ 1710 ASSERT_ICON_SYNC_THREAD(); 1711 1712 // 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 1713 // entries. We also don't want to keep performing this check and reporting this error if it has already found danglers before so we 1714 // keep track of whether we've found any. We skip the check in the release build pretending to have already found danglers already. 1715#ifndef NDEBUG 1716 static bool danglersFound = true; 1717#else 1718 static bool danglersFound = false; 1719#endif 1720 1721 if ((pruneIfFound || !danglersFound) && SQLiteStatement(m_syncDB, "SELECT url FROM PageURL WHERE PageURL.iconID NOT IN (SELECT iconID FROM IconInfo) LIMIT 1;").returnsAtLeastOneResult()) { 1722 danglersFound = true; 1723 LOG(IconDatabase, "Dangling PageURL entries found"); 1724 if (pruneIfFound && !m_syncDB.executeCommand("DELETE FROM PageURL WHERE iconID NOT IN (SELECT iconID FROM IconInfo);")) 1725 LOG(IconDatabase, "Unable to prune dangling PageURLs"); 1726 } 1727} 1728 1729void IconDatabase::removeAllIconsOnThread() 1730{ 1731 ASSERT_ICON_SYNC_THREAD(); 1732 1733 LOG(IconDatabase, "Removing all icons on the sync thread"); 1734 1735 // Delete all the prepared statements so they can start over 1736 deleteAllPreparedStatements(); 1737 1738 // To reset the on-disk database, we'll wipe all its tables then vacuum it 1739 // This is easier and safer than closing it, deleting the file, and recreating from scratch 1740 m_syncDB.clearAllTables(); 1741 m_syncDB.runVacuumCommand(); 1742 createDatabaseTables(m_syncDB); 1743 1744 LOG(IconDatabase, "Dispatching notification that we removed all icons"); 1745 dispatchDidRemoveAllIconsOnMainThread(); 1746} 1747 1748void IconDatabase::deleteAllPreparedStatements() 1749{ 1750 ASSERT_ICON_SYNC_THREAD(); 1751 1752 m_setIconIDForPageURLStatement.clear(); 1753 m_removePageURLStatement.clear(); 1754 m_getIconIDForIconURLStatement.clear(); 1755 m_getImageDataForIconURLStatement.clear(); 1756 m_addIconToIconInfoStatement.clear(); 1757 m_addIconToIconDataStatement.clear(); 1758 m_getImageDataStatement.clear(); 1759 m_deletePageURLsForIconURLStatement.clear(); 1760 m_deleteIconFromIconInfoStatement.clear(); 1761 m_deleteIconFromIconDataStatement.clear(); 1762 m_updateIconInfoStatement.clear(); 1763 m_updateIconDataStatement.clear(); 1764 m_setIconInfoStatement.clear(); 1765 m_setIconDataStatement.clear(); 1766} 1767 1768void* IconDatabase::cleanupSyncThread() 1769{ 1770 ASSERT_ICON_SYNC_THREAD(); 1771 1772#ifndef NDEBUG 1773 double timeStamp = currentTime(); 1774#endif 1775 1776 // If the removeIcons flag is set, remove all icons from the db. 1777 if (m_removeIconsRequested) 1778 removeAllIconsOnThread(); 1779 1780 // Sync remaining icons out 1781 LOG(IconDatabase, "(THREAD) Doing final writeout and closure of sync thread"); 1782 writeToDatabase(); 1783 1784 // Close the database 1785 MutexLocker locker(m_syncLock); 1786 1787 m_databaseDirectory = String(); 1788 m_completeDatabasePath = String(); 1789 deleteAllPreparedStatements(); 1790 m_syncDB.close(); 1791 1792#ifndef NDEBUG 1793 LOG(IconDatabase, "(THREAD) Final closure took %.4f seconds", currentTime() - timeStamp); 1794#endif 1795 1796 m_syncThreadRunning = false; 1797 return 0; 1798} 1799 1800bool IconDatabase::imported() 1801{ 1802 ASSERT_ICON_SYNC_THREAD(); 1803 1804 if (m_isImportedSet) 1805 return m_imported; 1806 1807 SQLiteStatement query(m_syncDB, "SELECT IconDatabaseInfo.value FROM IconDatabaseInfo WHERE IconDatabaseInfo.key = \"ImportedSafari2Icons\";"); 1808 if (query.prepare() != SQLResultOk) { 1809 LOG_ERROR("Unable to prepare imported statement"); 1810 return false; 1811 } 1812 1813 int result = query.step(); 1814 if (result == SQLResultRow) 1815 result = query.getColumnInt(0); 1816 else { 1817 if (result != SQLResultDone) 1818 LOG_ERROR("imported statement failed"); 1819 result = 0; 1820 } 1821 1822 m_isImportedSet = true; 1823 return m_imported = result; 1824} 1825 1826void IconDatabase::setImported(bool import) 1827{ 1828 ASSERT_ICON_SYNC_THREAD(); 1829 1830 m_imported = import; 1831 m_isImportedSet = true; 1832 1833 String queryString = import ? 1834 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 1);" : 1835 "INSERT INTO IconDatabaseInfo (key, value) VALUES (\"ImportedSafari2Icons\", 0);"; 1836 1837 SQLiteStatement query(m_syncDB, queryString); 1838 1839 if (query.prepare() != SQLResultOk) { 1840 LOG_ERROR("Unable to prepare set imported statement"); 1841 return; 1842 } 1843 1844 if (query.step() != SQLResultDone) 1845 LOG_ERROR("set imported statement failed"); 1846} 1847 1848// readySQLiteStatement() handles two things 1849// 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user 1850// switches to and from private browsing 1851// 2 - Lazy construction of the Statement in the first place, in case we've never made this query before 1852inline void readySQLiteStatement(OwnPtr<SQLiteStatement>& statement, SQLiteDatabase& db, const String& str) 1853{ 1854 if (statement && (statement->database() != &db || statement->isExpired())) { 1855 if (statement->isExpired()) 1856 LOG(IconDatabase, "SQLiteStatement associated with %s is expired", str.ascii().data()); 1857 statement.set(0); 1858 } 1859 if (!statement) { 1860 statement = adoptPtr(new SQLiteStatement(db, str)); 1861 if (statement->prepare() != SQLResultOk) 1862 LOG_ERROR("Preparing statement %s failed", str.ascii().data()); 1863 } 1864} 1865 1866void IconDatabase::setIconURLForPageURLInSQLDatabase(const String& iconURL, const String& pageURL) 1867{ 1868 ASSERT_ICON_SYNC_THREAD(); 1869 1870 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); 1871 1872 if (!iconID) 1873 iconID = addIconURLToSQLDatabase(iconURL); 1874 1875 if (!iconID) { 1876 LOG_ERROR("Failed to establish an ID for iconURL %s", urlForLogging(iconURL).ascii().data()); 1877 ASSERT(false); 1878 return; 1879 } 1880 1881 setIconIDForPageURLInSQLDatabase(iconID, pageURL); 1882} 1883 1884void IconDatabase::setIconIDForPageURLInSQLDatabase(int64_t iconID, const String& pageURL) 1885{ 1886 ASSERT_ICON_SYNC_THREAD(); 1887 1888 readySQLiteStatement(m_setIconIDForPageURLStatement, m_syncDB, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);"); 1889 m_setIconIDForPageURLStatement->bindText(1, pageURL); 1890 m_setIconIDForPageURLStatement->bindInt64(2, iconID); 1891 1892 int result = m_setIconIDForPageURLStatement->step(); 1893 if (result != SQLResultDone) { 1894 ASSERT(false); 1895 LOG_ERROR("setIconIDForPageURLQuery failed for url %s", urlForLogging(pageURL).ascii().data()); 1896 } 1897 1898 m_setIconIDForPageURLStatement->reset(); 1899} 1900 1901void IconDatabase::removePageURLFromSQLDatabase(const String& pageURL) 1902{ 1903 ASSERT_ICON_SYNC_THREAD(); 1904 1905 readySQLiteStatement(m_removePageURLStatement, m_syncDB, "DELETE FROM PageURL WHERE url = (?);"); 1906 m_removePageURLStatement->bindText(1, pageURL); 1907 1908 if (m_removePageURLStatement->step() != SQLResultDone) 1909 LOG_ERROR("removePageURLFromSQLDatabase failed for url %s", urlForLogging(pageURL).ascii().data()); 1910 1911 m_removePageURLStatement->reset(); 1912} 1913 1914 1915int64_t IconDatabase::getIconIDForIconURLFromSQLDatabase(const String& iconURL) 1916{ 1917 ASSERT_ICON_SYNC_THREAD(); 1918 1919 readySQLiteStatement(m_getIconIDForIconURLStatement, m_syncDB, "SELECT IconInfo.iconID FROM IconInfo WHERE IconInfo.url = (?);"); 1920 m_getIconIDForIconURLStatement->bindText(1, iconURL); 1921 1922 int64_t result = m_getIconIDForIconURLStatement->step(); 1923 if (result == SQLResultRow) 1924 result = m_getIconIDForIconURLStatement->getColumnInt64(0); 1925 else { 1926 if (result != SQLResultDone) 1927 LOG_ERROR("getIconIDForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); 1928 result = 0; 1929 } 1930 1931 m_getIconIDForIconURLStatement->reset(); 1932 return result; 1933} 1934 1935int64_t IconDatabase::addIconURLToSQLDatabase(const String& iconURL) 1936{ 1937 ASSERT_ICON_SYNC_THREAD(); 1938 1939 // There would be a transaction here to make sure these two inserts are atomic 1940 // In practice the only caller of this method is always wrapped in a transaction itself so placing another 1941 // here is unnecessary 1942 1943 readySQLiteStatement(m_addIconToIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url, stamp) VALUES (?, 0);"); 1944 m_addIconToIconInfoStatement->bindText(1, iconURL); 1945 1946 int result = m_addIconToIconInfoStatement->step(); 1947 m_addIconToIconInfoStatement->reset(); 1948 if (result != SQLResultDone) { 1949 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconInfo", urlForLogging(iconURL).ascii().data()); 1950 return 0; 1951 } 1952 int64_t iconID = m_syncDB.lastInsertRowID(); 1953 1954 readySQLiteStatement(m_addIconToIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); 1955 m_addIconToIconDataStatement->bindInt64(1, iconID); 1956 1957 result = m_addIconToIconDataStatement->step(); 1958 m_addIconToIconDataStatement->reset(); 1959 if (result != SQLResultDone) { 1960 LOG_ERROR("addIconURLToSQLDatabase failed to insert %s into IconData", urlForLogging(iconURL).ascii().data()); 1961 return 0; 1962 } 1963 1964 return iconID; 1965} 1966 1967PassRefPtr<SharedBuffer> IconDatabase::getImageDataForIconURLFromSQLDatabase(const String& iconURL) 1968{ 1969 ASSERT_ICON_SYNC_THREAD(); 1970 1971 RefPtr<SharedBuffer> imageData; 1972 1973 readySQLiteStatement(m_getImageDataForIconURLStatement, m_syncDB, "SELECT IconData.data FROM IconData WHERE IconData.iconID IN (SELECT iconID FROM IconInfo WHERE IconInfo.url = (?));"); 1974 m_getImageDataForIconURLStatement->bindText(1, iconURL); 1975 1976 int result = m_getImageDataForIconURLStatement->step(); 1977 if (result == SQLResultRow) { 1978 Vector<char> data; 1979 m_getImageDataForIconURLStatement->getColumnBlobAsVector(0, data); 1980 imageData = SharedBuffer::create(data.data(), data.size()); 1981 } else if (result != SQLResultDone) 1982 LOG_ERROR("getImageDataForIconURLFromSQLDatabase failed for url %s", urlForLogging(iconURL).ascii().data()); 1983 1984 m_getImageDataForIconURLStatement->reset(); 1985 1986 return imageData.release(); 1987} 1988 1989void IconDatabase::removeIconFromSQLDatabase(const String& iconURL) 1990{ 1991 ASSERT_ICON_SYNC_THREAD(); 1992 1993 if (iconURL.isEmpty()) 1994 return; 1995 1996 // There would be a transaction here to make sure these removals are atomic 1997 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary 1998 1999 // It's possible this icon is not in the database because of certain rapid browsing patterns (such as a stress test) where the 2000 // icon is marked to be added then marked for removal before it is ever written to disk. No big deal, early return 2001 int64_t iconID = getIconIDForIconURLFromSQLDatabase(iconURL); 2002 if (!iconID) 2003 return; 2004 2005 readySQLiteStatement(m_deletePageURLsForIconURLStatement, m_syncDB, "DELETE FROM PageURL WHERE PageURL.iconID = (?);"); 2006 m_deletePageURLsForIconURLStatement->bindInt64(1, iconID); 2007 2008 if (m_deletePageURLsForIconURLStatement->step() != SQLResultDone) 2009 LOG_ERROR("m_deletePageURLsForIconURLStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2010 2011 readySQLiteStatement(m_deleteIconFromIconInfoStatement, m_syncDB, "DELETE FROM IconInfo WHERE IconInfo.iconID = (?);"); 2012 m_deleteIconFromIconInfoStatement->bindInt64(1, iconID); 2013 2014 if (m_deleteIconFromIconInfoStatement->step() != SQLResultDone) 2015 LOG_ERROR("m_deleteIconFromIconInfoStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2016 2017 readySQLiteStatement(m_deleteIconFromIconDataStatement, m_syncDB, "DELETE FROM IconData WHERE IconData.iconID = (?);"); 2018 m_deleteIconFromIconDataStatement->bindInt64(1, iconID); 2019 2020 if (m_deleteIconFromIconDataStatement->step() != SQLResultDone) 2021 LOG_ERROR("m_deleteIconFromIconDataStatement failed for url %s", urlForLogging(iconURL).ascii().data()); 2022 2023 m_deletePageURLsForIconURLStatement->reset(); 2024 m_deleteIconFromIconInfoStatement->reset(); 2025 m_deleteIconFromIconDataStatement->reset(); 2026} 2027 2028void IconDatabase::writeIconSnapshotToSQLDatabase(const IconSnapshot& snapshot) 2029{ 2030 ASSERT_ICON_SYNC_THREAD(); 2031 2032 if (snapshot.iconURL.isEmpty()) 2033 return; 2034 2035 // A nulled out timestamp and data means this icon is destined to be deleted - do that instead of writing it out 2036 if (!snapshot.timestamp && !snapshot.data) { 2037 LOG(IconDatabase, "Removing %s from on-disk database", urlForLogging(snapshot.iconURL).ascii().data()); 2038 removeIconFromSQLDatabase(snapshot.iconURL); 2039 return; 2040 } 2041 2042 // There would be a transaction here to make sure these removals are atomic 2043 // In practice the only caller of this method is always wrapped in a transaction itself so placing another here is unnecessary 2044 2045 // Get the iconID for this url 2046 int64_t iconID = getIconIDForIconURLFromSQLDatabase(snapshot.iconURL); 2047 2048 // If there is already an iconID in place, update the database. 2049 // Otherwise, insert new records 2050 if (iconID) { 2051 readySQLiteStatement(m_updateIconInfoStatement, m_syncDB, "UPDATE IconInfo SET stamp = ?, url = ? WHERE iconID = ?;"); 2052 m_updateIconInfoStatement->bindInt64(1, snapshot.timestamp); 2053 m_updateIconInfoStatement->bindText(2, snapshot.iconURL); 2054 m_updateIconInfoStatement->bindInt64(3, iconID); 2055 2056 if (m_updateIconInfoStatement->step() != SQLResultDone) 2057 LOG_ERROR("Failed to update icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2058 2059 m_updateIconInfoStatement->reset(); 2060 2061 readySQLiteStatement(m_updateIconDataStatement, m_syncDB, "UPDATE IconData SET data = ? WHERE iconID = ?;"); 2062 m_updateIconDataStatement->bindInt64(2, iconID); 2063 2064 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 2065 // signifying that this icon doesn't have any data 2066 if (snapshot.data && snapshot.data->size()) 2067 m_updateIconDataStatement->bindBlob(1, snapshot.data->data(), snapshot.data->size()); 2068 else 2069 m_updateIconDataStatement->bindNull(1); 2070 2071 if (m_updateIconDataStatement->step() != SQLResultDone) 2072 LOG_ERROR("Failed to update icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2073 2074 m_updateIconDataStatement->reset(); 2075 } else { 2076 readySQLiteStatement(m_setIconInfoStatement, m_syncDB, "INSERT INTO IconInfo (url,stamp) VALUES (?, ?);"); 2077 m_setIconInfoStatement->bindText(1, snapshot.iconURL); 2078 m_setIconInfoStatement->bindInt64(2, snapshot.timestamp); 2079 2080 if (m_setIconInfoStatement->step() != SQLResultDone) 2081 LOG_ERROR("Failed to set icon info for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2082 2083 m_setIconInfoStatement->reset(); 2084 2085 int64_t iconID = m_syncDB.lastInsertRowID(); 2086 2087 readySQLiteStatement(m_setIconDataStatement, m_syncDB, "INSERT INTO IconData (iconID, data) VALUES (?, ?);"); 2088 m_setIconDataStatement->bindInt64(1, iconID); 2089 2090 // If we *have* image data, bind it to this statement - Otherwise bind "null" for the blob data, 2091 // signifying that this icon doesn't have any data 2092 if (snapshot.data && snapshot.data->size()) 2093 m_setIconDataStatement->bindBlob(2, snapshot.data->data(), snapshot.data->size()); 2094 else 2095 m_setIconDataStatement->bindNull(2); 2096 2097 if (m_setIconDataStatement->step() != SQLResultDone) 2098 LOG_ERROR("Failed to set icon data for url %s", urlForLogging(snapshot.iconURL).ascii().data()); 2099 2100 m_setIconDataStatement->reset(); 2101 } 2102} 2103 2104bool IconDatabase::wasExcludedFromBackup() 2105{ 2106 ASSERT_ICON_SYNC_THREAD(); 2107 2108 return SQLiteStatement(m_syncDB, "SELECT value FROM IconDatabaseInfo WHERE key = 'ExcludedFromBackup';").getColumnInt(0); 2109} 2110 2111void IconDatabase::setWasExcludedFromBackup() 2112{ 2113 ASSERT_ICON_SYNC_THREAD(); 2114 2115 SQLiteStatement(m_syncDB, "INSERT INTO IconDatabaseInfo (key, value) VALUES ('ExcludedFromBackup', 1)").executeCommand(); 2116} 2117 2118class ClientWorkItem { 2119public: 2120 ClientWorkItem(IconDatabaseClient* client) 2121 : m_client(client) 2122 { } 2123 virtual void performWork() = 0; 2124 virtual ~ClientWorkItem() { } 2125 2126protected: 2127 IconDatabaseClient* m_client; 2128}; 2129 2130class ImportedIconURLForPageURLWorkItem : public ClientWorkItem { 2131public: 2132 ImportedIconURLForPageURLWorkItem(IconDatabaseClient* client, const String& pageURL) 2133 : ClientWorkItem(client) 2134 , m_pageURL(new String(pageURL.threadsafeCopy())) 2135 { } 2136 2137 virtual ~ImportedIconURLForPageURLWorkItem() 2138 { 2139 delete m_pageURL; 2140 } 2141 2142 virtual void performWork() 2143 { 2144 ASSERT(m_client); 2145 m_client->didImportIconURLForPageURL(*m_pageURL); 2146 m_client = 0; 2147 } 2148 2149private: 2150 String* m_pageURL; 2151}; 2152 2153class ImportedIconDataForPageURLWorkItem : public ClientWorkItem { 2154public: 2155 ImportedIconDataForPageURLWorkItem(IconDatabaseClient* client, const String& pageURL) 2156 : ClientWorkItem(client) 2157 , m_pageURL(new String(pageURL.threadsafeCopy())) 2158 { } 2159 2160 virtual ~ImportedIconDataForPageURLWorkItem() 2161 { 2162 delete m_pageURL; 2163 } 2164 2165 virtual void performWork() 2166 { 2167 ASSERT(m_client); 2168 m_client->didImportIconDataForPageURL(*m_pageURL); 2169 m_client = 0; 2170 } 2171 2172private: 2173 String* m_pageURL; 2174}; 2175 2176class RemovedAllIconsWorkItem : public ClientWorkItem { 2177public: 2178 RemovedAllIconsWorkItem(IconDatabaseClient* client) 2179 : ClientWorkItem(client) 2180 { } 2181 2182 virtual void performWork() 2183 { 2184 ASSERT(m_client); 2185 m_client->didRemoveAllIcons(); 2186 m_client = 0; 2187 } 2188}; 2189 2190class FinishedURLImport : public ClientWorkItem { 2191public: 2192 FinishedURLImport(IconDatabaseClient* client) 2193 : ClientWorkItem(client) 2194 { } 2195 2196 virtual void performWork() 2197 { 2198 ASSERT(m_client); 2199 m_client->didFinishURLImport(); 2200 m_client = 0; 2201 } 2202}; 2203 2204static void performWorkItem(void* context) 2205{ 2206 ClientWorkItem* item = static_cast<ClientWorkItem*>(context); 2207 item->performWork(); 2208 delete item; 2209} 2210 2211void IconDatabase::dispatchDidImportIconURLForPageURLOnMainThread(const String& pageURL) 2212{ 2213 ASSERT_ICON_SYNC_THREAD(); 2214 2215 ImportedIconURLForPageURLWorkItem* work = new ImportedIconURLForPageURLWorkItem(m_client, pageURL); 2216 callOnMainThread(performWorkItem, work); 2217} 2218 2219void IconDatabase::dispatchDidImportIconDataForPageURLOnMainThread(const String& pageURL) 2220{ 2221 ASSERT_ICON_SYNC_THREAD(); 2222 2223 ImportedIconDataForPageURLWorkItem* work = new ImportedIconDataForPageURLWorkItem(m_client, pageURL); 2224 callOnMainThread(performWorkItem, work); 2225} 2226 2227void IconDatabase::dispatchDidRemoveAllIconsOnMainThread() 2228{ 2229 ASSERT_ICON_SYNC_THREAD(); 2230 2231 RemovedAllIconsWorkItem* work = new RemovedAllIconsWorkItem(m_client); 2232 callOnMainThread(performWorkItem, work); 2233} 2234 2235void IconDatabase::dispatchDidFinishURLImportOnMainThread() 2236{ 2237 ASSERT_ICON_SYNC_THREAD(); 2238 2239 FinishedURLImport* work = new FinishedURLImport(m_client); 2240 callOnMainThread(performWorkItem, work); 2241} 2242 2243 2244} // namespace WebCore 2245 2246#endif // ENABLE(ICONDATABASE) 2247