bcfade519065c9c552e4b7c28bbc29de8e88f8f3
[WebKit-https.git] / WebCore / loader / icon / IconDatabase.cpp
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
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.
12  *
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. 
24  */
25
26 #include "config.h"
27 #include "IconDatabase.h"
28
29 #include "Image.h"
30 #include "Logging.h"
31 #include "PlatformString.h"
32 #include <errno.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <time.h>
36
37
38 // FIXME - Make sure we put a private browsing consideration in that uses the temporary tables anytime private browsing would be an issue.
39
40 // 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
41 // multiple strings and numbers should be handled differently than with String + String + String + etc.
42
43 namespace WebCore {
44
45 IconDatabase* IconDatabase::m_sharedInstance = 0;
46
47 // This version number is in the DB and marks the current generation of the schema
48 // Theoretically once the switch is flipped this should never change
49 // Currently, an out-of-date schema causes the DB to be wiped and reset.  This isn't 
50 // so bad during development but in the future, we would need to write a conversion
51 // function to advance older released schemas to "current"
52 const int IconDatabase::currentDatabaseVersion = 4;
53
54 // Icons expire once a day
55 const int IconDatabase::iconExpirationTime = 60*60*24; 
56 // Absent icons are rechecked once a week
57 const int IconDatabase::missingIconExpirationTime = 60*60*24*7; 
58
59 const String& IconDatabase::defaultDatabaseFilename()
60 {
61     static String defaultDatabaseFilename = "icon.db";
62     return defaultDatabaseFilename;
63 }
64
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);   
83
84 IconDatabase* IconDatabase::sharedIconDatabase()
85 {
86     if (!m_sharedInstance) {
87         m_sharedInstance = new IconDatabase();
88     }
89     return m_sharedInstance;
90 }
91
92 IconDatabase::IconDatabase()
93     : m_currentDB(&m_mainDB)
94     , m_privateBrowsingEnabled(false)
95     , m_startupTimer(this, &IconDatabase::pruneUnretainedIconsOnStartup)
96     , m_pruneTimer(this, &IconDatabase::pruneIconsPendingDeletion)
97 {
98     
99 }
100
101 bool IconDatabase::open(const String& databasePath)
102 {
103     if (isOpen()) {
104         LOG_ERROR("Attempt to reopen the IconDatabase which is already open.  Must close it first.");
105         return false;
106     }
107     
108     // First we'll formulate the full path for the database file
109     String dbFilename;
110     if (databasePath[databasePath.length()] == '/')
111         dbFilename = databasePath + defaultDatabaseFilename();
112     else
113         dbFilename = databasePath + "/" + defaultDatabaseFilename();
114
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());
119         return false;
120     }
121     
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);
126     }
127
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
129     bool result;
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
132     ASSERT(result);
133     
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);
137     
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());
141
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
146     if (isOpen()) {
147         m_startupTimer.startOneShot(0);
148         m_pruneTimer.startRepeating(10);
149     }
150     
151     return isOpen();
152 }
153
154 void IconDatabase::close()
155 {
156     m_mainDB.close();
157     m_privateBrowsingDB.close();
158 }
159
160 bool IconDatabase::isEmpty()
161 {
162     if (m_privateBrowsingEnabled)
163         if (!pageURLTableIsEmptyQuery(m_privateBrowsingDB))
164             return false;
165             
166     return pageURLTableIsEmptyQuery(m_mainDB);
167 }
168
169 bool IconDatabase::isValidDatabase(SQLDatabase& db)
170 {
171     // These two tables should always exist in a valid db
172     if (!db.tableExists("Icon") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
173         return false;
174     
175     if (SQLStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0) < currentDatabaseVersion) {
176         LOG(IconDatabase, "DB version is not found or below expected valid version");
177         return false;
178     }
179     
180     return true;
181 }
182
183 void IconDatabase::clearDatabaseTables(SQLDatabase& db)
184 {
185     String query = "SELECT name FROM sqlite_master WHERE type='table';";
186     Vector<String> tables;
187     if (!SQLStatement(db, query).returnTextResults16(0, tables)) {
188         LOG(IconDatabase, "Unable to retrieve list of tables from database");
189         return;
190     }
191     
192     for (Vector<String>::iterator table = tables.begin(); table != tables.end(); ++table ) {
193         if (!db.executeCommand("DROP TABLE " + *table)) {
194             LOG(IconDatabase, "Unable to drop table %s", (*table).ascii().data());
195         }
196     }
197 }
198
199 void IconDatabase::createDatabaseTables(SQLDatabase& db)
200 {
201     if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
202         LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
203         db.close();
204         return;
205     }
206     if (!db.executeCommand("CREATE TABLE Icon (iconID INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE ON CONFLICT FAIL, stamp INTEGER, data BLOB);")) {
207         LOG_ERROR("Could not create Icon table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
208         db.close();
209         return;
210     }
211     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;")) {
212         LOG_ERROR("Could not create timestamp updater in database (%i) - %s", db.lastError(), db.lastErrorMsg());
213         db.close();
214         return;
215     }
216     if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
217         LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
218         db.close();
219         return;
220     }
221     if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
222         LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
223         db.close();
224         return;
225     }
226 }    
227
228 Vector<unsigned char> IconDatabase::imageDataForIconURL(const String& iconURL)
229 {      
230     // If private browsing is enabled, we'll check there first as the most up-to-date data for an icon will be there
231     if (m_privateBrowsingEnabled) {    
232         Vector<unsigned char> blob = imageDataForIconURLQuery(m_privateBrowsingDB, iconURL);
233         if (!blob.isEmpty())
234             return blob;
235     } 
236     
237     // It wasn't found there, so lets check the main tables
238     return imageDataForIconURLQuery(m_mainDB, iconURL);
239 }
240
241 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
242 {
243     if (m_privateBrowsingEnabled == flag)
244         return;
245     
246     m_privateBrowsingEnabled = flag;
247     
248     if (m_privateBrowsingEnabled) {
249         createDatabaseTables(m_privateBrowsingDB);
250         m_currentDB = &m_privateBrowsingDB;
251     } else {
252         clearDatabaseTables(m_privateBrowsingDB);
253         m_currentDB = &m_mainDB;
254     }
255 }
256
257 Image* IconDatabase::iconForPageURL(const String& pageURL, const IntSize& size, bool cache)
258 {   
259     // See if we even have an IconURL for this PageURL...
260     String iconURL = iconURLForPageURL(pageURL);
261     if (iconURL.isEmpty())
262         return 0;
263     
264     // If we do, maybe we have a SiteIcon for this IconURL
265     if (m_iconURLToSiteIcons.contains(iconURL)) {
266         SiteIcon* icon = m_iconURLToSiteIcons.get(iconURL);
267         return icon->getImage(size);
268     }
269         
270     // If we don't have either, we have to create the SiteIcon
271     SiteIcon* icon = new SiteIcon(iconURL);
272     m_iconURLToSiteIcons.set(iconURL, icon);
273     return icon->getImage(size);
274 }
275
276 // FIXME 4667425 - this check needs to see if the icon's data is empty or not and apply
277 // iconExpirationTime to present icons, and missingIconExpirationTime for missing icons
278 bool IconDatabase::isIconExpiredForIconURL(const String& iconURL)
279 {
280     if (iconURL.isEmpty()) 
281         return true;
282     
283     int stamp;
284     if (m_privateBrowsingEnabled) {
285         stamp = timeStampForIconURLQuery(m_privateBrowsingDB, iconURL);
286         if (stamp)
287             return (time(NULL) - stamp) > iconExpirationTime;
288     }
289     
290     stamp = timeStampForIconURLQuery(m_mainDB, iconURL);
291     if (stamp)
292         return (time(NULL) - stamp) > iconExpirationTime;
293     return false;
294 }
295     
296 String IconDatabase::iconURLForPageURL(const String& pageURL)
297 {    
298     if (pageURL.isEmpty()) 
299         return String();
300         
301     if (m_pageURLToIconURLMap.contains(pageURL))
302         return m_pageURLToIconURLMap.get(pageURL);
303     
304     // Try the private browsing database because if any PageURL's IconURL was updated during privated browsing, 
305     // the most up-to-date iconURL would be there
306     if (m_privateBrowsingEnabled) {
307         String iconURL = iconURLForPageURLQuery(m_privateBrowsingDB, pageURL);
308         if (!iconURL.isEmpty()) {
309             m_pageURLToIconURLMap.set(pageURL, iconURL);
310             return iconURL;
311         }
312     }
313     
314     String iconURL = iconURLForPageURLQuery(m_mainDB, pageURL);
315     if (!iconURL.isEmpty())
316         m_pageURLToIconURLMap.set(pageURL, iconURL);
317     return iconURL;
318 }
319
320 Image* IconDatabase::defaultIcon(const IntSize& size)
321 {
322     return 0;
323 }
324
325 void IconDatabase::retainIconForPageURL(const String& pageURL)
326 {
327     if (pageURL.isEmpty())
328         return;
329         
330     int retainCount;
331     
332     // If we don't have the retain count for this page, we need to setup records of its retain
333     // Otherwise, get the count and increment it
334     if (!m_pageURLToRetainCount.contains(pageURL)) {
335         // If this pageURL is marked for deletion, bring it back from the brink
336         m_pageURLsPendingDeletion.remove(pageURL);
337         
338         // The new retain count will be 1
339         retainCount = 1;
340         
341          // If we have an iconURL for this pageURL, we'll now retain the iconURL
342         String iconURL = iconURLForPageURL(pageURL);
343         if (!iconURL.isEmpty())
344             retainIconURL(iconURL);
345
346     } else
347         retainCount = m_pageURLToRetainCount.get(pageURL) + 1;
348         
349     m_pageURLToRetainCount.set(pageURL, retainCount);        
350 }
351
352 void IconDatabase::releaseIconForPageURL(const String& pageURL)
353 {
354     if (pageURL.isEmpty())
355         return;
356         
357     // Check if this pageURL is actually retained
358     if(!m_pageURLToRetainCount.contains(pageURL)) {
359         LOG_ERROR("Attempting to release icon for URL %s which is not retained", pageURL.ascii().data());
360         return;
361     }
362     
363     // Get its retain count
364     int retainCount = m_pageURLToRetainCount.get(pageURL);
365     ASSERT(retainCount > 0);
366     
367     // If it still has a positive retain count, store the new count and bail
368     if (--retainCount) {
369         m_pageURLToRetainCount.set(pageURL, retainCount);
370         return;
371     }
372     
373     LOG(IconDatabase, "No more retainers for PageURL %s", pageURL.ascii().data());
374     
375     // Otherwise, remove all record of the retain count
376     m_pageURLToRetainCount.remove(pageURL);   
377     
378     // Then mark this pageURL for deletion
379     m_pageURLsPendingDeletion.add(pageURL);
380             
381     // Grab the iconURL and release it
382     String iconURL = iconURLForPageURL(pageURL);
383     if (!iconURL.isEmpty())
384         releaseIconURL(iconURL);
385 }
386
387 void IconDatabase::retainIconURL(const String& iconURL)
388 {
389     ASSERT(!iconURL.isEmpty());
390     
391     if (m_iconURLToRetainCount.contains(iconURL)) {
392         ASSERT(m_iconURLToRetainCount.get(iconURL) > 0);
393         m_iconURLToRetainCount.set(iconURL, m_iconURLToRetainCount.get(iconURL) + 1);
394     } else {
395         m_iconURLToRetainCount.set(iconURL, 1);
396         if (m_iconURLsPendingDeletion.contains(iconURL))
397             m_iconURLsPendingDeletion.remove(iconURL);
398     }
399 }
400
401 void IconDatabase::releaseIconURL(const String& iconURL)
402 {
403     ASSERT(!iconURL.isEmpty());
404     
405     // If the iconURL has no retain count, we can bail
406     if (!m_iconURLToRetainCount.contains(iconURL))
407         return;
408     
409     // Otherwise, decrement it
410     int retainCount = m_iconURLToRetainCount.get(iconURL) - 1;
411     ASSERT(retainCount > -1);
412     
413     // If the icon is still retained, store the count and bail
414     if (retainCount) {
415         m_iconURLToRetainCount.set(iconURL, retainCount);
416         return;
417     }
418     
419     LOG(IconDatabase, "No more retainers for IconURL %s", iconURL.ascii().data());
420     
421     // Otherwise, this icon is toast.  Remove all traces of its retain count...
422     m_iconURLToRetainCount.remove(iconURL);
423     
424     // And since we delay the actual deletion of icons, so lets add it to that queue
425     m_iconURLsPendingDeletion.add(iconURL);
426 }
427
428 void IconDatabase::forgetPageURL(const String& pageURL)
429 {
430     // Remove the PageURL->IconURL mapping
431     m_pageURLToIconURLMap.remove(pageURL);
432     
433     // And remove this pageURL from the DB
434     forgetPageURLQuery(*m_currentDB, pageURL);
435 }
436     
437 bool IconDatabase::isIconURLRetained(const String& iconURL)
438 {
439     if (iconURL.isEmpty())
440         return false;
441         
442     return m_iconURLToRetainCount.contains(iconURL);
443 }
444
445 void IconDatabase::forgetIconForIconURLFromDatabase(const String& iconURL)
446 {
447     if (iconURL.isEmpty())
448         return;
449
450     // For private browsing safety, since this alters the database, we only forget from the current database
451     // If we're in private browsing and the Icon also exists in the main database, it will be pruned on the next startup
452     int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL, false);
453     
454     // If we didn't actually have an icon for this iconURL... well, thats a screwy condition we should track down, but also 
455     // something we could move on from
456     ASSERT(iconID);
457     if (!iconID) {
458         LOG_ERROR("Attempting to forget icon for IconURL %s, though we don't have it in the database", iconURL.ascii().data());
459         return;
460     }
461         
462     String escapedIconURL = iconURL;
463     escapedIconURL.replace('\'', "''");
464     
465     if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM Icon WHERE Icon.iconID = %lli;", iconID)))
466         LOG_ERROR("Unable to drop Icon for IconURL", iconURL.ascii().data()); 
467     if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM PageURL WHERE PageURL.iconID = %lli", iconID)))
468         LOG_ERROR("Unable to drop all PageURL for IconURL", iconURL.ascii().data()); 
469 }
470
471 void IconDatabase::setIconDataForIconURL(const void* data, int size, const String& iconURL)
472 {
473     ASSERT(size > -1);
474     if (size)
475         ASSERT(data);
476     else
477         data = 0;
478         
479     if (iconURL.isEmpty())
480         return;
481     
482     // First, if we already have a SiteIcon in memory, let's update its image data
483     if (m_iconURLToSiteIcons.contains(iconURL))
484         m_iconURLToSiteIcons.get(iconURL)->manuallySetImageData((unsigned char*)data, size);
485
486     // Next, we actually commit the image data to the database
487     // Start by making sure there's an entry for this IconURL in the current database
488     int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL, true);
489     ASSERT(iconID);
490     
491     // First we create and prepare the SQLStatement
492     // The following statement also works to set the icon data to NULL because sqlite defaults unbound ? parameters to NULL
493     SQLStatement sql(*m_currentDB, "UPDATE Icon SET data = ? WHERE iconID = ?;");
494     sql.prepare();
495         
496     // Then we bind the icondata and iconID to the SQLStatement
497     if (data)
498         sql.bindBlob(1, data, size);
499     sql.bindInt64(2, iconID);
500     
501     // Finally we step and make sure the step was successful
502     if (sql.step() != SQLITE_DONE)
503         LOG_ERROR("Unable to set icon data for iconURL %s", iconURL.ascii().data());
504         
505     return;
506 }
507
508 void IconDatabase::setHaveNoIconForIconURL(const String& iconURL)
509 {   
510     setIconDataForIconURL(0, 0, iconURL);
511 }
512
513 void IconDatabase::setIconURLForPageURL(const String& iconURL, const String& pageURL)
514 {
515     ASSERT(!iconURL.isEmpty());
516     ASSERT(!pageURL.isEmpty());
517     
518     // If the urls already map to each other, bail.
519     // This happens surprisingly often, and seems to cream iBench performance
520     if (m_pageURLToIconURLMap.get(pageURL) == iconURL)
521         return;
522
523     // If this pageURL is retained, we have some work to do on the IconURL retain counts
524     if (m_pageURLToRetainCount.contains(pageURL)) {
525         String oldIconURL = m_pageURLToIconURLMap.get(pageURL);
526         if (!oldIconURL.isEmpty())
527             releaseIconURL(oldIconURL);
528         retainIconURL(iconURL);
529     }
530     
531     // Cache the pageURL->iconURL map
532     m_pageURLToIconURLMap.set(pageURL, iconURL);
533
534     // Store it in the database
535     int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL);
536     if (!iconID) {
537         LOG_ERROR("Failed to establish an ID for iconURL %s", iconURL.ascii().data());
538         return;
539     }
540     setIconIDForPageURLQuery(*m_currentDB, iconID, pageURL);
541 }
542
543 int64_t IconDatabase::establishIconIDForIconURL(SQLDatabase& db, const String& iconURL, bool createIfNecessary)
544 {
545     String escapedIconURL = iconURL;
546     escapedIconURL.replace('\'', "''");
547     
548     // Get the iconID thats already in this database and return it - or return 0 if we're read-only
549     int64_t iconID = getIconIDForIconURLQuery(db, iconURL);
550     if (iconID || !createIfNecessary)
551         return iconID;
552         
553     // Create the icon table entry for the iconURL
554     return addIconForIconURLQuery(db, iconURL);
555 }
556
557 void IconDatabase::pruneUnreferencedIcons(int numberToPrune)
558 {
559     if (!numberToPrune || !isOpen())
560         return;
561     
562     if (numberToPrune > 0) {
563         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)))
564             LOG_ERROR("Failed to prune %i unreferenced icons from the DB - %s", numberToPrune, m_mainDB.lastErrorMsg());
565     } else {
566         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));"))
567             LOG_ERROR("Failed to prune all unreferenced icons from the DB - %s", m_mainDB.lastErrorMsg());
568     }
569 }
570
571 void IconDatabase::pruneUnretainedIconsOnStartup(Timer<IconDatabase>*)
572 {
573     if (!isOpen())
574         return;
575         
576 // FIXME - The PageURL delete and the pruneunreferenced icons need to be in an atomic transaction
577 #ifndef NDEBUG
578     CFTimeInterval start = CFAbsoluteTimeGetCurrent();
579 #endif
580     
581     // FIXME - rdar://4690949 - Need to prune unretained pageURLs here
582     LOG_ERROR("pruneUnretainedIconsOnStartup is still unimplemented");
583     pruneUnreferencedIcons(-1);
584
585 #ifndef NDEBUG
586     CFTimeInterval duration = CFAbsoluteTimeGetCurrent() - start;
587     LOG(IconDatabase, "Pruning unretained icons took %.4f seconds", duration);
588     if (duration > 1.0) 
589         LOG_ERROR("Pruning unretained icons took %.4f seconds - this is much too long!", duration);
590 #endif
591 }
592
593 void IconDatabase::pruneIconsPendingDeletion(Timer<IconDatabase>*)
594 {
595 #ifndef NDEBUG
596     CFTimeInterval start = CFAbsoluteTimeGetCurrent();
597 #endif
598
599     // First lets wipe all the pageURLs
600     HashSet<String>::iterator i = m_pageURLsPendingDeletion.begin();
601     for (;i != m_pageURLsPendingDeletion.end(); ++i) {    
602         forgetPageURL(*i);
603         LOG(IconDatabase, "Deleted PageURL %s", (*i).ascii().data());
604     }
605     m_pageURLsPendingDeletion.clear();
606
607     // Then get rid of all traces of the icons and IconURLs
608     SiteIcon* icon;    
609     for (i = m_iconURLsPendingDeletion.begin();i != m_iconURLsPendingDeletion.end(); ++i) {
610         // Forget the SiteIcon
611         icon = m_iconURLToSiteIcons.get(*i);
612         m_iconURLToSiteIcons.remove(*i);
613         delete icon;
614         
615         // Forget the IconURL from the database
616         forgetIconForIconURLFromDatabase(*i);
617         LOG(IconDatabase, "Deleted icon %s", (*i).ascii().data());   
618     }
619     m_iconURLsPendingDeletion.clear();
620     
621 #ifndef NDEBUG
622     CFTimeInterval duration = CFAbsoluteTimeGetCurrent() - start;
623     LOG(IconDatabase, "Wiping the items pending-deletion took %.4f seconds", duration);
624     if (duration > 1.0) 
625         LOG_ERROR("Wiping the items pending-deletion took %.4f seconds - this is much too long!", duration);
626 #endif
627 }
628
629 bool IconDatabase::hasEntryForIconURL(const String& iconURL)
630 {
631     if (iconURL.isEmpty())
632         return false;
633         
634     // First check the in memory mapped icons...
635     if (m_iconURLToSiteIcons.contains(iconURL))
636         return true;
637
638     // Then we'll check the main database
639     if (hasIconForIconURLQuery(m_mainDB, iconURL))
640         return true;
641         
642     // Finally, the last resort - check the private browsing database
643     if (m_privateBrowsingEnabled)  
644         if (hasIconForIconURLQuery(m_privateBrowsingDB, iconURL))
645             return true;    
646
647     // We must not have this iconURL!
648     return false;
649 }
650
651 IconDatabase::~IconDatabase()
652 {
653     close();
654 }
655
656
657 // Query helper functions
658 bool pageURLTableIsEmptyQuery(SQLDatabase& db)
659 {
660     return !(SQLStatement(db, "SELECT iconID FROM PageURL LIMIT 1;").returnsAtLeastOneResult());
661 }
662
663 Vector<unsigned char> imageDataForIconURLQuery(SQLDatabase& db, const String& iconURL)
664 {
665     String escapedIconURL = iconURL;
666     escapedIconURL.replace('\'', "''");
667     return SQLStatement(db, "SELECT Icon.data FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").getColumnBlobAsVector(0);
668 }
669
670 int timeStampForIconURLQuery(SQLDatabase& db, const String& iconURL)
671 {
672     String escapedIconURL = iconURL;
673     escapedIconURL.replace('\'', "''");
674     return SQLStatement(db, "SELECT Icon.stamp FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").getColumnInt(0);
675 }
676
677
678 String iconURLForPageURLQuery(SQLDatabase& db, const String& pageURL)
679 {
680     String escapedPageURL = pageURL;
681     escapedPageURL.replace('\'', "''");
682     return SQLStatement(db, "SELECT Icon.url FROM Icon, PageURL WHERE PageURL.url = '" + escapedPageURL + "' AND Icon.iconID = PageURL.iconID").getColumnText16(0);
683 }
684
685 void forgetPageURLQuery(SQLDatabase& db, const String& pageURL)
686 {
687     String escapedPageURL = pageURL;
688     escapedPageURL.replace('\'', "''");
689     
690     db.executeCommand("DELETE FROM PageURL WHERE url = '" + escapedPageURL + "';");
691 }
692
693 void setIconIDForPageURLQuery(SQLDatabase& db, int64_t iconID, const String& pageURL)
694 {
695     String escapedPageURL = pageURL;
696     escapedPageURL.replace('\'', "''");
697     if (!db.executeCommand("INSERT INTO PageURL (url, iconID) VALUES ('" + escapedPageURL + "', " + String::number(iconID) + ");"))
698         LOG_ERROR("Failed to set iconid %lli for PageURL %s", iconID, pageURL.ascii().data());
699 }
700
701 int64_t getIconIDForIconURLQuery(SQLDatabase& db, const String& iconURL)
702 {
703     String escapedIconURL = iconURL;
704     escapedIconURL.replace('\'', "''");
705     return SQLStatement(db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").getColumnInt64(0);
706 }
707
708 int64_t addIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
709 {
710     String escapedIconURL = iconURL;
711     escapedIconURL.replace('\'', "''");
712     if (db.executeCommand("INSERT INTO Icon (url) VALUES ('" + escapedIconURL + "');"))
713         return db.lastInsertRowID();
714     return 0;
715 }
716
717 bool hasIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
718 {
719     String escapedIconURL = iconURL;
720     escapedIconURL.replace('\'', "''");
721     return SQLStatement(db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = '" + escapedIconURL + "';").returnsAtLeastOneResult();
722 }
723
724 } //namespace WebCore