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