Reviewed by Brady Eidson.
[WebKit-https.git] / WebCore / storage / DatabaseTracker.cpp
1 /*
2  * Copyright (C) 2007, 2008 Apple 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "DatabaseTracker.h"
31
32 #include "ChromeClient.h"
33 #include "Database.h"
34 #include "DatabaseTrackerClient.h"
35 #include "Document.h"
36 #include "FileSystem.h"
37 #include "Logging.h"
38 #include "OriginQuotaManager.h"
39 #include "Page.h"
40 #include "SecurityOrigin.h"
41 #include "SecurityOriginHash.h"
42 #include "SQLiteStatement.h"
43
44 using namespace std;
45
46 namespace WebCore {
47
48 OriginQuotaManager& DatabaseTracker::originQuotaManager()
49 {
50     populateOrigins();
51     ASSERT(m_quotaManager);
52     return *m_quotaManager;
53 }
54
55 DatabaseTracker& DatabaseTracker::tracker()
56 {
57     static DatabaseTracker tracker;
58     return tracker;
59 }
60
61 DatabaseTracker::DatabaseTracker()
62     : m_client(0)
63     , m_proposedDatabase(0)
64 #ifndef NDEBUG
65     , m_thread(currentThread())
66 #endif
67 {
68 }
69
70 void DatabaseTracker::setDatabaseDirectoryPath(const String& path)
71 {
72     ASSERT(currentThread() == m_thread);
73     ASSERT(!m_database.isOpen());
74     m_databaseDirectoryPath = path;
75 }
76
77 const String& DatabaseTracker::databaseDirectoryPath() const
78 {
79     ASSERT(currentThread() == m_thread);
80     return m_databaseDirectoryPath;
81 }
82
83 String DatabaseTracker::trackerDatabasePath() const
84 {
85     ASSERT(currentThread() == m_thread);
86     if (m_databaseDirectoryPath.isEmpty())
87         return String();
88     return pathByAppendingComponent(m_databaseDirectoryPath, "Databases.db");
89 }
90
91 void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist)
92 {
93     ASSERT(currentThread() == m_thread);
94
95     if (m_database.isOpen())
96         return;
97
98     String databasePath = trackerDatabasePath();
99     if (databasePath.isEmpty())
100         return;
101
102     if (!createIfDoesNotExist && !fileExists(databasePath))
103         return;
104
105     makeAllDirectories(m_databaseDirectoryPath);
106     if (!m_database.open(databasePath)) {
107         // FIXME: What do do here?
108         return;
109     }
110     if (!m_database.tableExists("Origins")) {
111         if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
112             // FIXME: and here
113         }
114     }
115     if (!m_database.tableExists("Databases")) {
116         if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
117             // FIXME: and here
118         }
119     }
120 }
121
122 bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize)
123 {
124     ASSERT(currentThread() == m_thread);
125
126     // Populate the origins before we establish a database; this guarantees that quotaForOrigin
127     // can run on the database thread later.
128     populateOrigins();
129
130     SecurityOrigin* origin = document->securityOrigin();
131
132     // Since we're imminently opening a database within this Document's origin, make sure this origin is being tracked by the QuotaTracker
133     // by fetching it's current usage now
134     unsigned long long usage = usageForOrigin(origin);
135     
136     // If a database already exists, ignore the passed-in estimated size and say it's OK.
137     if (hasEntryForDatabase(origin, name))
138         return true;
139
140     // If the database will fit, allow its creation.
141     unsigned long long requirement = usage + max(1UL, estimatedSize);
142     if (requirement < usage)
143         return false; // If the estimated size is so big it causes an overflow, don't allow creation.
144     if (requirement <= quotaForOrigin(origin))
145         return true;
146
147     // Give the chrome client a chance to increase the quota.
148     // Temporarily make the details of the proposed database available, so the client can get at them.
149     Page* page = document->page();
150     if (!page)
151         return false;
152     pair<SecurityOrigin*, DatabaseDetails> details(origin, DatabaseDetails(name, displayName, estimatedSize, 0));
153     m_proposedDatabase = &details;
154     page->chrome()->client()->exceededDatabaseQuota(document->frame(), name);
155     m_proposedDatabase = 0;
156
157     // If the database will fit now, allow its creation.
158     return requirement <= quotaForOrigin(origin);
159 }
160
161 bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin)
162 {
163     ASSERT(currentThread() == m_thread);
164     populateOrigins();
165     MutexLocker lockQuotaMap(m_quotaMapGuard);
166     return m_quotaMap->contains(origin);
167 }
168
169 bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier)
170 {
171     ASSERT(currentThread() == m_thread);
172     openTrackerDatabase(false);
173     if (!m_database.isOpen())
174         return false;
175     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
176
177     if (statement.prepare() != SQLResultOk)
178         return false;
179
180     statement.bindText(1, origin->stringIdentifier());
181     statement.bindText(2, databaseIdentifier);
182
183     return statement.step() == SQLResultRow;
184 }
185
186 String DatabaseTracker::originPath(SecurityOrigin* origin) const
187 {
188     ASSERT(currentThread() == m_thread);
189     if (m_databaseDirectoryPath.isEmpty())
190         return String();
191     return pathByAppendingComponent(m_databaseDirectoryPath, origin->stringIdentifier());
192 }
193
194 String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists)
195 {
196     ASSERT(currentThread() == m_thread);
197
198     if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name)
199         return String();
200
201     String originIdentifier = origin->stringIdentifier();
202     String originPath = this->originPath(origin);
203     
204     // Make sure the path for this SecurityOrigin exists
205     if (createIfNotExists && !makeAllDirectories(originPath))
206         return String();
207     
208     // See if we have a path for this database yet
209     openTrackerDatabase(false);
210     if (!m_database.isOpen())
211         return String();
212     SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
213
214     if (statement.prepare() != SQLResultOk)
215         return String();
216
217     statement.bindText(1, originIdentifier);
218     statement.bindText(2, name);
219
220     int result = statement.step();
221
222     if (result == SQLResultRow)
223         return pathByAppendingComponent(originPath, statement.getColumnText(0));
224     if (!createIfNotExists)
225         return String();
226         
227     if (result != SQLResultDone) {
228         LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin->stringIdentifier().ascii().data(), name.ascii().data());
229         return String();
230     }
231     statement.finalize();
232     
233     SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");
234
235     // FIXME: More informative error handling here, even though these steps should never fail
236     if (sequenceStatement.prepare() != SQLResultOk)
237         return String();
238     result = sequenceStatement.step();
239
240     // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
241     // I can't imagine it'd over overflow
242     int64_t seq = 0;
243     if (result == SQLResultRow) {
244         seq = sequenceStatement.getColumnInt64(0);
245     } else if (result != SQLResultDone)
246         return String();
247     sequenceStatement.finalize();
248
249     String filename;
250     do {
251         ++seq;
252         filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
253     } while (fileExists(filename));
254
255     if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
256         return String();
257
258     // If this origin's quota is being tracked (open handle to a database in this origin), add this new database
259     // to the quota manager now
260     {
261         Locker<OriginQuotaManager> locker(originQuotaManager());
262         if (originQuotaManager().tracksOrigin(origin))
263             originQuotaManager().addDatabase(origin, name, filename);
264     }
265     
266     return filename;
267 }
268
269 void DatabaseTracker::populateOrigins()
270 {
271     if (m_quotaMap)
272         return;
273
274     ASSERT(currentThread() == m_thread);
275
276     m_quotaMap.set(new QuotaMap);
277     m_quotaManager.set(new OriginQuotaManager);
278
279     openTrackerDatabase(false);
280     if (!m_database.isOpen())
281         return;
282
283     SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
284
285     if (statement.prepare() != SQLResultOk)
286         return;
287
288     int result;
289     while ((result = statement.step()) == SQLResultRow) {
290         RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromIdentifier(statement.getColumnText(0));
291         m_quotaMap->set(origin.get(), statement.getColumnInt64(1));
292     }
293
294     if (result != SQLResultDone)
295         LOG_ERROR("Failed to read in all origins from the database");
296 }
297
298 void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
299 {
300     ASSERT(currentThread() == m_thread);
301     populateOrigins();
302     MutexLocker lockQuotaMap(m_quotaMapGuard);
303     copyKeysToVector(*m_quotaMap, result);
304 }
305
306 bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector)
307 {
308     ASSERT(currentThread() == m_thread);
309     openTrackerDatabase(false);
310     if (!m_database.isOpen())
311         return false;
312
313     SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
314
315     if (statement.prepare() != SQLResultOk)
316         return false;
317
318     statement.bindText(1, origin->stringIdentifier());
319
320     int result;
321     while ((result = statement.step()) == SQLResultRow)
322         resultVector.append(statement.getColumnText(0));
323
324     if (result != SQLResultDone) {
325         LOG_ERROR("Failed to retrieve all database names for origin %s", origin->stringIdentifier().ascii().data());
326         return false;
327     }
328
329     return true;
330 }
331
332 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin)
333 {
334     ASSERT(currentThread() == m_thread);
335
336     if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name)
337         return m_proposedDatabase->second;
338
339     String originIdentifier = origin->stringIdentifier();
340
341     openTrackerDatabase(false);
342     if (!m_database.isOpen())
343         return DatabaseDetails();
344     SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
345     if (statement.prepare() != SQLResultOk)
346         return DatabaseDetails();
347    
348     statement.bindText(1, originIdentifier);
349     statement.bindText(2, name);
350     
351     int result = statement.step();
352     if (result == SQLResultDone)
353         return DatabaseDetails();
354     
355     if (result != SQLResultRow) {
356         LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
357         return DatabaseDetails();
358     }
359     
360     return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
361 }
362
363 void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize)
364 {
365     ASSERT(currentThread() == m_thread);
366
367     String originIdentifier = origin->stringIdentifier();
368     int64_t guid = 0;
369     
370     openTrackerDatabase(true);
371     if (!m_database.isOpen())
372         return;
373     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
374     if (statement.prepare() != SQLResultOk)
375         return;
376         
377     statement.bindText(1, originIdentifier);
378     statement.bindText(2, name);
379     
380     int result = statement.step();
381     if (result == SQLResultRow)
382         guid = statement.getColumnInt64(0);
383     statement.finalize();
384
385     if (guid == 0) {
386         if (result != SQLResultDone)
387             LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
388         else {
389             // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
390             // But since the tracker file is an external resource not under complete control of our code, it's somewhat invalid to make this an ASSERT case
391             // So we'll print an error instead
392             LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker",
393                        name.ascii().data(), originIdentifier.ascii().data());
394         }
395         return;
396     }
397     
398     SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
399     if (updateStatement.prepare() != SQLResultOk)
400         return;
401     
402     updateStatement.bindText(1, displayName);
403     updateStatement.bindInt64(2, estimatedSize);
404     updateStatement.bindInt64(3, guid);
405     
406     if (updateStatement.step() != SQLResultDone) {
407         LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
408         return;  
409     }
410     
411     if (m_client)
412         m_client->dispatchDidModifyDatabase(origin, name);
413 }
414
415 unsigned long long DatabaseTracker::usageForDatabase(const String& name, SecurityOrigin* origin)
416 {
417     ASSERT(currentThread() == m_thread);
418     String path = fullPathForDatabase(origin, name, false);
419     if (path.isEmpty())
420         return 0;
421         
422     long long size;
423     return getFileSize(path, size) ? size : 0;
424 }
425
426 void DatabaseTracker::addOpenDatabase(Database* database)
427 {
428     if (!database)
429         return;
430
431     MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
432
433     if (!m_openDatabaseMap)
434         m_openDatabaseMap.set(new DatabaseOriginMap);
435
436     RefPtr<SecurityOrigin> origin(database->securityOriginCopy());
437     String name(database->stringIdentifier());
438
439     DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
440     if (!nameMap) {
441         nameMap = new DatabaseNameMap;
442         m_openDatabaseMap->set(origin, nameMap);
443     }
444
445     DatabaseSet* databaseSet = nameMap->get(name);
446     if (!databaseSet) {
447         databaseSet = new DatabaseSet;
448         nameMap->set(name, databaseSet);
449     }
450
451     databaseSet->add(database);
452
453     LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
454 }
455
456 void DatabaseTracker::removeOpenDatabase(Database* database)
457 {
458     if (!database)
459         return;
460
461     MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
462
463     if (!m_openDatabaseMap) {
464         ASSERT_NOT_REACHED();
465         return;
466     }
467
468     RefPtr<SecurityOrigin> origin(database->securityOriginCopy());
469     String name(database->stringIdentifier());
470
471     DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
472     if (!nameMap) {
473         ASSERT_NOT_REACHED();
474         return;
475     }
476
477     DatabaseSet* databaseSet = nameMap->get(name);
478     if (!databaseSet) {
479         ASSERT_NOT_REACHED();
480         return;
481     }
482
483     databaseSet->remove(database);
484
485     LOG(StorageAPI, "Removed open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
486
487     if (!databaseSet->isEmpty())
488         return;
489
490     nameMap->remove(name);
491     delete databaseSet;
492
493     if (!nameMap->isEmpty())
494         return;
495
496     m_openDatabaseMap->remove(origin);
497     delete nameMap;
498 }
499
500 unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
501 {
502     ASSERT(currentThread() == m_thread);
503     Locker<OriginQuotaManager> locker(originQuotaManager());
504
505     // Use the OriginQuotaManager mechanism to calculate the usage
506     if (originQuotaManager().tracksOrigin(origin))
507         return originQuotaManager().diskUsage(origin);
508     
509     // If the OriginQuotaManager doesn't track this origin already, prime it to do so
510     originQuotaManager().trackOrigin(origin);
511     
512     Vector<String> names;
513     databaseNamesForOrigin(origin, names);
514
515     for (unsigned i = 0; i < names.size(); ++i)
516         originQuotaManager().addDatabase(origin, names[i], fullPathForDatabase(origin, names[i], false));
517     
518     if (!originQuotaManager().tracksOrigin(origin))
519         return 0;
520     return originQuotaManager().diskUsage(origin);
521 }
522
523 unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin)
524 {
525     ASSERT(currentThread() == m_thread || m_quotaMap);
526     populateOrigins();
527     MutexLocker lockQuotaMap(m_quotaMapGuard);
528     return m_quotaMap->get(origin);
529 }
530
531 void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota)
532 {
533     ASSERT(currentThread() == m_thread);
534     if (quotaForOrigin(origin) == quota)
535         return;
536
537     openTrackerDatabase(true);
538     if (!m_database.isOpen())
539         return;
540
541     {
542         MutexLocker lockQuotaMap(m_quotaMapGuard);
543
544         if (!m_quotaMap->contains(origin)) {
545             SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
546             if (statement.prepare() != SQLResultOk) {
547                 LOG_ERROR("Unable to establish origin %s in the tracker", origin->stringIdentifier().ascii().data());
548             } else {
549                 statement.bindText(1, origin->stringIdentifier());
550                 statement.bindInt64(2, quota);
551
552                 if (statement.step() != SQLResultDone)
553                     LOG_ERROR("Unable to establish origin %s in the tracker", origin->stringIdentifier().ascii().data());
554             }
555         } else {
556             SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");        
557             bool error = statement.prepare() != SQLResultOk;
558             if (!error) {
559                 statement.bindInt64(1, quota);
560                 statement.bindText(2, origin->stringIdentifier());
561
562                 error = !statement.executeCommand();
563             }
564
565             if (error)
566                 LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->stringIdentifier().ascii().data());
567         }
568
569         // FIXME: Is it really OK to update the quota in memory if we failed to update it on disk?
570         m_quotaMap->set(origin, quota);
571     }
572
573     if (m_client)
574         m_client->dispatchDidModifyOrigin(origin);
575 }
576
577 bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path)
578 {
579     ASSERT(currentThread() == m_thread);
580     openTrackerDatabase(true);
581     if (!m_database.isOpen())
582         return false;
583         
584     // New database should never be added until the origin has been established
585     ASSERT(hasEntryForOrigin(origin));
586
587     SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
588
589     if (statement.prepare() != SQLResultOk)
590         return false;
591
592     statement.bindText(1, origin->stringIdentifier());
593     statement.bindText(2, name);
594     statement.bindText(3, path);
595
596     if (!statement.executeCommand()) {
597         LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin->stringIdentifier().ascii().data(), m_database.lastErrorMsg());
598         return false;
599     }
600     
601     if (m_client)
602         m_client->dispatchDidModifyOrigin(origin);
603     
604     return true;
605 }
606
607 void DatabaseTracker::deleteAllDatabases()
608 {
609     ASSERT(currentThread() == m_thread);
610
611     Vector<RefPtr<SecurityOrigin> > originsCopy;
612     origins(originsCopy);
613
614     for (unsigned i = 0; i < originsCopy.size(); ++i)
615         deleteOrigin(originsCopy[i].get());
616 }
617
618 void DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
619 {
620     ASSERT(currentThread() == m_thread);
621     openTrackerDatabase(false);
622     if (!m_database.isOpen())
623         return;
624
625     Vector<String> databaseNames;
626     if (!databaseNamesForOrigin(origin, databaseNames)) {
627         LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->stringIdentifier().ascii().data());
628         return;
629     }
630     
631     for (unsigned i = 0; i < databaseNames.size(); ++i) {
632         if (!deleteDatabaseFile(origin, databaseNames[i])) {
633             LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->stringIdentifier().ascii().data());
634             return;
635         }
636     }
637     
638     SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
639     if (statement.prepare() != SQLResultOk) {
640         LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
641         return;
642     }
643         
644     statement.bindText(1, origin->stringIdentifier());
645     
646     if (!statement.executeCommand()) {
647         LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
648         return;
649     }
650     
651     SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
652     if (originStatement.prepare() != SQLResultOk) {
653         LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin->stringIdentifier().ascii().data());
654         return;
655     }
656
657     originStatement.bindText(1, origin->stringIdentifier());
658     
659     if (!originStatement.executeCommand()) {
660         LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->stringIdentifier().ascii().data());
661         return;
662     }
663
664     deleteEmptyDirectory(originPath(origin));
665
666     RefPtr<SecurityOrigin> originPossiblyLastReference = origin;
667     {
668         MutexLocker lockQuotaMap(m_quotaMapGuard);
669         m_quotaMap->remove(origin);
670
671         Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
672         originQuotaManager().removeOrigin(origin);
673
674         // If we removed the last origin, do some additional deletion.
675         if (m_quotaMap->isEmpty()) {
676             if (m_database.isOpen())
677                 m_database.close();
678             deleteFile(trackerDatabasePath());
679             deleteEmptyDirectory(m_databaseDirectoryPath);
680         }
681     }
682
683     if (m_client) {
684         m_client->dispatchDidModifyOrigin(origin);
685         for (unsigned i = 0; i < databaseNames.size(); ++i)
686             m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
687     }
688 }
689
690 void DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name)
691 {
692     ASSERT(currentThread() == m_thread);
693     openTrackerDatabase(false);
694     if (!m_database.isOpen())
695         return;
696
697     if (!deleteDatabaseFile(origin, name)) {
698         LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->stringIdentifier().ascii().data());
699         return;
700     }
701     
702     SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
703     if (statement.prepare() != SQLResultOk) {
704         LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin->stringIdentifier().ascii().data());
705         return;
706     }
707         
708     statement.bindText(1, origin->stringIdentifier());
709     statement.bindText(2, name);
710     
711     if (!statement.executeCommand()) {
712         LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin->stringIdentifier().ascii().data());
713         return;
714     }
715     
716     {
717         Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
718         originQuotaManager().removeDatabase(origin, name);
719     }
720     
721     if (m_client) {
722         m_client->dispatchDidModifyOrigin(origin);
723         m_client->dispatchDidModifyDatabase(origin, name);
724     }
725 }
726
727 bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name)
728 {
729     ASSERT(currentThread() == m_thread);
730     String fullPath = fullPathForDatabase(origin, name, false);
731     if (fullPath.isEmpty())
732         return true;
733
734     {
735         MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
736         if (m_openDatabaseMap) {
737             // There are some open databases, lets check if they are for this origin.
738             DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
739             if (nameMap && nameMap->size()) {
740                 // There are some open databases for this origin, lets check
741                 // if they are this database by name.
742                 DatabaseSet* databaseSet = nameMap->get(name);
743                 if (databaseSet && databaseSet->size()) {
744                     // We have some database open with this name. Mark them as deleted.
745                     DatabaseSet::const_iterator end = databaseSet->end();
746                     for (DatabaseSet::const_iterator it = databaseSet->begin(); it != end; ++it)
747                         (*it)->markAsDeletedAndClose();
748                 }
749             }
750         }
751     }
752
753     return deleteFile(fullPath);
754 }
755
756 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
757 {
758     ASSERT(currentThread() == m_thread);
759     m_client = client;
760 }
761
762 static Mutex& notificationMutex()
763 {
764     static Mutex mutex;
765     return mutex;
766 }
767
768 static Vector<pair<SecurityOrigin*, String> >& notificationQueue()
769 {
770     static Vector<pair<SecurityOrigin*, String> > queue;
771     return queue;
772 }
773
774 void DatabaseTracker::scheduleNotifyDatabaseChanged(SecurityOrigin* origin, const String& name)
775 {
776     MutexLocker locker(notificationMutex());
777
778     notificationQueue().append(pair<SecurityOrigin*, String>(origin, name.copy()));
779     scheduleForNotification();
780 }
781
782 static bool notificationScheduled = false;
783
784 void DatabaseTracker::scheduleForNotification()
785 {
786     ASSERT(!notificationMutex().tryLock());
787
788     if (!notificationScheduled) {
789         callOnMainThread(DatabaseTracker::notifyDatabasesChanged, 0);
790         notificationScheduled = true;
791     }
792 }
793
794 void DatabaseTracker::notifyDatabasesChanged(void*)
795 {
796     // Note that if DatabaseTracker ever becomes non-singleton, we'll have to amend this notification
797     // mechanism to include which tracker the notification goes out on as well.
798     DatabaseTracker& theTracker(tracker());
799
800     Vector<pair<SecurityOrigin*, String> > notifications;
801     {
802         MutexLocker locker(notificationMutex());
803
804         notifications.swap(notificationQueue());
805
806         notificationScheduled = false;
807     }
808
809     if (!theTracker.m_client)
810         return;
811
812     for (unsigned i = 0; i < notifications.size(); ++i)
813         theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first, notifications[i].second);
814 }
815
816
817 } // namespace WebCore