Reviewed by "common sense" (and Maciej)
[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 "IconDataCache.h"
30 #include "Image.h"
31 #include "Logging.h"
32 #include "PlatformString.h"
33 #include "SQLStatement.h"
34 #include "SQLTransaction.h"
35 #include "SystemTime.h"
36
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.
39
40 namespace WebCore {
41
42 IconDatabase* IconDatabase::m_sharedInstance = 0;
43
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;
50
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; 
55
56 const int IconDatabase::updateTimerDelay = 5; 
57
58 const String& IconDatabase::defaultDatabaseFilename()
59 {
60     static String defaultDatabaseFilename = "icon.db";
61     return defaultDatabaseFilename;
62 }
63
64 IconDatabase* IconDatabase::sharedIconDatabase()
65 {
66     if (!m_sharedInstance) {
67         m_sharedInstance = new IconDatabase();
68     }
69     return m_sharedInstance;
70 }
71
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)
89 {
90     
91 }
92
93 bool IconDatabase::open(const String& databasePath)
94 {
95     if (isOpen()) {
96         LOG_ERROR("Attempt to reopen the IconDatabase which is already open.  Must close it first.");
97         return false;
98     }
99     
100     // First we'll formulate the full path for the database file
101     String dbFilename;
102     if (databasePath[databasePath.length()] == '/')
103         dbFilename = databasePath + defaultDatabaseFilename();
104     else
105         dbFilename = databasePath + "/" + defaultDatabaseFilename();
106
107     // <rdar://problem/4707718> - If user's Icon directory is unwritable, Safari will crash at startup
108     // Now, we'll see if we can open the on-disk database.  And, if we can't, we'll return false.  
109     // WebKit will then ignore us and act as if the database is disabled
110     if (!m_mainDB.open(dbFilename)) {
111         LOG_ERROR("Unable to open icon database at path %s - %s", dbFilename.ascii().data(), m_mainDB.lastErrorMsg());
112         return false;
113     }
114     
115     if (!isValidDatabase(m_mainDB)) {
116         LOG(IconDatabase, "%s is missing or in an invalid state - reconstructing", dbFilename.ascii().data());
117         m_mainDB.clearAllTables();
118         createDatabaseTables(m_mainDB);
119     }
120
121     m_initialPruningTransaction = new SQLTransaction(m_mainDB);
122     // 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     bool result;
124     result = m_mainDB.executeCommand("CREATE TEMP TABLE PageRetain (url TEXT);");
125     // Creating an in-memory temp table should never, ever, ever fail
126     ASSERT(result);
127
128     // These are actually two different SQLite config options - not my fault they are named confusingly  ;)
129     m_mainDB.setSynchronous(SQLDatabase::SyncOff);
130     m_mainDB.setFullsync(false);
131
132     m_initialPruningTransaction->begin();
133     
134     // Open the in-memory table for private browsing
135     if (!m_privateBrowsingDB.open(":memory:"))
136         LOG_ERROR("Unable to open in-memory database for private browsing - %s", m_privateBrowsingDB.lastErrorMsg());
137
138     // Only if we successfully remained open will we start our "initial purge timer"
139     // rdar://4690949 - when we have deferred reads and writes all the way in, the prunetimer
140     // will become "deferredTimer" or something along those lines, and will be set only when
141     // a deferred read/write is queued
142     if (isOpen())
143         m_startupTimer.startOneShot(0);
144     
145     return isOpen();
146 }
147
148 void IconDatabase::close()
149 {
150     // This will close all the SQL statements and transactions we have open,
151     // syncing the DB at the appropriate point
152     deleteAllPreparedStatements(true);
153  
154     m_mainDB.close();
155     m_privateBrowsingDB.close();
156 }
157
158 void IconDatabase::removeAllIcons()
159 {
160     // We don't need to sync anything anymore since we're wiping everything.  
161     // So we can kill the update timer, and clear all the hashes of "items that need syncing"
162     m_updateTimer.stop();
163     m_iconDataCachesPendingUpdate.clear();
164     m_pageURLsPendingAddition.clear();
165     m_pageURLsPendingDeletion.clear();
166     m_iconURLsPendingDeletion.clear();
167     
168     //  Now clear all in-memory URLs and Icons
169     m_pageURLToIconURLMap.clear();
170     m_pageURLToRetainCount.clear();
171     m_iconURLToRetainCount.clear();
172     
173     deleteAllValues(m_iconURLToIconDataCacheMap);
174     m_iconURLToIconDataCacheMap.clear();
175         
176     // Wipe any pre-prepared statements, otherwise resetting the SQLDatabases themselves will fail
177     deleteAllPreparedStatements(false);
178     
179     // The easiest way to wipe the in-memory database is by closing and reopening it
180     m_privateBrowsingDB.close();
181     if (!m_privateBrowsingDB.open(":memory:"))
182         LOG_ERROR("Unable to open in-memory database for private browsing - %s", m_privateBrowsingDB.lastErrorMsg());
183     createDatabaseTables(m_privateBrowsingDB);
184         
185     // To reset the on-disk database, we'll wipe all its tables then vacuum it
186     // This is easier and safer than closing it, deleting the file, and recreating from scratch
187     m_mainDB.clearAllTables();
188     m_mainDB.runVacuumCommand();
189     createDatabaseTables(m_mainDB);
190 }
191
192 // There are two instances where you'd want to deleteAllPreparedStatements - one with sync, and one without
193 // A - Closing down the database on application exit - in this case, you *do* want to save the icons out
194 // B - Resetting the DB via removeAllIcons() - in this case, you *don't* want to sync, because it would be a waste of time
195 void IconDatabase::deleteAllPreparedStatements(bool withSync)
196 {
197     // Must wipe the initial retain statement before the initial transaction
198     delete m_preparedPageRetainInsertStatement;
199     m_preparedPageRetainInsertStatement = 0;
200     delete m_initialPruningTransaction;
201     m_initialPruningTransaction = 0;
202
203     // Sync, if desired
204     if (withSync)
205         syncDatabase();
206         
207     // Order doesn't matter on these
208     delete m_timeStampForIconURLStatement;
209     m_timeStampForIconURLStatement = 0;
210     delete m_iconURLForPageURLStatement;
211     m_iconURLForPageURLStatement = 0;
212     delete m_hasIconForIconURLStatement;
213     m_hasIconForIconURLStatement = 0;
214     delete m_forgetPageURLStatement;
215     m_forgetPageURLStatement = 0;
216     delete m_setIconIDForPageURLStatement;
217     m_setIconIDForPageURLStatement = 0;
218     delete m_getIconIDForIconURLStatement;
219     m_getIconIDForIconURLStatement = 0;
220     delete m_addIconForIconURLStatement;
221     m_addIconForIconURLStatement = 0;
222     delete m_imageDataForIconURLStatement;
223     m_imageDataForIconURLStatement = 0;
224 }
225
226 bool IconDatabase::isEmpty()
227 {
228     if (m_privateBrowsingEnabled)
229         if (!pageURLTableIsEmptyQuery(m_privateBrowsingDB))
230             return false;
231             
232     return pageURLTableIsEmptyQuery(m_mainDB);
233 }
234
235 bool IconDatabase::isValidDatabase(SQLDatabase& db)
236 {
237     // These two tables should always exist in a valid db
238     if (!db.tableExists("Icon") || !db.tableExists("PageURL") || !db.tableExists("IconDatabaseInfo"))
239         return false;
240     
241     if (SQLStatement(db, "SELECT value FROM IconDatabaseInfo WHERE key = 'Version';").getColumnInt(0) < currentDatabaseVersion) {
242         LOG(IconDatabase, "DB version is not found or below expected valid version");
243         return false;
244     }
245     
246     return true;
247 }
248
249 void IconDatabase::createDatabaseTables(SQLDatabase& db)
250 {
251     if (!db.executeCommand("CREATE TABLE PageURL (url TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,iconID INTEGER NOT NULL ON CONFLICT FAIL);")) {
252         LOG_ERROR("Could not create PageURL table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
253         db.close();
254         return;
255     }
256     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);")) {
257         LOG_ERROR("Could not create Icon table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
258         db.close();
259         return;
260     }
261     if (!db.executeCommand("CREATE TABLE IconDatabaseInfo (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
262         LOG_ERROR("Could not create IconDatabaseInfo table in database (%i) - %s", db.lastError(), db.lastErrorMsg());
263         db.close();
264         return;
265     }
266     if (!db.executeCommand(String("INSERT INTO IconDatabaseInfo VALUES ('Version', ") + String::number(currentDatabaseVersion) + ");")) {
267         LOG_ERROR("Could not insert icon database version into IconDatabaseInfo table (%i) - %s", db.lastError(), db.lastErrorMsg());
268         db.close();
269         return;
270     }
271 }    
272
273 void IconDatabase::imageDataForIconURL(const String& iconURL, Vector<unsigned char>& result)
274 {      
275     // If private browsing is enabled, we'll check there first as the most up-to-date data for an icon will be there
276     if (m_privateBrowsingEnabled) {    
277         imageDataForIconURLQuery(m_privateBrowsingDB, iconURL, result);
278         if (!result.isEmpty())
279             return;
280     } 
281     
282     // It wasn't found there, so lets check the main tables
283     imageDataForIconURLQuery(m_mainDB, iconURL, result);
284 }
285
286 void IconDatabase::setPrivateBrowsingEnabled(bool flag)
287 {
288     if (m_privateBrowsingEnabled == flag)
289         return;
290     
291     // Sync any deferred DB changes before we change the active DB
292     syncDatabase();
293     
294     m_privateBrowsingEnabled = flag;
295     
296     if (m_privateBrowsingEnabled) {
297         createDatabaseTables(m_privateBrowsingDB);
298         m_currentDB = &m_privateBrowsingDB;
299     } else {
300         m_privateBrowsingDB.clearAllTables();
301         m_currentDB = &m_mainDB;
302     }
303 }
304
305 Image* IconDatabase::iconForPageURL(const String& pageURL, const IntSize& size, bool cache)
306 {   
307     // See if we even have an IconURL for this PageURL...
308     String iconURL = iconURLForPageURL(pageURL);
309     if (iconURL.isEmpty())
310         return 0;
311     
312     // If we do, maybe we have a IconDataCache for this IconURL
313     IconDataCache* icon = getOrCreateIconDataCache(iconURL);
314     
315     // If it's a new IconDataCache object that doesn't have its imageData set yet,
316     // we'll read in that image data now
317     if (icon->imageDataStatus() == ImageDataStatusUnknown) {
318         Vector<unsigned char> data;
319         imageDataForIconURL(iconURL, data);
320         icon->setImageData(data.data(), data.size());
321     }
322         
323     return icon->getImage(size);
324 }
325
326 // FIXME 4667425 - this check needs to see if the icon's data is empty or not and apply
327 // iconExpirationTime to present icons, and missingIconExpirationTime for missing icons
328 bool IconDatabase::isIconExpiredForIconURL(const String& iconURL)
329 {
330     if (iconURL.isEmpty()) 
331         return true;
332     
333     // If we have a IconDataCache, then it definitely has the Timestamp in it
334     IconDataCache* icon = m_iconURLToIconDataCacheMap.get(iconURL);
335     if (icon) 
336         return (int)currentTime() - icon->getTimestamp() > iconExpirationTime;
337             
338     // Otherwise, we'll get the timestamp from the DB and use it
339     int stamp;
340     if (m_privateBrowsingEnabled) {
341         stamp = timeStampForIconURLQuery(m_privateBrowsingDB, iconURL);
342         if (stamp)
343             return ((int)currentTime() - stamp) > iconExpirationTime;
344     }
345     
346     stamp = timeStampForIconURLQuery(m_mainDB, iconURL);
347     if (stamp)
348         return ((int)currentTime() - stamp) > iconExpirationTime;
349     
350     return false;
351 }
352     
353 String IconDatabase::iconURLForPageURL(const String& pageURL)
354 {    
355     if (pageURL.isEmpty()) 
356         return String();
357         
358     if (m_pageURLToIconURLMap.contains(pageURL))
359         return m_pageURLToIconURLMap.get(pageURL);
360     
361     // Try the private browsing database because if any PageURL's IconURL was updated during privated browsing, 
362     // the most up-to-date iconURL would be there
363     if (m_privateBrowsingEnabled) {
364         String iconURL = iconURLForPageURLQuery(m_privateBrowsingDB, pageURL);
365         if (!iconURL.isEmpty()) {
366             m_pageURLToIconURLMap.set(pageURL, iconURL);
367             return iconURL;
368         }
369     }
370     
371     String iconURL = iconURLForPageURLQuery(m_mainDB, pageURL);
372     if (!iconURL.isEmpty())
373         m_pageURLToIconURLMap.set(pageURL, iconURL);
374     return iconURL;
375 }
376
377 Image* IconDatabase::defaultIcon(const IntSize& size)
378 {
379     if (!m_defaultIconDataCache) {
380         m_defaultIconDataCache = new IconDataCache("urlIcon");
381         m_defaultIconDataCache->loadImageFromResource("urlIcon");
382     }
383     
384     return m_defaultIconDataCache->getImage(size);
385 }
386
387 void IconDatabase::retainIconForPageURL(const String& pageURL)
388 {
389     if (pageURL.isEmpty())
390         return;
391     
392     // If we don't have the retain count for this page, we need to setup records of its retain
393     // Otherwise, get the count and increment it
394     int retainCount;
395     if (!(retainCount = m_pageURLToRetainCount.get(pageURL))) {
396         m_pageURLToRetainCount.set(pageURL, 1);   
397
398         // If we haven't done initial pruning, we store this retain record in the temporary in-memory table
399         // Note we only keep the URL in the temporary table, not the full retain count, because for pruning-considerations
400         // we only care *if* a pageURL is retained - not the full count.  This call to retainIconForPageURL incremented the PageURL's
401         // retain count from 0 to 1 therefore we may store it in the temporary table
402         // Also, if we haven't done pruning yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot, 
403         // so we bail here and skip those steps
404         if (!m_initialPruningComplete) {
405             String escapedPageURL = escapeSQLString(pageURL);
406             if (!m_preparedPageRetainInsertStatement) {
407                 m_preparedPageRetainInsertStatement = new SQLStatement(m_mainDB, "INSERT INTO PageRetain VALUES (?);");
408                 m_preparedPageRetainInsertStatement->prepare();
409             }
410             m_preparedPageRetainInsertStatement->reset();
411             m_preparedPageRetainInsertStatement->bindText16(1, pageURL);
412             if (m_preparedPageRetainInsertStatement->step() != SQLITE_DONE)
413                 LOG_ERROR("Failed to record icon retention in temporary table for IconURL %s", pageURL.ascii().data());
414             return;
415         }
416         
417         // If this pageURL is marked for deletion, bring it back from the brink
418         m_pageURLsPendingDeletion.remove(pageURL);
419         
420         // If we have an iconURL for this pageURL, we'll now retain the iconURL
421         String iconURL = iconURLForPageURL(pageURL);
422         if (!iconURL.isEmpty())
423             retainIconURL(iconURL);
424
425     } else
426         m_pageURLToRetainCount.set(pageURL, retainCount + 1);   
427 }
428
429 void IconDatabase::releaseIconForPageURL(const String& pageURL)
430 {
431     if (pageURL.isEmpty())
432         return;
433         
434     // Check if this pageURL is actually retained
435     if(!m_pageURLToRetainCount.contains(pageURL)) {
436         LOG_ERROR("Attempting to release icon for URL %s which is not retained", pageURL.ascii().data());
437         return;
438     }
439     
440     // Get its retain count
441     int retainCount = m_pageURLToRetainCount.get(pageURL);
442     ASSERT(retainCount > 0);
443     
444     // If it still has a positive retain count, store the new count and bail
445     if (--retainCount) {
446         m_pageURLToRetainCount.set(pageURL, retainCount);
447         return;
448     }
449     
450     LOG(IconDatabase, "No more retainers for PageURL %s", pageURL.ascii().data());
451     
452     // Otherwise, remove all record of the retain count
453     m_pageURLToRetainCount.remove(pageURL);   
454     
455     // If we haven't done initial pruning, we remove this retain record from the temporary in-memory table
456     // Note we only keep the URL in the temporary table, not the full retain count, because for pruning-considerations
457     // we only care *if* a pageURL is retained - not the full count.  This call to releaseIconForPageURL decremented the PageURL's
458     // retain count from 1 to 0 therefore we may remove it from the temporary table
459     // Also, if we haven't done pruning yet, we want to avoid any pageURL->iconURL lookups and the pageURLsPendingDeletion is moot, 
460     // so we bail here and skip those steps
461     if (!m_initialPruningComplete) {
462         String escapedPageURL = escapeSQLString(pageURL);
463         if (!m_mainDB.executeCommand("DELETE FROM PageRetain WHERE url='" + escapedPageURL + "';"))
464             LOG_ERROR("Failed to delete record of icon retention from temporary table for IconURL %s", pageURL.ascii().data());
465         return;
466     }
467
468     
469     // Then mark this pageURL for deletion
470     m_pageURLsPendingDeletion.add(pageURL);
471     
472     // Grab the iconURL and release it
473     String iconURL = iconURLForPageURL(pageURL);
474     if (!iconURL.isEmpty())
475         releaseIconURL(iconURL);
476 }
477
478 void IconDatabase::retainIconURL(const String& iconURL)
479 {
480     ASSERT(!iconURL.isEmpty());
481     
482     if (int retainCount = m_iconURLToRetainCount.get(iconURL)) {
483         ASSERT(retainCount > 0);
484         m_iconURLToRetainCount.set(iconURL, retainCount + 1);
485     } else {
486         m_iconURLToRetainCount.set(iconURL, 1);
487         if (m_iconURLsPendingDeletion.contains(iconURL))
488             m_iconURLsPendingDeletion.remove(iconURL);
489     }   
490 }
491
492 void IconDatabase::releaseIconURL(const String& iconURL)
493 {
494     ASSERT(!iconURL.isEmpty());
495     
496     // If the iconURL has no retain count, we can bail
497     if (!m_iconURLToRetainCount.contains(iconURL))
498         return;
499     
500     // Otherwise, decrement it
501     int retainCount = m_iconURLToRetainCount.get(iconURL) - 1;
502     ASSERT(retainCount > -1);
503     
504     // If the icon is still retained, store the count and bail
505     if (retainCount) {
506         m_iconURLToRetainCount.set(iconURL, retainCount);
507         return;
508     }
509     
510     LOG(IconDatabase, "No more retainers for IconURL %s", iconURL.ascii().data());
511     
512     // Otherwise, this icon is toast.  Remove all traces of its retain count...
513     m_iconURLToRetainCount.remove(iconURL);
514     
515     // And since we delay the actual deletion of icons, so lets add it to that queue
516     m_iconURLsPendingDeletion.add(iconURL);
517 }
518
519 void IconDatabase::forgetPageURL(const String& pageURL)
520 {
521     // Remove the PageURL->IconURL mapping
522     m_pageURLToIconURLMap.remove(pageURL);
523     
524     // And remove this pageURL from the DB
525     forgetPageURLQuery(*m_currentDB, pageURL);
526 }
527     
528 bool IconDatabase::isIconURLRetained(const String& iconURL)
529 {
530     if (iconURL.isEmpty())
531         return false;
532         
533     return m_iconURLToRetainCount.contains(iconURL);
534 }
535
536 void IconDatabase::forgetIconForIconURLFromDatabase(const String& iconURL)
537 {
538     if (iconURL.isEmpty())
539         return;
540
541     // For private browsing safety, since this alters the database, we only forget from the current database
542     // If we're in private browsing and the Icon also exists in the main database, it will be pruned on the next startup
543     int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL, false);
544     
545     // If we didn't actually have an icon for this iconURL... well, thats a screwy condition we should track down, but also 
546     // something we could move on from
547     ASSERT(iconID);
548     if (!iconID) {
549         LOG_ERROR("Attempting to forget icon for IconURL %s, though we don't have it in the database", iconURL.ascii().data());
550         return;
551     }
552     
553     if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM Icon WHERE Icon.iconID = %lli;", iconID)))
554         LOG_ERROR("Unable to drop Icon for IconURL", iconURL.ascii().data()); 
555     if (!m_currentDB->executeCommand(String::sprintf("DELETE FROM PageURL WHERE PageURL.iconID = %lli", iconID)))
556         LOG_ERROR("Unable to drop all PageURL for IconURL", iconURL.ascii().data()); 
557 }
558
559 IconDataCache* IconDatabase::getOrCreateIconDataCache(const String& iconURL)
560 {
561     IconDataCache* icon;
562     if ((icon = m_iconURLToIconDataCacheMap.get(iconURL)))
563         return icon;
564         
565     icon = new IconDataCache(iconURL);
566     m_iconURLToIconDataCacheMap.set(iconURL, icon);
567     
568     // Get the most current time stamp for this IconURL
569     int timestamp = 0;
570     if (m_privateBrowsingEnabled)
571         timestamp = timeStampForIconURLQuery(m_privateBrowsingDB, iconURL);
572     if (!timestamp)
573         timestamp = timeStampForIconURLQuery(m_mainDB, iconURL);
574         
575     // If we can't get a timestamp for this URL, then it is a new icon and we initialize its timestamp now
576     if (!timestamp) {
577         icon->setTimestamp((int)currentTime());
578         m_iconDataCachesPendingUpdate.add(icon);
579     } else 
580         icon->setTimestamp(timestamp);
581         
582     return icon;
583 }
584
585 void IconDatabase::setIconDataForIconURL(const void* data, int size, const String& iconURL)
586 {
587     ASSERT(size > -1);
588     if (size)
589         ASSERT(data);
590     else
591         data = 0;
592         
593     if (iconURL.isEmpty())
594         return;
595     
596     // Get the IconDataCache for this IconURL (note, IconDataCacheForIconURL will create it if necessary)
597     IconDataCache* icon = getOrCreateIconDataCache(iconURL);
598     
599     // Set the data in the IconDataCache
600     icon->setImageData((unsigned char*)data, size);
601     
602     // Update the timestamp in the IconDataCache to NOW
603     icon->setTimestamp((int)currentTime());
604
605     // Mark the IconDataCache as requiring an update to the database
606     m_iconDataCachesPendingUpdate.add(icon);
607 }
608
609 void IconDatabase::setHaveNoIconForIconURL(const String& iconURL)
610 {   
611     setIconDataForIconURL(0, 0, iconURL);
612 }
613
614 bool IconDatabase::setIconURLForPageURL(const String& iconURL, const String& pageURL)
615 {
616     ASSERT(!iconURL.isEmpty());
617     ASSERT(!pageURL.isEmpty());
618     
619     // If the urls already map to each other, bail.
620     // This happens surprisingly often, and seems to cream iBench performance
621     if (m_pageURLToIconURLMap.get(pageURL) == iconURL)
622         return false;
623
624     // If this pageURL is retained, we have some work to do on the IconURL retain counts
625     if (m_pageURLToRetainCount.contains(pageURL)) {
626         String oldIconURL = m_pageURLToIconURLMap.get(pageURL);
627         if (!oldIconURL.isEmpty())
628             releaseIconURL(oldIconURL);
629         retainIconURL(iconURL);
630     } else {
631         // If this pageURL is *not* retained, then we may be marking it for deletion, as well!
632         // As counterintuitive as it seems to mark it for addition and for deletion at the same time,
633         // it's valid because when we do a new pageURL->iconURL mapping we *have* to mark it for addition,
634         // no matter what, as there is no efficient was to determine if the mapping is in the DB already.
635         // But, if the iconURL is marked for deletion, we'll also mark this pageURL for deletion - if a 
636         // client comes along and retains it before the timer fires, the "pendingDeletion" lists will
637         // be manipulated appopriately and new pageURL will be brought back from the brink
638         if (m_iconURLsPendingDeletion.contains(iconURL))
639             m_pageURLsPendingDeletion.add(pageURL);
640     }
641     
642     // Cache the pageURL->iconURL map
643     m_pageURLToIconURLMap.set(pageURL, iconURL);
644     
645     // And mark this mapping to be added to the database
646     m_pageURLsPendingAddition.add(pageURL);
647     
648     // Then start the timer to commit this change - or further delay the timer if it
649     // was already started
650     m_updateTimer.startOneShot(updateTimerDelay);
651     
652     return true;
653 }
654
655 void IconDatabase::setIconURLForPageURLInDatabase(const String& iconURL, const String& pageURL)
656 {
657     int64_t iconID = establishIconIDForIconURL(*m_currentDB, iconURL);
658     if (!iconID) {
659         LOG_ERROR("Failed to establish an ID for iconURL %s", iconURL.ascii().data());
660         return;
661     }
662     setIconIDForPageURLQuery(*m_currentDB, iconID, pageURL);
663 }
664
665 int64_t IconDatabase::establishIconIDForIconURL(SQLDatabase& db, const String& iconURL, bool createIfNecessary)
666 {
667     String escapedIconURL = escapeSQLString(iconURL);
668     
669     // Get the iconID thats already in this database and return it - or return 0 if we're read-only
670     int64_t iconID = getIconIDForIconURLQuery(db, iconURL);
671     if (iconID || !createIfNecessary)
672         return iconID;
673         
674     // Create the icon table entry for the iconURL
675     return addIconForIconURLQuery(db, iconURL);
676 }
677
678 void IconDatabase::pruneUnretainedIconsOnStartup(Timer<IconDatabase>*)
679 {
680     if (!isOpen())
681         return;
682         
683     // This function should only be called once per run, and ideally only via the timer
684     // on program startup
685     ASSERT(!m_initialPruningComplete);
686
687 #ifndef NDEBUG
688     double timestamp = currentTime();
689 #endif
690     
691     // rdar://4690949 - Need to prune unretained iconURLs here, then prune out all pageURLs that reference
692     // nonexistent icons
693     
694     // Finalize the PageRetain statement
695     delete m_preparedPageRetainInsertStatement;
696     m_preparedPageRetainInsertStatement = 0;
697     
698     // Commit all of the PageRetains and start a new transaction for the pruning dirty-work
699     m_initialPruningTransaction->commit();
700     m_initialPruningTransaction->begin();
701     
702     // Then wipe all PageURLs and Icons that aren't retained
703     if (!m_mainDB.executeCommand("DELETE FROM PageURL WHERE PageURL.url NOT IN (SELECT url FROM PageRetain);") ||
704         !m_mainDB.executeCommand("DELETE FROM Icon WHERE Icon.iconID NOT IN (SELECT iconID FROM PageURL);") ||
705         !m_mainDB.executeCommand("DROP TABLE PageRetain;"))
706         LOG_ERROR("Failed to execute SQL to prune unretained pages and icons from the on-disk tables");
707     
708     
709     // Since we lazily retained the pageURLs without getting the iconURLs or retaining the iconURLs, 
710     // we need to do that now
711     // We now should be in a spiffy situation where we know every single pageURL left in the DB is retained, so we are interested
712     // in the iconURLs for all remaining pageURLs
713     // So we can simply add all the remaining mappings, and retain each pageURL's icon once
714     
715     SQLStatement sql(m_mainDB, "SELECT PageURL.url, Icon.url FROM PageURL INNER JOIN Icon ON PageURL.iconID=Icon.iconID");
716     sql.prepare();
717     int result;
718     while((result = sql.step()) == SQLITE_ROW) {
719         String iconURL = sql.getColumnText16(1);
720         m_pageURLToIconURLMap.set(sql.getColumnText16(0), iconURL);
721         retainIconURL(iconURL);
722         LOG(IconDatabase, "Found a PageURL that mapped to %s", iconURL.ascii().data());
723     }
724     if (result != SQLITE_DONE)
725         LOG_ERROR("Error reading PageURL->IconURL mappings from on-disk DB");
726     sql.finalize();
727     
728     // Commit the transaction and do some cleanup
729     m_initialPruningTransaction->commit();
730     delete m_initialPruningTransaction;
731     m_initialPruningTransaction = 0;
732     
733     m_initialPruningComplete = true;
734     
735 #ifndef NDEBUG
736     timestamp = currentTime() - timestamp;
737     if (timestamp <= 1.0)
738         LOG(IconDatabase, "Pruning unretained icons took %.4f seconds", timestamp);
739     else
740         LOG(IconDatabase, "Pruning unretained icons took %.4f seconds - this is much too long!", timestamp);
741 #endif
742 }
743
744 void IconDatabase::updateDatabase(Timer<IconDatabase>*)
745 {
746     syncDatabase();
747 }
748
749 void IconDatabase::syncDatabase()
750 {
751 #ifndef NDEBUG
752     double timestamp = currentTime();
753 #endif
754
755     // First we'll do the pending additions
756     // Starting with the IconDataCaches that need updating/insertion
757     for (HashSet<IconDataCache*>::iterator i = m_iconDataCachesPendingUpdate.begin(), end = m_iconDataCachesPendingUpdate.end(); i != end; ++i) {
758         (*i)->writeToDatabase(*m_currentDB);
759         LOG(IconDatabase, "Wrote IconDataCache for IconURL %s with timestamp of %li to the DB", (*i)->getIconURL().ascii().data(), (*i)->getTimestamp());
760     }
761     m_iconDataCachesPendingUpdate.clear();
762     
763     HashSet<String>::iterator i = m_pageURLsPendingAddition.begin(), end = m_pageURLsPendingAddition.end();
764     for (; i != end; ++i) {
765         setIconURLForPageURLInDatabase(m_pageURLToIconURLMap.get(*i), *i);
766         LOG(IconDatabase, "Committed IconURL for PageURL %s to database", (*i).ascii().data());
767     }
768     m_pageURLsPendingAddition.clear();
769     
770     // Then we'll do the pending deletions
771     // First lets wipe all the pageURLs
772     for (i = m_pageURLsPendingDeletion.begin(), end = m_pageURLsPendingDeletion.end(); i != end; ++i) {    
773         forgetPageURL(*i);
774         LOG(IconDatabase, "Deleted PageURL %s", (*i).ascii().data());
775     }
776     m_pageURLsPendingDeletion.clear();
777
778     // Then get rid of all traces of the icons and IconURLs
779     IconDataCache* icon;    
780     for (i = m_iconURLsPendingDeletion.begin(), end = m_iconURLsPendingDeletion.end(); i != end; ++i) {
781         // Forget the IconDataCache
782         icon = m_iconURLToIconDataCacheMap.get(*i);
783         if (icon)
784             m_iconURLToIconDataCacheMap.remove(*i);
785         delete icon;
786         
787         // Forget the IconURL from the database
788         forgetIconForIconURLFromDatabase(*i);
789         LOG(IconDatabase, "Deleted icon %s", (*i).ascii().data());   
790     }
791     m_iconURLsPendingDeletion.clear();
792     
793     // If the timer was running to cause this update, we can kill the timer as its firing would be redundant
794     m_updateTimer.stop();
795     
796 #ifndef NDEBUG
797     timestamp = currentTime() - timestamp;
798     if (timestamp <= 1.0)
799         LOG(IconDatabase, "Updating the database took %.4f seconds", timestamp);
800     else 
801         LOG(IconDatabase, "Updating the database took %.4f seconds - this is much too long!", timestamp);
802 #endif
803 }
804
805 bool IconDatabase::hasEntryForIconURL(const String& iconURL)
806 {
807     if (iconURL.isEmpty())
808         return false;
809         
810     // First check the in memory mapped icons...
811     if (m_iconURLToIconDataCacheMap.contains(iconURL))
812         return true;
813
814     // Then we'll check the main database
815     if (hasIconForIconURLQuery(m_mainDB, iconURL))
816         return true;
817         
818     // Finally, the last resort - check the private browsing database
819     if (m_privateBrowsingEnabled)  
820         if (hasIconForIconURLQuery(m_privateBrowsingDB, iconURL))
821             return true;    
822
823     // We must not have this iconURL!
824     return false;
825 }
826
827 IconDatabase::~IconDatabase()
828 {
829     close();
830     m_startupTimer.stop();
831     m_updateTimer.stop();
832     if (m_sharedInstance == this)
833         m_sharedInstance = 0;
834 }
835
836 // readySQLStatement() handles two things
837 // 1 - If the SQLDatabase& argument is different, the statement must be destroyed and remade.  This happens when the user
838 //     switches to and from private browsing
839 // 2 - Lazy construction of the Statement in the first place, in case we've never made this query before
840 inline void readySQLStatement(SQLStatement*& statement, SQLDatabase& db, const String& str)
841 {
842     if (statement && statement->database() != &db) {
843         delete statement;
844         statement = 0;
845     }
846     if (!statement) {
847         statement = new SQLStatement(db, str);
848         statement->prepare();
849     }
850 }
851
852 // Any common IconDatabase query should be seperated into a fooQuery() and a *m_fooStatement.  
853 // This way we can lazily construct the SQLStatment for a query on its first use, then reuse the Statement binding
854 // the new parameter as needed
855 // The statement must be deleted in IconDatabase::close() before the actual SQLDatabase::close() call
856 // Also, m_fooStatement must be reset() before fooQuery() returns otherwise we will constantly get "database file locked" 
857 // errors in various combinations of queries
858
859 bool IconDatabase::pageURLTableIsEmptyQuery(SQLDatabase& db)
860 {  
861     // We won't make this use a m_fooStatement because its not really a "common" query
862     return !SQLStatement(db, "SELECT iconID FROM PageURL LIMIT 1;").returnsAtLeastOneResult();
863 }
864
865 void IconDatabase::imageDataForIconURLQuery(SQLDatabase& db, const String& iconURL, Vector<unsigned char>& result)
866 {
867     readySQLStatement(m_imageDataForIconURLStatement, db, "SELECT Icon.data FROM Icon WHERE Icon.url = (?);");
868     m_imageDataForIconURLStatement->bindText16(1, iconURL, false);
869     m_imageDataForIconURLStatement->step();
870     m_imageDataForIconURLStatement->getColumnBlobAsVector(0, result);
871     m_imageDataForIconURLStatement->reset();
872 }
873
874 int IconDatabase::timeStampForIconURLQuery(SQLDatabase& db, const String& iconURL)
875 {
876     readySQLStatement(m_timeStampForIconURLStatement, db, "SELECT Icon.stamp FROM Icon WHERE Icon.url = (?);");
877     m_timeStampForIconURLStatement->bindText16(1, iconURL, false);
878     m_timeStampForIconURLStatement->step();
879     int result = m_timeStampForIconURLStatement->getColumnInt(0);
880     m_timeStampForIconURLStatement->reset();
881     return result;
882 }
883
884 String IconDatabase::iconURLForPageURLQuery(SQLDatabase& db, const String& pageURL)
885 {
886     readySQLStatement(m_iconURLForPageURLStatement, db, "SELECT Icon.url FROM Icon, PageURL WHERE PageURL.url = (?) AND Icon.iconID = PageURL.iconID;");
887     m_iconURLForPageURLStatement->bindText16(1, pageURL, false);
888     m_iconURLForPageURLStatement->step();
889     String result = m_iconURLForPageURLStatement->getColumnText16(0);
890     m_iconURLForPageURLStatement->reset();
891     return result;
892 }
893
894 void IconDatabase::forgetPageURLQuery(SQLDatabase& db, const String& pageURL)
895 {
896     readySQLStatement(m_forgetPageURLStatement, db, "DELETE FROM PageURL WHERE url = (?);");
897     m_forgetPageURLStatement->bindText16(1, pageURL, false);
898     m_forgetPageURLStatement->step();
899     m_forgetPageURLStatement->reset();
900 }
901
902 void IconDatabase::setIconIDForPageURLQuery(SQLDatabase& db, int64_t iconID, const String& pageURL)
903 {
904     readySQLStatement(m_setIconIDForPageURLStatement, db, "INSERT INTO PageURL (url, iconID) VALUES ((?), ?);");
905     m_setIconIDForPageURLStatement->bindText16(1, pageURL, false);
906     m_setIconIDForPageURLStatement->bindInt64(2, iconID);
907     m_setIconIDForPageURLStatement->step();
908     m_setIconIDForPageURLStatement->reset();
909 }
910
911 int64_t IconDatabase::getIconIDForIconURLQuery(SQLDatabase& db, const String& iconURL)
912 {
913     readySQLStatement(m_getIconIDForIconURLStatement, db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = (?);");
914     m_getIconIDForIconURLStatement->bindText16(1, iconURL, false);
915     m_getIconIDForIconURLStatement->step();
916     int64_t result = m_getIconIDForIconURLStatement->getColumnInt64(0);
917     m_getIconIDForIconURLStatement->reset();
918     return result;
919 }
920
921 int64_t IconDatabase::addIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
922 {
923     readySQLStatement(m_addIconForIconURLStatement, db, "INSERT INTO Icon (url) VALUES ((?));");
924     m_addIconForIconURLStatement->bindText16(1, iconURL, false);
925     int64_t result = 0;
926     if (m_addIconForIconURLStatement->step() == SQLITE_OK)
927         result = db.lastInsertRowID();
928     m_addIconForIconURLStatement->reset();
929     return result;
930 }
931
932 bool IconDatabase::hasIconForIconURLQuery(SQLDatabase& db, const String& iconURL)
933 {
934     readySQLStatement(m_hasIconForIconURLStatement, db, "SELECT Icon.iconID FROM Icon WHERE Icon.url = (?);");
935     m_hasIconForIconURLStatement->bindText16(1, iconURL, false);
936     bool result = m_hasIconForIconURLStatement->step() == SQLITE_ROW;
937     m_hasIconForIconURLStatement->reset();
938     return result;
939 }
940
941 } //namespace WebCore