2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 #include "IconDatabase.h"
29 #include "IconDataCache.h"
32 #include "PlatformString.h"
33 #include "SQLStatement.h"
34 #include "SQLTransaction.h"
35 #include "SystemTime.h"
37 // FIXME - One optimization to be made when this is no longer in flux is to make query construction smarter - that is queries that are created from
38 // multiple strings and numbers should be handled differently than with String + String + String + etc.
42 IconDatabase* IconDatabase::m_sharedInstance = 0;
44 // This version number is in the DB and marks the current generation of the schema
45 // Theoretically once the switch is flipped this should never change
46 // Currently, an out-of-date schema causes the DB to be wiped and reset. This isn't
47 // so bad during development but in the future, we would need to write a conversion
48 // function to advance older released schemas to "current"
49 const int IconDatabase::currentDatabaseVersion = 5;
51 // Icons expire once a day
52 const int IconDatabase::iconExpirationTime = 60*60*24;
53 // Absent icons are rechecked once a week
54 const int IconDatabase::missingIconExpirationTime = 60*60*24*7;
56 const int IconDatabase::updateTimerDelay = 5;
58 const String& IconDatabase::defaultDatabaseFilename()
60 static String defaultDatabaseFilename = "icon.db";
61 return defaultDatabaseFilename;
64 IconDatabase* IconDatabase::sharedIconDatabase()
66 if (!m_sharedInstance) {
67 m_sharedInstance = new IconDatabase();
69 return m_sharedInstance;
72 IconDatabase::IconDatabase()
73 : m_timeStampForIconURLStatement(0)
74 , m_iconURLForPageURLStatement(0)
75 , m_hasIconForIconURLStatement(0)
76 , m_forgetPageURLStatement(0)
77 , m_setIconIDForPageURLStatement(0)
78 , m_getIconIDForIconURLStatement(0)
79 , m_addIconForIconURLStatement(0)
80 , m_imageDataForIconURLStatement(0)
81 , m_currentDB(&m_mainDB)
82 , m_defaultIconDataCache(0)
83 , m_privateBrowsingEnabled(false)
84 , m_startupTimer(this, &IconDatabase::pruneUnretainedIconsOnStartup)
85 , m_updateTimer(this, &IconDatabase::updateDatabase)
86 , m_initialPruningComplete(false)
87 , m_initialPruningTransaction(0)
88 , m_preparedPageRetainInsertStatement(0)
93 bool IconDatabase::open(const String& databasePath)
96 LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first.");
100 // First we'll formulate the full path for the database file
102 if (databasePath[databasePath.length()] == '/')
103 dbFilename = databasePath + defaultDatabaseFilename();
105 dbFilename = databasePath + "/" + defaultDatabaseFilename();
107 // Now, we'll see if we can open the on-disk table
108 // If we can't, this ::open() failed and we should bail now
109 if (!m_mainDB.open(dbFilename)) {
110 LOG(IconDatabase, "Unable to open icon database at path %s", dbFilename.ascii().data());
114 if (!isValidDatabase(m_mainDB)) {
115 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", dbFilename.ascii().data());
116 clearDatabaseTables(m_mainDB);
117 createDatabaseTables(m_mainDB);
120 m_initialPruningTransaction = new SQLTransaction(m_mainDB);
121 // We're going to track an icon's retain count in a temp table in memory so we can cross reference it to to the on disk tables
123 result = m_mainDB.executeCommand("CREATE TEMP TABLE PageRetain (url TEXT);");
124 // Creating an in-memory temp table should never, ever, ever fail
127 // These are actually two different SQLite config options - not my fault they are named confusingly ;)
128 m_mainDB.setSynchronous(SQLDatabase::SyncOff);
129 m_mainDB.setFullsync(false);
131 m_initialPruningTransaction->begin();
133 // Open the in-memory table for private browsing
134 if (!m_privateBrowsingDB.open(":memory:"))
135 LOG_ERROR("Unabled to open in-memory database for private browsing - %s", m_privateBrowsingDB.lastErrorMsg());
137 // Only if we successfully remained open will we start our "initial purge timer"
138 // rdar://4690949 - when we have deferred reads and writes all the way in, the prunetimer
139 // will become "deferredTimer" or something along those lines, and will be set only when
140 // a deferred read/write is queued
142 m_startupTimer.startOneShot(0);
147 void IconDatabase::close()
149 delete m_initialPruningTransaction;
150 if (m_preparedPageRetainInsertStatement)
151 m_preparedPageRetainInsertStatement->finalize();
152 delete m_preparedPageRetainInsertStatement;
155 delete m_timeStampForIconURLStatement;
156 delete m_iconURLForPageURLStatement;
157 delete m_hasIconForIconURLStatement;
158 delete m_forgetPageURLStatement;
159 delete m_setIconIDForPageURLStatement;
160 delete m_getIconIDForIconURLStatement;
161 delete m_addIconForIconURLStatement;
162 delete m_imageDataForIconURLStatement;
164 m_privateBrowsingDB.close();
167 bool IconDatabase::isEmpty()
169 if (m_privateBrowsingEnabled)
170 if (!pageURLTableIsEmptyQuery(m_privateBrowsingDB))
173 return pageURLTableIsEmptyQuery(m_mainDB);
176 bool IconDatabase::isValidDatabase(SQLDatabase& db)
178 // These two tables should always exist in a valid db
179 if (!db.tableExists("Icon") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
182 if (SQLStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0) < currentDatabaseVersion) {
183 LOG(IconDatabase, "DB version is not found or below expected valid version");
190 void IconDatabase::clearDatabaseTables(SQLDatabase& db)
192 String query = "SELECT name FROM sqlite_master WHERE type='table';";
193 Vector<String> tables;
194 if (!SQLStatement(db, query).returnTextResults16(0, tables)) {
195 LOG(IconDatabase, "Unable to retrieve list of tables from database");
199 for (Vector<String>::iterator table = tables.begin(); table != tables.end(); ++table ) {
200 if (!db.executeCommand("DROP TABLE " + *table)) {
201 LOG(IconDatabase, "Unable to drop table %s", (*table).ascii().data());
206 void IconDatabase::createDatabaseTables(SQLDatabase& db)
208 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
209 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
213 if (!db.executeCommand("CREATE TABLE Icon (iconID INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE ON CONFLICT REPLACE, url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, stamp INTEGER, data BLOB);")) {
214 LOG_ERROR("Could not create Icon table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
218 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
219 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
223 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
224 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
230 void IconDatabase::imageDataForIconURL(const String& iconURL, Vector<unsigned char>& result)
232 // If private browsing is enabled, we'll check there first as the most up-to-date data for an icon will be there
233 if (m_privateBrowsingEnabled) {
234 imageDataForIconURLQuery(m_privateBrowsingDB, iconURL, result);
235 if (!result.isEmpty())
239 // It wasn't found there, so lets check the main tables
240 imageDataForIconURLQuery(m_mainDB, iconURL, result);
243 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
245 if (m_privateBrowsingEnabled == flag)
248 // Sync any deferred DB changes before we change the active DB
251 m_privateBrowsingEnabled = flag;
253 if (m_privateBrowsingEnabled) {
254 createDatabaseTables(m_privateBrowsingDB);
255 m_currentDB = &m_privateBrowsingDB;
257 clearDatabaseTables(m_privateBrowsingDB);
258 m_currentDB = &m_mainDB;
262 Image* IconDatabase::iconForPageURL(const String& pageURL, const IntSize& size, bool cache)
264 // See if we even have an IconURL for this PageURL...
265 String iconURL = iconURLForPageURL(pageURL);
266 if (iconURL.isEmpty())
269 // If we do, maybe we have a IconDataCache for this IconURL
270 IconDataCache* icon = getOrCreateIconDataCache(iconURL);
272 // If it's a new IconDataCache object that doesn't have its imageData set yet,
273 // we'll read in that image data now
274 if (icon->imageDataStatus() == ImageDataStatusUnknown) {
275 Vector<unsigned char> data;
276 imageDataForIconURL(iconURL, data);
277 icon->setImageData(data.data(), data.size());
280 return icon->getImage(size);
283 // FIXME 4667425 - this check needs to see if the icon's data is empty or not and apply
284 // iconExpirationTime to present icons, and missingIconExpirationTime for missing icons
285 bool IconDatabase::isIconExpiredForIconURL(const String& iconURL)
287 if (iconURL.isEmpty())
290 // If we have a IconDataCache, then it definitely has the Timestamp in it
291 IconDataCache* icon = m_iconURLToIconDataCacheMap.get(iconURL);
293 return (int)currentTime() - icon->getTimestamp() > iconExpirationTime;
295 // Otherwise, we'll get the timestamp from the DB and use it
297 if (m_privateBrowsingEnabled) {
298 stamp = timeStampForIconURLQuery(m_privateBrowsingDB, iconURL);
300 return ((int)currentTime() - stamp) > iconExpirationTime;
303 stamp = timeStampForIconURLQuery(m_mainDB, iconURL);
305 return ((int)currentTime() - stamp) > iconExpirationTime;
310 String IconDatabase::iconURLForPageURL(const String& pageURL)
312 if (pageURL.isEmpty())
315 if (m_pageURLToIconURLMap.contains(pageURL))
316 return m_pageURLToIconURLMap.get(pageURL);
318 // Try the private browsing database because if any PageURL's IconURL was updated during privated browsing,
319 // the most up-to-date iconURL would be there
320 if (m_privateBrowsingEnabled) {
321 String iconURL = iconURLForPageURLQuery(m_privateBrowsingDB, pageURL);
322 if (!iconURL.isEmpty()) {
323 m_pageURLToIconURLMap.set(pageURL, iconURL);
328 String iconURL = iconURLForPageURLQuery(m_mainDB, pageURL);
329 if (!iconURL.isEmpty())
330 m_pageURLToIconURLMap.set(pageURL, iconURL);
334 Image* IconDatabase::defaultIcon(const IntSize& size)
336 if (!m_defaultIconDataCache) {
337 m_defaultIconDataCache = new IconDataCache("urlIcon");
338 m_defaultIconDataCache->loadImageFromResource("urlIcon");
341 return m_defaultIconDataCache->getImage(size);
344 void IconDatabase::retainIconForPageURL(const String& pageURL)
346 if (pageURL.isEmpty())
349 // If we don't have the retain count for this page, we need to setup records of its retain
350 // Otherwise, get the count and increment it
352 if (!(retainCount = m_pageURLToRetainCount.get(pageURL))) {
353 m_pageURLToRetainCount.set(pageURL, 1);
355 // If we haven't done initial pruning, we store this retain record in the temporary in-memory table
356 // Note we only keep the URL in the temporary table, not the full retain count, because for pruning-considerations
357 // we only care *if* a pageURL is retained - not the full count. This call to retainIconForPageURL incremented the PageURL's
358 // retain count from 0 to 1 therefore we may store it in the temporary table
359 // Also, if we haven't done pruning yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
360 // so we bail here and skip those steps
361 if (!m_initialPruningComplete) {
362 String escapedPageURL = escapeSQLString(pageURL);
363 if (!m_preparedPageRetainInsertStatement) {
364 m_preparedPageRetainInsertStatement = new SQLStatement(m_mainDB, "INSERT INTO PageRetain VALUES (?);");
365 m_preparedPageRetainInsertStatement->prepare();
367 m_preparedPageRetainInsertStatement->reset();
368 m_preparedPageRetainInsertStatement->bindText16(1, pageURL);
369 if (m_preparedPageRetainInsertStatement->step() != SQLITE_DONE)
370 LOG_ERROR("Failed to record icon retention in temporary table for IconURL %s", pageURL.ascii().data());
374 // If this pageURL is marked for deletion, bring it back from the brink
375 m_pageURLsPendingDeletion.remove(pageURL);
377 // If we have an iconURL for this pageURL, we'll now retain the iconURL
378 String iconURL = iconURLForPageURL(pageURL);
379 if (!iconURL.isEmpty())
380 retainIconURL(iconURL);
383 m_pageURLToRetainCount.set(pageURL, retainCount + 1);
386 void IconDatabase::releaseIconForPageURL(const String& pageURL)
388 if (pageURL.isEmpty())
391 // Check if this pageURL is actually retained
392 if(!m_pageURLToRetainCount.contains(pageURL)) {
393 LOG_ERROR("Attempting to release icon for URL %s which is not retained", pageURL.ascii().data());
397 // Get its retain count
398 int retainCount = m_pageURLToRetainCount.get(pageURL);
399 ASSERT(retainCount > 0);
401 // If it still has a positive retain count, store the new count and bail
403 m_pageURLToRetainCount.set(pageURL, retainCount);
407 LOG(IconDatabase, "No more retainers for PageURL %s", pageURL.ascii().data());
409 // Otherwise, remove all record of the retain count
410 m_pageURLToRetainCount.remove(pageURL);
412 // If we haven't done initial pruning, we remove this retain record from the temporary in-memory table
413 // Note we only keep the URL in the temporary table, not the full retain count, because for pruning-considerations
414 // we only care *if* a pageURL is retained - not the full count. This call to releaseIconForPageURL decremented the PageURL's
415 // retain count from 1 to 0 therefore we may remove it from the temporary table
416 // Also, if we haven't done pruning yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot,
417 // so we bail here and skip those steps
418 if (!m_initialPruningComplete) {
419 String escapedPageURL = escapeSQLString(pageURL);
420 if (!m_mainDB.executeCommand("DELETE FROM PageRetain WHERE url='" + escapedPageURL + "';"))
421 LOG_ERROR("Failed to delete record of icon retention from temporary table for IconURL %s", pageURL.ascii().data());
426 // Then mark this pageURL for deletion
427 m_pageURLsPendingDeletion.add(pageURL);
429 // Grab the iconURL and release it
430 String iconURL = iconURLForPageURL(pageURL);
431 if (!iconURL.isEmpty())
432 releaseIconURL(iconURL);
435 void IconDatabase::retainIconURL(const String& iconURL)
437 ASSERT(!iconURL.isEmpty());
439 if (int retainCount = m_iconURLToRetainCount.get(iconURL)) {
440 ASSERT(retainCount > 0);
441 m_iconURLToRetainCount.set(iconURL, retainCount + 1);
443 m_iconURLToRetainCount.set(iconURL, 1);
444 if (m_iconURLsPendingDeletion.contains(iconURL))
445 m_iconURLsPendingDeletion.remove(iconURL);
449 void IconDatabase::releaseIconURL(const String& iconURL)
451 ASSERT(!iconURL.isEmpty());
453 // If the iconURL has no retain count, we can bail
454 if (!m_iconURLToRetainCount.contains(iconURL))
457 // Otherwise, decrement it
458 int retainCount = m_iconURLToRetainCount.get(iconURL) - 1;
459 ASSERT(retainCount > -1);
461 // If the icon is still retained, store the count and bail
463 m_iconURLToRetainCount.set(iconURL, retainCount);
467 LOG(IconDatabase, "No more retainers for IconURL %s", iconURL.ascii().data());
469 // Otherwise, this icon is toast. Remove all traces of its retain count...
470 m_iconURLToRetainCount.remove(iconURL);
472 // And since we delay the actual deletion of icons, so lets add it to that queue
473 m_iconURLsPendingDeletion.add(iconURL);
476 void IconDatabase::forgetPageURL(const String& pageURL)
478 // Remove the PageURL->IconURL mapping
479 m_pageURLToIconURLMap.remove(pageURL);
481 // And remove this pageURL from the DB
482 forgetPageURLQuery(*m_currentDB, pageURL);
485 bool IconDatabase::isIconURLRetained(const String& iconURL)
487 if (iconURL.isEmpty())
490 return m_iconURLToRetainCount.contains(iconURL);
493 void IconDatabase::forgetIconForIconURLFromDatabase(const String& iconURL)
495 if (iconURL.isEmpty())
498 // For private browsing safety, since this alters the database, we only forget from the current database
499 // If we're in private browsing and the Icon also exists in the main database, it will be pruned on the next startup
500 int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL, false);
502 // If we didn't actually have an icon for this iconURL... well, thats a screwy condition we should track down, but also
503 // something we could move on from
506 LOG_ERROR("Attempting to forget icon for IconURL %s, though we don't have it in the database", iconURL.ascii().data());
510 if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM Icon WHERE Icon.iconID = %lli;", iconID)))
511 LOG_ERROR("Unable to drop Icon for IconURL", iconURL.ascii().data());
512 if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM PageURL WHERE PageURL.iconID = %lli", iconID)))
513 LOG_ERROR("Unable to drop all PageURL for IconURL", iconURL.ascii().data());
516 IconDataCache* IconDatabase::getOrCreateIconDataCache(const String& iconURL)
519 if ((icon = m_iconURLToIconDataCacheMap.get(iconURL)))
522 icon = new IconDataCache(iconURL);
523 m_iconURLToIconDataCacheMap.set(iconURL, icon);
525 // Get the most current time stamp for this IconURL
527 if (m_privateBrowsingEnabled)
528 timestamp = timeStampForIconURLQuery(m_privateBrowsingDB, iconURL);
530 timestamp = timeStampForIconURLQuery(m_mainDB, iconURL);
532 // If we can't get a timestamp for this URL, then it is a new icon and we initialize its timestamp now
534 icon->setTimestamp((int)currentTime());
535 m_iconDataCachesPendingUpdate.add(icon);
537 icon->setTimestamp(timestamp);
542 void IconDatabase::setIconDataForIconURL(const void* data, int size, const String& iconURL)
550 if (iconURL.isEmpty())
553 // Get the IconDataCache for this IconURL (note, IconDataCacheForIconURL will create it if necessary)
554 IconDataCache* icon = getOrCreateIconDataCache(iconURL);
556 // Set the data in the IconDataCache
557 icon->setImageData((unsigned char*)data, size);
559 // Update the timestamp in the IconDataCache to NOW
560 icon->setTimestamp((int)currentTime());
562 // Mark the IconDataCache as requiring an update to the database
563 m_iconDataCachesPendingUpdate.add(icon);
566 void IconDatabase::setHaveNoIconForIconURL(const String& iconURL)
568 setIconDataForIconURL(0, 0, iconURL);
571 bool IconDatabase::setIconURLForPageURL(const String& iconURL, const String& pageURL)
573 ASSERT(!iconURL.isEmpty());
574 ASSERT(!pageURL.isEmpty());
576 // If the urls already map to each other, bail.
577 // This happens surprisingly often, and seems to cream iBench performance
578 if (m_pageURLToIconURLMap.get(pageURL) == iconURL)
581 // If this pageURL is retained, we have some work to do on the IconURL retain counts
582 if (m_pageURLToRetainCount.contains(pageURL)) {
583 String oldIconURL = m_pageURLToIconURLMap.get(pageURL);
584 if (!oldIconURL.isEmpty())
585 releaseIconURL(oldIconURL);
586 retainIconURL(iconURL);
588 // If this pageURL is *not* retained, then we may be marking it for deletion, as well!
589 // As counterintuitive as it seems to mark it for addition and for deletion at the same time,
590 // it's valid because when we do a new pageURL->iconURL mapping we *have* to mark it for addition,
591 // no matter what, as there is no efficient was to determine if the mapping is in the DB already.
592 // But, if the iconURL is marked for deletion, we'll also mark this pageURL for deletion - if a
593 // client comes along and retains it before the timer fires, the "pendingDeletion" lists will
594 // be manipulated appopriately and new pageURL will be brought back from the brink
595 if (m_iconURLsPendingDeletion.contains(iconURL))
596 m_pageURLsPendingDeletion.add(pageURL);
599 // Cache the pageURL->iconURL map
600 m_pageURLToIconURLMap.set(pageURL, iconURL);
602 // And mark this mapping to be added to the database
603 m_pageURLsPendingAddition.add(pageURL);
605 // Then start the timer to commit this change - or further delay the timer if it
606 // was already started
607 m_updateTimer.startOneShot(updateTimerDelay);
612 void IconDatabase::setIconURLForPageURLInDatabase(const String& iconURL, const String& pageURL)
614 int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL);
616 LOG_ERROR("Failed to establish an ID for iconURL %s", iconURL.ascii().data());
619 setIconIDForPageURLQuery(*m_currentDB, iconID, pageURL);
622 int64_t IconDatabase::establishIconIDForIconURL(SQLDatabase& db, const String& iconURL, bool createIfNecessary)
624 String escapedIconURL = escapeSQLString(iconURL);
626 // Get the iconID thats already in this database and return it - or return 0 if we're read-only
627 int64_t iconID = getIconIDForIconURLQuery(db, iconURL);
628 if (iconID || !createIfNecessary)
631 // Create the icon table entry for the iconURL
632 return addIconForIconURLQuery(db, iconURL);
635 void IconDatabase::pruneUnretainedIconsOnStartup(Timer<IconDatabase>*)
640 // This function should only be called once per run, and ideally only via the timer
641 // on program startup
642 ASSERT(!m_initialPruningComplete);
645 double timestamp = currentTime();
648 // rdar://4690949 - Need to prune unretained iconURLs here, then prune out all pageURLs that reference
651 // Finalize the PageRetain statement
652 delete m_preparedPageRetainInsertStatement;
653 m_preparedPageRetainInsertStatement = 0;
655 // Commit all of the PageRetains and start a new transaction for the pruning dirty-work
656 m_initialPruningTransaction->commit();
657 m_initialPruningTransaction->begin();
659 // Then wipe all PageURLs and Icons that aren't retained
660 if (!m_mainDB.executeCommand("DELETE FROM PageURL WHERE PageURL.url NOT IN (SELECT url FROM PageRetain);") ||
661 !m_mainDB.executeCommand("DELETE FROM Icon WHERE Icon.iconID NOT IN (SELECT iconID FROM PageURL);") ||
662 !m_mainDB.executeCommand("DROP TABLE PageRetain;"))
663 LOG_ERROR("Failed to execute SQL to prune unretained pages and icons from the on-disk tables");
666 // Since we lazily retained the pageURLs without getting the iconURLs or retaining the iconURLs,
667 // we need to do that now
668 // We now should be in a spiffy situation where we know every single pageURL left in the DB is retained, so we are interested
669 // in the iconURLs for all remaining pageURLs
670 // So we can simply add all the remaining mappings, and retain each pageURL's icon once
672 SQLStatement sql(m_mainDB, "SELECT PageURL.url, Icon.url FROM PageURL INNER JOIN Icon ON PageURL.iconID=Icon.iconID");
675 while((result = sql.step()) == SQLITE_ROW) {
676 String iconURL = sql.getColumnText16(1);
677 m_pageURLToIconURLMap.set(sql.getColumnText16(0), iconURL);
678 retainIconURL(iconURL);
679 LOG(IconDatabase, "Found a PageURL that mapped to %s", iconURL.ascii().data());
681 if (result != SQLITE_DONE)
682 LOG_ERROR("Error reading PageURL->IconURL mappings from on-disk DB");
685 // Commit the transaction and do some cleanup
686 m_initialPruningTransaction->commit();
687 delete m_initialPruningTransaction;
688 m_initialPruningTransaction = 0;
690 m_initialPruningComplete = true;
693 timestamp = currentTime() - timestamp;
694 if (timestamp <= 1.0)
695 LOG(IconDatabase, "Pruning unretained icons took %.4f seconds", timestamp);
697 LOG(IconDatabase, "Pruning unretained icons took %.4f seconds - this is much too long!", timestamp);
701 void IconDatabase::updateDatabase(Timer<IconDatabase>*)
706 void IconDatabase::syncDatabase()
709 double timestamp = currentTime();
712 // First we'll do the pending additions
713 // Starting with the IconDataCaches that need updating/insertion
714 for (HashSet<IconDataCache*>::iterator i = m_iconDataCachesPendingUpdate.begin(), end = m_iconDataCachesPendingUpdate.end(); i != end; ++i) {
715 (*i)->writeToDatabase(*m_currentDB);
716 LOG(IconDatabase, "Wrote IconDataCache for IconURL %s with timestamp of %lli to the DB", (*i)->getIconURL().ascii().data(), (*i)->getTimestamp());
718 m_iconDataCachesPendingUpdate.clear();
720 HashSet<String>::iterator i = m_pageURLsPendingAddition.begin(), end = m_pageURLsPendingAddition.end();
721 for (; i != end; ++i) {
722 setIconURLForPageURLInDatabase(m_pageURLToIconURLMap.get(*i), *i);
723 LOG(IconDatabase, "Commited IconURL for PageURL %s to database", (*i).ascii().data());
725 m_pageURLsPendingAddition.clear();
727 // Then we'll do the pending deletions
728 // First lets wipe all the pageURLs
729 for (i = m_pageURLsPendingDeletion.begin(), end = m_pageURLsPendingDeletion.end(); i != end; ++i) {
731 LOG(IconDatabase, "Deleted PageURL %s", (*i).ascii().data());
733 m_pageURLsPendingDeletion.clear();
735 // Then get rid of all traces of the icons and IconURLs
737 for (i = m_iconURLsPendingDeletion.begin(), end = m_iconURLsPendingDeletion.end(); i != end; ++i) {
738 // Forget the IconDataCache
739 icon = m_iconURLToIconDataCacheMap.get(*i);
741 m_iconURLToIconDataCacheMap.remove(*i);
744 // Forget the IconURL from the database
745 forgetIconForIconURLFromDatabase(*i);
746 LOG(IconDatabase, "Deleted icon %s", (*i).ascii().data());
748 m_iconURLsPendingDeletion.clear();
750 // If the timer was running to cause this update, we can kill the timer as its firing would be redundant
751 m_updateTimer.stop();
754 timestamp = currentTime() - timestamp;
755 if (timestamp <= 1.0)
756 LOG(IconDatabase, "Updating the database took %.4f seconds", timestamp);
758 LOG(IconDatabase, "Updating the database took %.4f seconds - this is much too long!", timestamp);
762 bool IconDatabase::hasEntryForIconURL(const String& iconURL)
764 if (iconURL.isEmpty())
767 // First check the in memory mapped icons...
768 if (m_iconURLToIconDataCacheMap.contains(iconURL))
771 // Then we'll check the main database
772 if (hasIconForIconURLQuery(m_mainDB, iconURL))
775 // Finally, the last resort - check the private browsing database
776 if (m_privateBrowsingEnabled)
777 if (hasIconForIconURLQuery(m_privateBrowsingDB, iconURL))
780 // We must not have this iconURL!
784 IconDatabase::~IconDatabase()
789 // readySQLStatement() handles two things
790 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade. This happens when the user
791 // switches to and from private browsing
792 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
793 inline void readySQLStatement(SQLStatement*& statement, SQLDatabase& db, const String& str)
795 if (statement && statement->database() != &db) {
800 statement = new SQLStatement(db, str);
801 statement->prepare();
805 // Any common IconDatabase query should be seperated into a fooQuery() and a *m_fooStatement.
806 // This way we can lazily construct the SQLStatment for a query on its first use, then reuse the Statement binding
807 // the new parameter as needed
808 // The statement must be deleted in IconDatabase::close() before the actual SQLDatabase::close() call
809 // Also, m_fooStatement must be reset() before fooQuery() returns otherwise we will constantly get "database file locked"
810 // errors in various combinations of queries
812 bool IconDatabase::pageURLTableIsEmptyQuery(SQLDatabase& db)
814 // We won't make this use a m_fooStatement because its not really a "common" query
815 return !SQLStatement(db, "SELECT iconID FROM PageURL LIMIT 1;").returnsAtLeastOneResult();
818 void IconDatabase::imageDataForIconURLQuery(SQLDatabase& db, const String& iconURL, Vector<unsigned char>& result)
820 readySQLStatement(m_imageDataForIconURLStatement, db, "SELECT Icon.data FROM Icon WHERE Icon.url = (?);");
821 m_imageDataForIconURLStatement->bindText16(1, iconURL, false);
822 m_imageDataForIconURLStatement->step();
823 m_imageDataForIconURLStatement->getColumnBlobAsVector(0, result);
824 m_imageDataForIconURLStatement->reset();
827 int IconDatabase::timeStampForIconURLQuery(SQLDatabase& db, const String& iconURL)
829 readySQLStatement(m_timeStampForIconURLStatement, db, "SELECT Icon.stamp FROM Icon WHERE Icon.url = (?);");
830 m_timeStampForIconURLStatement->bindText16(1, iconURL, false);
831 m_timeStampForIconURLStatement->step();
832 int result = m_timeStampForIconURLStatement->getColumnInt(0);
833 m_timeStampForIconURLStatement->reset();
837 String IconDatabase::iconURLForPageURLQuery(SQLDatabase& db, const String& pageURL)
839 readySQLStatement(m_iconURLForPageURLStatement, db, "SELECT Icon.url FROM Icon, PageURL WHERE PageURL.url = (?) AND Icon.iconID = PageURL.iconID;");
840 m_iconURLForPageURLStatement->bindText16(1, pageURL, false);
841 m_iconURLForPageURLStatement->step();
842 String result = m_iconURLForPageURLStatement->getColumnText16(0);
843 m_iconURLForPageURLStatement->reset();
847 void IconDatabase::forgetPageURLQuery(SQLDatabase& db, const String& pageURL)
849 readySQLStatement(m_forgetPageURLStatement, db, "DELETE FROM PageURL WHERE url = (?);");
850 m_forgetPageURLStatement->bindText16(1, pageURL, false);
851 m_forgetPageURLStatement->step();
852 m_forgetPageURLStatement->reset();
855 void IconDatabase::setIconIDForPageURLQuery(SQLDatabase& db, int64_t iconID, const String& pageURL)
857 readySQLStatement(m_setIconIDForPageURLStatement, db, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
858 m_setIconIDForPageURLStatement->bindText16(1, pageURL, false);
859 m_setIconIDForPageURLStatement->bindInt64(2, iconID);
860 m_setIconIDForPageURLStatement->step();
861 m_setIconIDForPageURLStatement->reset();
864 int64_t IconDatabase::getIconIDForIconURLQuery(SQLDatabase& db, const String& iconURL)
866 readySQLStatement(m_getIconIDForIconURLStatement, db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = (?);");
867 m_getIconIDForIconURLStatement->bindText16(1, iconURL, false);
868 m_getIconIDForIconURLStatement->step();
869 int64_t result = m_getIconIDForIconURLStatement->getColumnInt64(0);
870 m_getIconIDForIconURLStatement->reset();
874 int64_t IconDatabase::addIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
876 readySQLStatement(m_addIconForIconURLStatement, db, "INSERT INTO Icon (url) VALUES ((?));");
877 m_addIconForIconURLStatement->bindText16(1, iconURL, false);
879 if (m_addIconForIconURLStatement->step() == SQLITE_OK)
880 result = db.lastInsertRowID();
881 m_addIconForIconURLStatement->reset();
885 bool IconDatabase::hasIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
887 readySQLStatement(m_hasIconForIconURLStatement, db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = (?);");
888 m_hasIconForIconURLStatement->bindText16(1, iconURL, false);
889 bool result = m_hasIconForIconURLStatement->step() == SQLITE_ROW;
890 m_hasIconForIconURLStatement->reset();
894 } //namespace WebCore