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