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