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"
31 #include "PlatformString.h"
34 #include <sys/types.h>
38 // 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
39 // multiple strings and numbers should be handled differently than with String + String + String + etc.
43 IconDatabase* IconDatabase::m_sharedInstance = 0;
45 // This version number is in the DB and marks the current generation of the schema
46 // Theoretically once the switch is flipped this should never change
47 // Currently, an out-of-date schema causes the DB to be wiped and reset. This isn't
48 // so bad during development but in the future, we would need to write a conversion
49 // function to advance older released schemas to "current"
50 const int IconDatabase::currentDatabaseVersion = 4;
52 // Icons expire once a day
53 const int IconDatabase::iconExpirationTime = 60*60*24;
54 // Absent icons are rechecked once a week
55 const int IconDatabase::missingIconExpirationTime = 60*60*24*7;
57 const int IconDatabase::updateTimerDelay = 5;
59 const String& IconDatabase::defaultDatabaseFilename()
61 static String defaultDatabaseFilename = "icon.db";
62 return defaultDatabaseFilename;
65 // Query - Checks for at least 1 entry in the PageURL table
66 bool pageURLTableIsEmptyQuery(SQLDatabase&);
67 // Query - Returns the time stamp for an Icon entry
68 int timeStampForIconURLQuery(SQLDatabase&, const String& iconURL);
69 // Query - Returns the IconURL for a PageURL
70 String iconURLForPageURLQuery(SQLDatabase&, const String& pageURL);
71 // Query - Checks for the existence of the given IconURL in the Icon table
72 bool hasIconForIconURLQuery(SQLDatabase& db, const String& iconURL);
73 // Query - Deletes a PageURL from the PageURL table
74 void forgetPageURLQuery(SQLDatabase& db, const String& pageURL);
75 // Query - Sets the Icon.iconID for a PageURL in the PageURL table
76 void setIconIDForPageURLQuery(SQLDatabase& db, int64_t, const String&);
77 // Query - Returns the iconID for the given IconURL
78 int64_t getIconIDForIconURLQuery(SQLDatabase& db, const String& iconURL);
79 // Query - Creates the Icon entry for the given IconURL and returns the resulting iconID
80 int64_t addIconForIconURLQuery(SQLDatabase& db, const String& iconURL);
81 // Query - Returns the image data from the given database for the given IconURL
82 Vector<unsigned char> imageDataForIconURLQuery(SQLDatabase& db, const String& iconURL);
84 IconDatabase* IconDatabase::sharedIconDatabase()
86 if (!m_sharedInstance) {
87 m_sharedInstance = new IconDatabase();
89 return m_sharedInstance;
92 IconDatabase::IconDatabase()
93 : m_currentDB(&m_mainDB)
94 , m_privateBrowsingEnabled(false)
95 , m_startupTimer(this, &IconDatabase::pruneUnretainedIconsOnStartup)
96 , m_updateTimer(this, &IconDatabase::updateDatabase)
101 bool IconDatabase::open(const String& databasePath)
104 LOG_ERROR("Attempt to reopen the IconDatabase which is already open. Must close it first.");
108 // First we'll formulate the full path for the database file
110 if (databasePath[databasePath.length()] == '/')
111 dbFilename = databasePath + defaultDatabaseFilename();
113 dbFilename = databasePath + "/" + defaultDatabaseFilename();
115 // Now, we'll see if we can open the on-disk table
116 // If we can't, this ::open() failed and we should bail now
117 if (!m_mainDB.open(dbFilename)) {
118 LOG(IconDatabase, "Unable to open icon database at path %s", dbFilename.ascii().data());
122 if (!isValidDatabase(m_mainDB)) {
123 LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", dbFilename.ascii().data());
124 clearDatabaseTables(m_mainDB);
125 createDatabaseTables(m_mainDB);
128 // 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
130 result = m_mainDB.executeCommand("CREATE TEMP TABLE PageRetain (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,count INTEGER NOT NULL ON CONFLICT FAIL);");
131 // Creating an in-memory temp table should never, ever, ever fail
134 // These are actually two different SQLite config options - not my fault they are named confusingly ;)
135 m_mainDB.setSynchronous(SQLDatabase::SyncOff);
136 m_mainDB.setFullsync(false);
138 // Open the in-memory table for private browsing
139 if (!m_privateBrowsingDB.open(":memory:"))
140 LOG_ERROR("Unabled to open in-memory database for private browsing - %s", m_privateBrowsingDB.lastErrorMsg());
142 // Only if we successfully remained open will we start our "initial purge timer"
143 // rdar://4690949 - when we have deferred reads and writes all the way in, the prunetimer
144 // will become "deferredTimer" or something along those lines, and will be set only when
145 // a deferred read/write is queued
147 m_startupTimer.startOneShot(0);
152 void IconDatabase::close()
156 m_privateBrowsingDB.close();
159 bool IconDatabase::isEmpty()
161 if (m_privateBrowsingEnabled)
162 if (!pageURLTableIsEmptyQuery(m_privateBrowsingDB))
165 return pageURLTableIsEmptyQuery(m_mainDB);
168 bool IconDatabase::isValidDatabase(SQLDatabase& db)
170 // These two tables should always exist in a valid db
171 if (!db.tableExists("Icon") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
174 if (SQLStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0) < currentDatabaseVersion) {
175 LOG(IconDatabase, "DB version is not found or below expected valid version");
182 void IconDatabase::clearDatabaseTables(SQLDatabase& db)
184 String query = "SELECT name FROM sqlite_master WHERE type='table';";
185 Vector<String> tables;
186 if (!SQLStatement(db, query).returnTextResults16(0, tables)) {
187 LOG(IconDatabase, "Unable to retrieve list of tables from database");
191 for (Vector<String>::iterator table = tables.begin(); table != tables.end(); ++table ) {
192 if (!db.executeCommand("DROP TABLE " + *table)) {
193 LOG(IconDatabase, "Unable to drop table %s", (*table).ascii().data());
198 void IconDatabase::createDatabaseTables(SQLDatabase& db)
200 if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
201 LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
205 if (!db.executeCommand("CREATE TABLE Icon (iconID INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE ON CONFLICT FAIL, stamp INTEGER, data BLOB);")) {
206 LOG_ERROR("Could not create Icon table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
210 if (!db.executeCommand("CREATE TRIGGER update_icon_timestamp AFTER UPDATE ON Icon BEGIN UPDATE Icon SET stamp = strftime('%s','now') WHERE iconID = new.iconID; END;")) {
211 LOG_ERROR("Could not create timestamp updater in database (%i) - %s", db.lastError(), db.lastErrorMsg());
215 if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
216 LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
220 if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
221 LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
227 Vector<unsigned char> IconDatabase::imageDataForIconURL(const String& iconURL)
229 // If private browsing is enabled, we'll check there first as the most up-to-date data for an icon will be there
230 if (m_privateBrowsingEnabled) {
231 Vector<unsigned char> blob = imageDataForIconURLQuery(m_privateBrowsingDB, iconURL);
236 // It wasn't found there, so lets check the main tables
237 return imageDataForIconURLQuery(m_mainDB, iconURL);
240 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
242 if (m_privateBrowsingEnabled == flag)
245 // Sync any deferred DB changes before we change the active DB
248 m_privateBrowsingEnabled = flag;
250 if (m_privateBrowsingEnabled) {
251 createDatabaseTables(m_privateBrowsingDB);
252 m_currentDB = &m_privateBrowsingDB;
254 clearDatabaseTables(m_privateBrowsingDB);
255 m_currentDB = &m_mainDB;
259 Image* IconDatabase::iconForPageURL(const String& pageURL, const IntSize& size, bool cache)
261 // See if we even have an IconURL for this PageURL...
262 String iconURL = iconURLForPageURL(pageURL);
263 if (iconURL.isEmpty())
266 // If we do, maybe we have a SiteIcon for this IconURL
267 if (m_iconURLToSiteIcons.contains(iconURL)) {
268 SiteIcon* icon = m_iconURLToSiteIcons.get(iconURL);
269 return icon->getImage(size);
272 // If we don't have either, we have to create the SiteIcon
273 SiteIcon* icon = new SiteIcon(iconURL);
274 m_iconURLToSiteIcons.set(iconURL, icon);
275 return icon->getImage(size);
278 // FIXME 4667425 - this check needs to see if the icon's data is empty or not and apply
279 // iconExpirationTime to present icons, and missingIconExpirationTime for missing icons
280 bool IconDatabase::isIconExpiredForIconURL(const String& iconURL)
282 if (iconURL.isEmpty())
286 if (m_privateBrowsingEnabled) {
287 stamp = timeStampForIconURLQuery(m_privateBrowsingDB, iconURL);
289 return (time(NULL) - stamp) > iconExpirationTime;
292 stamp = timeStampForIconURLQuery(m_mainDB, iconURL);
294 return (time(NULL) - stamp) > iconExpirationTime;
298 String IconDatabase::iconURLForPageURL(const String& pageURL)
300 if (pageURL.isEmpty())
303 if (m_pageURLToIconURLMap.contains(pageURL))
304 return m_pageURLToIconURLMap.get(pageURL);
306 // Try the private browsing database because if any PageURL's IconURL was updated during privated browsing,
307 // the most up-to-date iconURL would be there
308 if (m_privateBrowsingEnabled) {
309 String iconURL = iconURLForPageURLQuery(m_privateBrowsingDB, pageURL);
310 if (!iconURL.isEmpty()) {
311 m_pageURLToIconURLMap.set(pageURL, iconURL);
316 String iconURL = iconURLForPageURLQuery(m_mainDB, pageURL);
317 if (!iconURL.isEmpty())
318 m_pageURLToIconURLMap.set(pageURL, iconURL);
322 Image* IconDatabase::defaultIcon(const IntSize& size)
327 void IconDatabase::retainIconForPageURL(const String& pageURL)
329 if (pageURL.isEmpty())
334 // If we don't have the retain count for this page, we need to setup records of its retain
335 // Otherwise, get the count and increment it
336 if (!m_pageURLToRetainCount.contains(pageURL)) {
337 // If this pageURL is marked for deletion, bring it back from the brink
338 m_pageURLsPendingDeletion.remove(pageURL);
340 // The new retain count will be 1
343 // If we have an iconURL for this pageURL, we'll now retain the iconURL
344 String iconURL = iconURLForPageURL(pageURL);
345 if (!iconURL.isEmpty())
346 retainIconURL(iconURL);
349 retainCount = m_pageURLToRetainCount.get(pageURL) + 1;
351 m_pageURLToRetainCount.set(pageURL, retainCount);
354 void IconDatabase::releaseIconForPageURL(const String& pageURL)
356 if (pageURL.isEmpty())
359 // Check if this pageURL is actually retained
360 if(!m_pageURLToRetainCount.contains(pageURL)) {
361 LOG_ERROR("Attempting to release icon for URL %s which is not retained", pageURL.ascii().data());
365 // Get its retain count
366 int retainCount = m_pageURLToRetainCount.get(pageURL);
367 ASSERT(retainCount > 0);
369 // If it still has a positive retain count, store the new count and bail
371 m_pageURLToRetainCount.set(pageURL, retainCount);
375 LOG(IconDatabase, "No more retainers for PageURL %s", pageURL.ascii().data());
377 // Otherwise, remove all record of the retain count
378 m_pageURLToRetainCount.remove(pageURL);
380 // Then mark this pageURL for deletion
381 m_pageURLsPendingDeletion.add(pageURL);
383 // Grab the iconURL and release it
384 String iconURL = iconURLForPageURL(pageURL);
385 if (!iconURL.isEmpty())
386 releaseIconURL(iconURL);
389 void IconDatabase::retainIconURL(const String& iconURL)
391 ASSERT(!iconURL.isEmpty());
393 if (m_iconURLToRetainCount.contains(iconURL)) {
394 ASSERT(m_iconURLToRetainCount.get(iconURL) > 0);
395 m_iconURLToRetainCount.set(iconURL, m_iconURLToRetainCount.get(iconURL) + 1);
397 m_iconURLToRetainCount.set(iconURL, 1);
398 if (m_iconURLsPendingDeletion.contains(iconURL))
399 m_iconURLsPendingDeletion.remove(iconURL);
403 void IconDatabase::releaseIconURL(const String& iconURL)
405 ASSERT(!iconURL.isEmpty());
407 // If the iconURL has no retain count, we can bail
408 if (!m_iconURLToRetainCount.contains(iconURL))
411 // Otherwise, decrement it
412 int retainCount = m_iconURLToRetainCount.get(iconURL) - 1;
413 ASSERT(retainCount > -1);
415 // If the icon is still retained, store the count and bail
417 m_iconURLToRetainCount.set(iconURL, retainCount);
421 LOG(IconDatabase, "No more retainers for IconURL %s", iconURL.ascii().data());
423 // Otherwise, this icon is toast. Remove all traces of its retain count...
424 m_iconURLToRetainCount.remove(iconURL);
426 // And since we delay the actual deletion of icons, so lets add it to that queue
427 m_iconURLsPendingDeletion.add(iconURL);
430 void IconDatabase::forgetPageURL(const String& pageURL)
432 // Remove the PageURL->IconURL mapping
433 m_pageURLToIconURLMap.remove(pageURL);
435 // And remove this pageURL from the DB
436 forgetPageURLQuery(*m_currentDB, pageURL);
439 bool IconDatabase::isIconURLRetained(const String& iconURL)
441 if (iconURL.isEmpty())
444 return m_iconURLToRetainCount.contains(iconURL);
447 void IconDatabase::forgetIconForIconURLFromDatabase(const String& iconURL)
449 if (iconURL.isEmpty())
452 // For private browsing safety, since this alters the database, we only forget from the current database
453 // If we're in private browsing and the Icon also exists in the main database, it will be pruned on the next startup
454 int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL, false);
456 // If we didn't actually have an icon for this iconURL... well, thats a screwy condition we should track down, but also
457 // something we could move on from
460 LOG_ERROR("Attempting to forget icon for IconURL %s, though we don't have it in the database", iconURL.ascii().data());
464 String escapedIconURL = iconURL;
465 escapedIconURL.replace('\'', "''");
467 if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM Icon WHERE Icon.iconID = %lli;", iconID)))
468 LOG_ERROR("Unable to drop Icon for IconURL", iconURL.ascii().data());
469 if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM PageURL WHERE PageURL.iconID = %lli", iconID)))
470 LOG_ERROR("Unable to drop all PageURL for IconURL", iconURL.ascii().data());
473 void IconDatabase::setIconDataForIconURL(const void* data, int size, const String& iconURL)
481 if (iconURL.isEmpty())
484 // First, if we already have a SiteIcon in memory, let's update its image data
485 if (m_iconURLToSiteIcons.contains(iconURL))
486 m_iconURLToSiteIcons.get(iconURL)->manuallySetImageData((unsigned char*)data, size);
488 // Next, we actually commit the image data to the database
489 // Start by making sure there's an entry for this IconURL in the current database
490 int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL, true);
493 // First we create and prepare the SQLStatement
494 // The following statement also works to set the icon data to NULL because sqlite defaults unbound ? parameters to NULL
495 SQLStatement sql(*m_currentDB, "UPDATE Icon SET data = ? WHERE iconID = ?;");
498 // Then we bind the icondata and iconID to the SQLStatement
500 sql.bindBlob(1, data, size);
501 sql.bindInt64(2, iconID);
503 // Finally we step and make sure the step was successful
504 if (sql.step() != SQLITE_DONE)
505 LOG_ERROR("Unable to set icon data for iconURL %s", iconURL.ascii().data());
510 void IconDatabase::setHaveNoIconForIconURL(const String& iconURL)
512 setIconDataForIconURL(0, 0, iconURL);
515 void IconDatabase::setIconURLForPageURL(const String& iconURL, const String& pageURL)
517 ASSERT(!iconURL.isEmpty());
518 ASSERT(!pageURL.isEmpty());
520 // If the urls already map to each other, bail.
521 // This happens surprisingly often, and seems to cream iBench performance
522 if (m_pageURLToIconURLMap.get(pageURL) == iconURL)
525 // If this pageURL is retained, we have some work to do on the IconURL retain counts
526 if (m_pageURLToRetainCount.contains(pageURL)) {
527 String oldIconURL = m_pageURLToIconURLMap.get(pageURL);
528 if (!oldIconURL.isEmpty())
529 releaseIconURL(oldIconURL);
530 retainIconURL(iconURL);
532 // If this pageURL is *not* retained, then we may be marking it for deletion, as well!
533 // As counterintuitive as it seems to mark it for addition and for deletion at the same time,
534 // it's valid because when we do a new pageURL->iconURL mapping we *have* to mark it for addition,
535 // no matter what, as there is no efficient was to determine if the mapping is in the DB already.
536 // But, if the iconURL is marked for deletion, we'll also mark this pageURL for deletion - if a
537 // client comes along and retains it before the timer fires, the "pendingDeletion" lists will
538 // be manipulated appopriately and new pageURL will be brought back from the brink
539 if (m_iconURLsPendingDeletion.contains(iconURL))
540 m_pageURLsPendingDeletion.add(pageURL);
543 // Cache the pageURL->iconURL map
544 m_pageURLToIconURLMap.set(pageURL, iconURL);
546 // And mark this mapping to be added to the database
547 m_pageURLsPendingAddition.set(pageURL, iconURL);
549 // Then start the timer to commit this change - or further delay the timer if it
550 // was already started
551 m_updateTimer.startOneShot(updateTimerDelay);
554 void IconDatabase::setIconURLForPageURLInDatabase(const String& iconURL, const String& pageURL)
556 int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL);
558 LOG_ERROR("Failed to establish an ID for iconURL %s", iconURL.ascii().data());
561 setIconIDForPageURLQuery(*m_currentDB, iconID, pageURL);
564 int64_t IconDatabase::establishIconIDForIconURL(SQLDatabase& db, const String& iconURL, bool createIfNecessary)
566 String escapedIconURL = iconURL;
567 escapedIconURL.replace('\'', "''");
569 // Get the iconID thats already in this database and return it - or return 0 if we're read-only
570 int64_t iconID = getIconIDForIconURLQuery(db, iconURL);
571 if (iconID || !createIfNecessary)
574 // Create the icon table entry for the iconURL
575 return addIconForIconURLQuery(db, iconURL);
578 void IconDatabase::pruneUnreferencedIcons(int numberToPrune)
580 if (!numberToPrune || !isOpen())
583 if (numberToPrune > 0) {
584 if (!m_mainDB.executeCommand(String::sprintf("DELETE FROM Icon WHERE Icon.iconID IN (SELECT Icon.iconID FROM Icon WHERE Icon.iconID NOT IN(SELECT PageURL.iconID FROM PageURL) LIMIT %i);", numberToPrune)))
585 LOG_ERROR("Failed to prune %i unreferenced icons from the DB - %s", numberToPrune, m_mainDB.lastErrorMsg());
587 if (!m_mainDB.executeCommand("DELETE FROM Icon WHERE Icon.iconID IN (SELECT Icon.iconID FROM Icon WHERE Icon.iconID NOT IN(SELECT PageURL.iconID FROM PageURL));"))
588 LOG_ERROR("Failed to prune all unreferenced icons from the DB - %s", m_mainDB.lastErrorMsg());
592 void IconDatabase::pruneUnretainedIconsOnStartup(Timer<IconDatabase>*)
598 CFTimeInterval start = CFAbsoluteTimeGetCurrent();
601 // FIXME - rdar://4690949 - Need to prune unretained pageURLs here
602 LOG_ERROR("pruneUnretainedIconsOnStartup is still unimplemented");
603 pruneUnreferencedIcons(-1);
606 CFTimeInterval duration = CFAbsoluteTimeGetCurrent() - start;
607 LOG(IconDatabase, "Pruning unretained icons took %.4f seconds", duration);
609 LOG_ERROR("Pruning unretained icons took %.4f seconds - this is much too long!", duration);
613 void IconDatabase::updateDatabase(Timer<IconDatabase>*)
618 void IconDatabase::syncDatabase()
621 CFTimeInterval start = CFAbsoluteTimeGetCurrent();
624 // First we'll do the pending additions
625 for (HashMap<String, String>::iterator i = m_pageURLsPendingAddition.begin(); i != m_pageURLsPendingAddition.end(); ++i) {
626 // The HashMap maps PageURL->IconURL, but the method takes IconURL then PageURL - so this ordering is correct
627 setIconURLForPageURLInDatabase(i->second, i->first);
628 LOG(IconDatabase, "Commited IconURL %s for PageURL %s to database", (i->second).ascii().data(), (i->first).ascii().data());
630 m_pageURLsPendingAddition.clear();
632 // Then we'll do the pending deletions
633 // First lets wipe all the pageURLs
634 HashSet<String>::iterator i = m_pageURLsPendingDeletion.begin();
635 for (; i != m_pageURLsPendingDeletion.end(); ++i) {
637 LOG(IconDatabase, "Deleted PageURL %s", (*i).ascii().data());
639 m_pageURLsPendingDeletion.clear();
641 // Then get rid of all traces of the icons and IconURLs
643 for (i = m_iconURLsPendingDeletion.begin(); i != m_iconURLsPendingDeletion.end(); ++i) {
644 // Forget the SiteIcon
645 icon = m_iconURLToSiteIcons.get(*i);
646 m_iconURLToSiteIcons.remove(*i);
649 // Forget the IconURL from the database
650 forgetIconForIconURLFromDatabase(*i);
651 LOG(IconDatabase, "Deleted icon %s", (*i).ascii().data());
653 m_iconURLsPendingDeletion.clear();
655 // If the timer was running to cause this update, we can kill the timer as its firing would be redundant
656 m_updateTimer.stop();
659 CFTimeInterval duration = CFAbsoluteTimeGetCurrent() - start;
660 LOG(IconDatabase, "Updating the database took %.4f seconds", duration);
662 LOG_ERROR("Updating the database took %.4f seconds - this is much too long!", duration);
666 bool IconDatabase::hasEntryForIconURL(const String& iconURL)
668 if (iconURL.isEmpty())
671 // First check the in memory mapped icons...
672 if (m_iconURLToSiteIcons.contains(iconURL))
675 // Then we'll check the main database
676 if (hasIconForIconURLQuery(m_mainDB, iconURL))
679 // Finally, the last resort - check the private browsing database
680 if (m_privateBrowsingEnabled)
681 if (hasIconForIconURLQuery(m_privateBrowsingDB, iconURL))
684 // We must not have this iconURL!
688 IconDatabase::~IconDatabase()
693 // Query helper functions
694 bool pageURLTableIsEmptyQuery(SQLDatabase& db)
696 return !(SQLStatement(db, "SELECT iconID FROM PageURL LIMIT 1;").returnsAtLeastOneResult());
699 Vector<unsigned char> imageDataForIconURLQuery(SQLDatabase& db, const String& iconURL)
701 String escapedIconURL = iconURL;
702 escapedIconURL.replace('\'', "''");
703 return SQLStatement(db, "SELECT Icon.data FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").getColumnBlobAsVector(0);
706 int timeStampForIconURLQuery(SQLDatabase& db, const String& iconURL)
708 String escapedIconURL = iconURL;
709 escapedIconURL.replace('\'', "''");
710 return SQLStatement(db, "SELECT Icon.stamp FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").getColumnInt(0);
714 String iconURLForPageURLQuery(SQLDatabase& db, const String& pageURL)
716 String escapedPageURL = pageURL;
717 escapedPageURL.replace('\'', "''");
718 return SQLStatement(db, "SELECT Icon.url FROM Icon, PageURL WHERE PageURL.url = '" + escapedPageURL + "' AND Icon.iconID = PageURL.iconID").getColumnText16(0);
721 void forgetPageURLQuery(SQLDatabase& db, const String& pageURL)
723 String escapedPageURL = pageURL;
724 escapedPageURL.replace('\'', "''");
726 db.executeCommand("DELETE FROM PageURL WHERE url = '" + escapedPageURL + "';");
729 void setIconIDForPageURLQuery(SQLDatabase& db, int64_t iconID, const String& pageURL)
731 String escapedPageURL = pageURL;
732 escapedPageURL.replace('\'', "''");
733 if (!db.executeCommand("INSERT INTO PageURL (url, iconID) VALUES ('" + escapedPageURL + "', " + String::number(iconID) + ");"))
734 LOG_ERROR("Failed to set iconid %lli for PageURL %s", iconID, pageURL.ascii().data());
737 int64_t getIconIDForIconURLQuery(SQLDatabase& db, const String& iconURL)
739 String escapedIconURL = iconURL;
740 escapedIconURL.replace('\'', "''");
741 return SQLStatement(db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").getColumnInt64(0);
744 int64_t addIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
746 String escapedIconURL = iconURL;
747 escapedIconURL.replace('\'', "''");
748 if (db.executeCommand("INSERT INTO Icon (url) VALUES ('" + escapedIconURL + "');"))
749 return db.lastInsertRowID();
753 bool hasIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
755 String escapedIconURL = iconURL;
756 escapedIconURL.replace('\'', "''");
757 return SQLStatement(db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").returnsAtLeastOneResult();
760 } //namespace WebCore