DatabaseTracker.cpp [plain text]
#include "config.h"
#include "DatabaseTracker.h"
#if ENABLE(DATABASE)
#include "Chrome.h"
#include "ChromeClient.h"
#include "Database.h"
#include "DatabaseThread.h"
#include "DatabaseTrackerClient.h"
#include "FileSystem.h"
#include "Logging.h"
#include "OriginQuotaManager.h"
#include "Page.h"
#include "ScriptExecutionContext.h"
#include "SecurityOrigin.h"
#include "SecurityOriginHash.h"
#include "SQLiteFileSystem.h"
#include "SQLiteStatement.h"
#include <wtf/MainThread.h>
#include <wtf/StdLibExtras.h>
#include "WebCoreThread.h"
using namespace std;
static WebCore::OriginQuotaManager& originQuotaManager()
{
DEFINE_STATIC_LOCAL(WebCore::OriginQuotaManager, quotaManager, ());
return quotaManager;
}
namespace WebCore {
static DatabaseTracker* staticTracker = 0;
void DatabaseTracker::initializeTracker(const String& databasePath)
{
ASSERT(!staticTracker);
if (staticTracker)
return;
staticTracker = new DatabaseTracker(databasePath);
}
DatabaseTracker& DatabaseTracker::tracker()
{
if (!staticTracker)
staticTracker = new DatabaseTracker("");
return *staticTracker;
}
DatabaseTracker::DatabaseTracker(const String& databasePath)
: m_client(0)
{
setDatabaseDirectoryPath(databasePath);
SQLiteFileSystem::registerSQLiteVFS();
MutexLocker lockDatabase(m_databaseGuard);
populateOrigins();
}
void DatabaseTracker::setDatabaseDirectoryPath(const String& path)
{
MutexLocker lockDatabase(m_databaseGuard);
ASSERT(!m_database.isOpen());
m_databaseDirectoryPath = path.threadsafeCopy();
}
String DatabaseTracker::databaseDirectoryPath() const
{
return m_databaseDirectoryPath.threadsafeCopy();
}
String DatabaseTracker::trackerDatabasePath() const
{
return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath, "Databases.db");
}
void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(WebThreadIsLockedOrDisabled());
if (m_database.isOpen())
return;
String databasePath = trackerDatabasePath();
if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist))
return;
if (!m_database.open(databasePath)) {
LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
return;
}
m_database.disableThreadingChecks();
if (!m_database.tableExists("Origins")) {
if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
LOG_ERROR("Failed to create Origins table");
}
}
if (!m_database.tableExists("Databases")) {
if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
LOG_ERROR("Failed to create Databases table");
}
}
}
bool DatabaseTracker::canEstablishDatabase(ScriptExecutionContext* context, const String& name, const String& displayName, unsigned long estimatedSize)
{
SecurityOrigin* origin = context->securityOrigin();
ProposedDatabase details;
unsigned long long requirement;
unsigned long long tempUsage;
{
MutexLocker lockDatabase(m_databaseGuard);
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
if (!canCreateDatabase(origin, name))
return false;
recordCreatingDatabase(origin, name);
unsigned long long usage = usageForOriginNoLock(origin);
if (hasEntryForDatabase(origin, name))
return true;
requirement = usage + max(1UL, estimatedSize);
tempUsage = usage;
if (requirement < usage) {
doneCreatingDatabase(origin, name);
return false; }
if (requirement <= quotaForOriginNoLock(origin))
return true;
details = ProposedDatabase(origin->threadsafeCopy(), DatabaseDetails(name.threadsafeCopy(), displayName.threadsafeCopy(), estimatedSize, 0));
m_proposedDatabases.add(&details);
}
context->databaseExceededQuota(name);
MutexLocker lockDatabase(m_databaseGuard);
m_proposedDatabases.remove(&details);
if (requirement <= quotaForOriginNoLock(origin))
return true;
doneCreatingDatabase(origin, name);
return false;
}
bool DatabaseTracker::hasEntryForOriginNoLock(SecurityOrigin* origin)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(m_quotaMap);
return m_quotaMap->contains(origin);
}
bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin)
{
MutexLocker lockDatabase(m_databaseGuard);
return hasEntryForOriginNoLock(origin);
}
bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(WebThreadIsLockedOrDisabled());
openTrackerDatabase(false);
if (!m_database.isOpen())
return false;
SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
if (statement.prepare() != SQLResultOk)
return false;
statement.bindText(1, origin->databaseIdentifier());
statement.bindText(2, databaseIdentifier);
return statement.step() == SQLResultRow;
}
unsigned long long DatabaseTracker::getMaxSizeForDatabase(const Database* database)
{
ASSERT(currentThread() == database->scriptExecutionContext()->databaseThread()->getThreadID());
MutexLocker lockDatabase(m_databaseGuard);
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
SecurityOrigin* origin = database->securityOrigin();
return quotaForOriginNoLock(origin) - originQuotaManager().diskUsage(origin) + SQLiteFileSystem::getDatabaseFileSize(database->fileName());
}
void DatabaseTracker::databaseChanged(Database* database)
{
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
originQuotaManager().markDatabase(database);
}
String DatabaseTracker::originPath(SecurityOrigin* origin) const
{
return SQLiteFileSystem::appendDatabaseFileNameToPath(m_databaseDirectoryPath.threadsafeCopy(), origin->databaseIdentifier());
}
String DatabaseTracker::fullPathForDatabaseNoLock(SecurityOrigin* origin, const String& name, bool createIfNotExists)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(!originQuotaManager().tryLock());
ASSERT(WebThreadIsLockedOrDisabled());
for (HashSet<ProposedDatabase*>::iterator iter = m_proposedDatabases.begin(); iter != m_proposedDatabases.end(); ++iter)
if ((*iter)->second.name() == name && (*iter)->first->equal(origin))
return String();
String originIdentifier = origin->databaseIdentifier();
String originPath = this->originPath(origin);
if (createIfNotExists && !SQLiteFileSystem::ensureDatabaseDirectoryExists(originPath))
return String();
if (!m_database.isOpen())
return String();
SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
if (statement.prepare() != SQLResultOk)
return String();
statement.bindText(1, originIdentifier);
statement.bindText(2, name);
int result = statement.step();
if (result == SQLResultRow)
return SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, statement.getColumnText(0));
if (!createIfNotExists)
return String();
if (result != SQLResultDone) {
LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", originIdentifier.ascii().data(), name.ascii().data());
return String();
}
statement.finalize();
String fileName = SQLiteFileSystem::getFileNameForNewDatabase(originPath, name, originIdentifier, &m_database);
if (!addDatabase(origin, name, fileName))
return String();
String fullFilePath = SQLiteFileSystem::appendDatabaseFileNameToPath(originPath, fileName);
if (originQuotaManager().tracksOrigin(origin))
originQuotaManager().addDatabase(origin, name, fullFilePath);
return fullFilePath;
}
String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists)
{
MutexLocker lockDatabase(m_databaseGuard);
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
return fullPathForDatabaseNoLock(origin, name, createIfNotExists).threadsafeCopy();
}
void DatabaseTracker::populateOrigins()
{
ASSERT(!m_databaseGuard.tryLock());
if (m_quotaMap)
return;
m_quotaMap.set(new QuotaMap);
openTrackerDatabase(false);
if (!m_database.isOpen())
return;
SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Failed to prepare statement.");
return;
}
int result;
while ((result = statement.step()) == SQLResultRow) {
RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromDatabaseIdentifier(statement.getColumnText(0));
m_quotaMap->set(origin.get()->threadsafeCopy(), statement.getColumnInt64(1));
}
if (result != SQLResultDone)
LOG_ERROR("Failed to read in all origins from the database.");
}
void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
{
MutexLocker lockDatabase(m_databaseGuard);
ASSERT(m_quotaMap);
copyKeysToVector(*m_quotaMap, result);
}
bool DatabaseTracker::databaseNamesForOriginNoLock(SecurityOrigin* origin, Vector<String>& resultVector)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(WebThreadIsLockedOrDisabled());
openTrackerDatabase(false);
if (!m_database.isOpen())
return false;
SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
if (statement.prepare() != SQLResultOk)
return false;
statement.bindText(1, origin->databaseIdentifier());
int result;
while ((result = statement.step()) == SQLResultRow)
resultVector.append(statement.getColumnText(0));
if (result != SQLResultDone) {
LOG_ERROR("Failed to retrieve all database names for origin %s", origin->databaseIdentifier().ascii().data());
return false;
}
return true;
}
bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector)
{
Vector<String> temp;
{
MutexLocker lockDatabase(m_databaseGuard);
if (!databaseNamesForOriginNoLock(origin, temp))
return false;
}
for (Vector<String>::iterator iter = temp.begin(); iter != temp.end(); ++iter)
resultVector.append(iter->threadsafeCopy());
return true;
}
DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin)
{
String originIdentifier = origin->databaseIdentifier();
String displayName;
int64_t expectedUsage;
{
MutexLocker lockDatabase(m_databaseGuard);
for (HashSet<ProposedDatabase*>::iterator iter = m_proposedDatabases.begin(); iter != m_proposedDatabases.end(); ++iter)
if ((*iter)->second.name() == name && (*iter)->first->equal(origin)) {
ASSERT((*iter)->second.thread() == currentThread() || ((isMainThread() || pthread_main_np()) && WebThreadIsLockedOrDisabled()));
return (*iter)->second;
}
openTrackerDatabase(false);
if (!m_database.isOpen())
return DatabaseDetails();
SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
if (statement.prepare() != SQLResultOk)
return DatabaseDetails();
statement.bindText(1, originIdentifier);
statement.bindText(2, name);
int result = statement.step();
if (result == SQLResultDone)
return DatabaseDetails();
if (result != SQLResultRow) {
LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
return DatabaseDetails();
}
displayName = statement.getColumnText(0);
expectedUsage = statement.getColumnInt64(1);
}
return DatabaseDetails(name, displayName, expectedUsage, usageForDatabase(name, origin));
}
void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize)
{
String originIdentifier = origin->databaseIdentifier();
int64_t guid = 0;
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(true);
if (!m_database.isOpen())
return;
SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
if (statement.prepare() != SQLResultOk)
return;
statement.bindText(1, originIdentifier);
statement.bindText(2, name);
int result = statement.step();
if (result == SQLResultRow)
guid = statement.getColumnInt64(0);
statement.finalize();
if (guid == 0) {
if (result != SQLResultDone)
LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
else {
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",
name.ascii().data(), originIdentifier.ascii().data());
}
return;
}
SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
if (updateStatement.prepare() != SQLResultOk)
return;
updateStatement.bindText(1, displayName);
updateStatement.bindInt64(2, estimatedSize);
updateStatement.bindInt64(3, guid);
if (updateStatement.step() != SQLResultDone) {
LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
return;
}
if (m_client)
m_client->dispatchDidModifyDatabase(origin, name);
}
unsigned long long DatabaseTracker::usageForDatabase(const String& name, SecurityOrigin* origin)
{
String path = fullPathForDatabase(origin, name, false);
if (path.isEmpty())
return 0;
return SQLiteFileSystem::getDatabaseFileSize(path);
}
void DatabaseTracker::addOpenDatabase(Database* database)
{
if (!database)
return;
{
MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap)
m_openDatabaseMap.set(new DatabaseOriginMap);
String name(database->stringIdentifier());
DatabaseNameMap* nameMap = m_openDatabaseMap->get(database->securityOrigin());
if (!nameMap) {
nameMap = new DatabaseNameMap;
m_openDatabaseMap->set(database->securityOrigin()->threadsafeCopy(), nameMap);
}
DatabaseSet* databaseSet = nameMap->get(name);
if (!databaseSet) {
databaseSet = new DatabaseSet;
nameMap->set(name.threadsafeCopy(), databaseSet);
}
databaseSet->add(database);
LOG(StorageAPI, "Added open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
}
MutexLocker lockDatabase(m_databaseGuard);
doneCreatingDatabase(database->securityOrigin(), database->stringIdentifier());
}
void DatabaseTracker::removeOpenDatabase(Database* database)
{
if (!database)
return;
{
MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap) {
ASSERT_NOT_REACHED();
return;
}
String name(database->stringIdentifier());
DatabaseNameMap* nameMap = m_openDatabaseMap->get(database->securityOrigin());
if (!nameMap) {
ASSERT_NOT_REACHED();
return;
}
DatabaseSet* databaseSet = nameMap->get(name);
if (!databaseSet) {
ASSERT_NOT_REACHED();
return;
}
databaseSet->remove(database);
LOG(StorageAPI, "Removed open Database %s (%p)\n", database->stringIdentifier().ascii().data(), database);
if (!databaseSet->isEmpty())
return;
nameMap->remove(name);
delete databaseSet;
if (!nameMap->isEmpty())
return;
m_openDatabaseMap->remove(database->securityOrigin());
delete nameMap;
}
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
originQuotaManager().removeOrigin(database->securityOrigin());
}
void DatabaseTracker::getOpenDatabases(SecurityOrigin* origin, const String& name, HashSet<RefPtr<Database> >* databases)
{
MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap)
return;
DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
if (!nameMap)
return;
DatabaseSet* databaseSet = nameMap->get(name);
if (!databaseSet)
return;
for (DatabaseSet::iterator it = databaseSet->begin(); it != databaseSet->end(); ++it)
databases->add(*it);
}
unsigned long long DatabaseTracker::usageForOriginNoLock(SecurityOrigin* origin)
{
ASSERT(!originQuotaManager().tryLock());
ASSERT(WebThreadIsLockedOrDisabled());
if (originQuotaManager().tracksOrigin(origin))
return originQuotaManager().diskUsage(origin);
originQuotaManager().trackOrigin(origin);
Vector<String> names;
databaseNamesForOriginNoLock(origin, names);
for (unsigned i = 0; i < names.size(); ++i)
originQuotaManager().addDatabase(origin, names[i], fullPathForDatabaseNoLock(origin, names[i], false));
if (!originQuotaManager().tracksOrigin(origin))
return 0;
return originQuotaManager().diskUsage(origin);
}
unsigned long long DatabaseTracker::usageForOrigin(SecurityOrigin* origin)
{
MutexLocker lockDatabase(m_databaseGuard);
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
return usageForOriginNoLock(origin);
}
unsigned long long DatabaseTracker::quotaForOriginNoLock(SecurityOrigin* origin)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(m_quotaMap);
return m_quotaMap->get(origin);
}
unsigned long long DatabaseTracker::quotaForOrigin(SecurityOrigin* origin)
{
MutexLocker lockDatabase(m_databaseGuard);
return quotaForOriginNoLock(origin);
}
void DatabaseTracker::setQuota(SecurityOrigin* origin, unsigned long long quota)
{
MutexLocker lockDatabase(m_databaseGuard);
if (quotaForOriginNoLock(origin) == quota)
return;
openTrackerDatabase(true);
if (!m_database.isOpen())
return;
bool insertedNewOrigin = false;
if (!m_quotaMap->contains(origin)) {
SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data());
} else {
statement.bindText(1, origin->databaseIdentifier());
statement.bindInt64(2, quota);
if (statement.step() != SQLResultDone)
LOG_ERROR("Unable to establish origin %s in the tracker", origin->databaseIdentifier().ascii().data());
else
insertedNewOrigin = true;
}
} else {
SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
bool error = statement.prepare() != SQLResultOk;
if (!error) {
statement.bindInt64(1, quota);
statement.bindText(2, origin->databaseIdentifier());
error = !statement.executeCommand();
}
if (error)
LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin->databaseIdentifier().ascii().data());
}
m_quotaMap->set(origin->threadsafeCopy(), quota);
if (m_client)
{
if (insertedNewOrigin)
m_client->dispatchDidAddNewOrigin(origin);
m_client->dispatchDidModifyOrigin(origin);
}
}
bool DatabaseTracker::addDatabase(SecurityOrigin* origin, const String& name, const String& path)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(m_quotaMap);
ASSERT(WebThreadIsLockedOrDisabled());
openTrackerDatabase(true);
if (!m_database.isOpen())
return false;
ASSERT(hasEntryForOriginNoLock(origin));
SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
if (statement.prepare() != SQLResultOk)
return false;
statement.bindText(1, origin->databaseIdentifier());
statement.bindText(2, name);
statement.bindText(3, path);
if (!statement.executeCommand()) {
LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin->databaseIdentifier().ascii().data(), m_database.lastErrorMsg());
return false;
}
if (m_client)
m_client->dispatchDidModifyOrigin(origin);
return true;
}
void DatabaseTracker::deleteAllDatabases()
{
Vector<RefPtr<SecurityOrigin> > originsCopy;
origins(originsCopy);
for (unsigned i = 0; i < originsCopy.size(); ++i)
deleteOrigin(originsCopy[i].get());
}
bool DatabaseTracker::deleteOrigin(SecurityOrigin* origin)
{
Vector<String> databaseNames;
{
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(false);
if (!m_database.isOpen())
return false;
if (!databaseNamesForOriginNoLock(origin, databaseNames)) {
LOG_ERROR("Unable to retrieve list of database names for origin %s", origin->databaseIdentifier().ascii().data());
return false;
}
if (!canDeleteOrigin(origin)) {
LOG_ERROR("Tried to delete an origin (%s) while either creating database in it or already deleting it", origin->databaseIdentifier().ascii().data());
ASSERT(false);
return false;
}
recordDeletingOrigin(origin);
}
for (unsigned i = 0; i < databaseNames.size(); ++i) {
if (!deleteDatabaseFile(origin, databaseNames[i])) {
LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin->databaseIdentifier().ascii().data());
}
}
{
MutexLocker lockDatabase(m_databaseGuard);
doneDeletingOrigin(origin);
SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
return false;
}
statement.bindText(1, origin->databaseIdentifier());
if (!statement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
return false;
}
SQLiteStatement originStatement(m_database, "DELETE FROM Origins WHERE origin=?");
if (originStatement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to prepare deletion of origin %s from tracker", origin->databaseIdentifier().ascii().data());
return false;
}
originStatement.bindText(1, origin->databaseIdentifier());
if (!originStatement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin->databaseIdentifier().ascii().data());
return false;
}
SQLiteFileSystem::deleteEmptyDatabaseDirectory(originPath(origin));
RefPtr<SecurityOrigin> originPossiblyLastReference = origin;
m_quotaMap->remove(origin);
{
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
originQuotaManager().removeOrigin(origin);
}
if (m_quotaMap->isEmpty()) {
if (m_database.isOpen())
m_database.close();
SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectoryPath);
}
if (m_client) {
m_client->dispatchDidModifyOrigin(origin);
m_client->dispatchDidDeleteDatabaseOrigin();
for (unsigned i = 0; i < databaseNames.size(); ++i)
m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
}
}
return true;
}
bool DatabaseTracker::canCreateDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
return !deletingDatabase(origin, name) && !deletingOrigin(origin);
}
void DatabaseTracker::recordCreatingDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
NameCountMap* nameMap = m_beingCreated.get(origin);
if (!nameMap) {
nameMap = new NameCountMap();
m_beingCreated.set(origin->threadsafeCopy(), nameMap);
}
long count = nameMap->get(name);
nameMap->set(name.threadsafeCopy(), count + 1);
}
void DatabaseTracker::doneCreatingDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
NameCountMap* nameMap = m_beingCreated.get(origin);
if (nameMap) {
long count = nameMap->get(name);
ASSERT(count > 0);
if (count <= 1) {
nameMap->remove(name);
if (nameMap->isEmpty()) {
m_beingCreated.remove(origin);
delete nameMap;
}
} else
nameMap->set(name, count - 1);
} else
ASSERT(false);
}
bool DatabaseTracker::creatingDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
NameCountMap* nameMap = m_beingCreated.get(origin);
return nameMap && nameMap->get(name);
}
bool DatabaseTracker::canDeleteDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
return !creatingDatabase(origin, name) && !deletingDatabase(origin, name);
}
void DatabaseTracker::recordDeletingDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(canDeleteDatabase(origin, name));
NameSet* nameSet = m_beingDeleted.get(origin);
if (!nameSet) {
nameSet = new NameSet();
m_beingDeleted.set(origin->threadsafeCopy(), nameSet);
}
ASSERT(!nameSet->contains(name));
nameSet->add(name.threadsafeCopy());
}
void DatabaseTracker::doneDeletingDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
NameSet* nameSet = m_beingDeleted.get(origin);
if (nameSet) {
ASSERT(nameSet->contains(name));
nameSet->remove(name);
if (nameSet->isEmpty()) {
m_beingDeleted.remove(origin);
delete nameSet;
}
} else {
ASSERT(false);
}
}
bool DatabaseTracker::deletingDatabase(SecurityOrigin *origin, const String& name)
{
ASSERT(!m_databaseGuard.tryLock());
NameSet* nameSet = m_beingDeleted.get(origin);
return nameSet && nameSet->contains(name);
}
bool DatabaseTracker::canDeleteOrigin(SecurityOrigin *origin)
{
ASSERT(!m_databaseGuard.tryLock());
return !(deletingOrigin(origin) || m_beingCreated.get(origin));
}
bool DatabaseTracker::deletingOrigin(SecurityOrigin *origin)
{
ASSERT(!m_databaseGuard.tryLock());
return m_originsBeingDeleted.contains(origin);
}
void DatabaseTracker::recordDeletingOrigin(SecurityOrigin *origin)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(!deletingOrigin(origin));
m_originsBeingDeleted.add(origin->threadsafeCopy());
}
void DatabaseTracker::doneDeletingOrigin(SecurityOrigin *origin)
{
ASSERT(!m_databaseGuard.tryLock());
ASSERT(deletingOrigin(origin));
m_originsBeingDeleted.remove(origin);
}
bool DatabaseTracker::deleteDatabase(SecurityOrigin* origin, const String& name)
{
{
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(false);
if (!m_database.isOpen())
return false;
if (!canDeleteDatabase(origin, name)) {
ASSERT(FALSE);
return false;
}
recordDeletingDatabase(origin, name);
}
if (!deleteDatabaseFile(origin, name)) {
LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin->databaseIdentifier().ascii().data());
MutexLocker lockDatabase(m_databaseGuard);
doneDeletingDatabase(origin, name);
return false;
}
MutexLocker lockDatabase(m_databaseGuard);
SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
if (statement.prepare() != SQLResultOk) {
LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data());
doneDeletingDatabase(origin, name);
return false;
}
statement.bindText(1, origin->databaseIdentifier());
statement.bindText(2, name);
if (!statement.executeCommand()) {
LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin->databaseIdentifier().ascii().data());
doneDeletingDatabase(origin, name);
return false;
}
{
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
originQuotaManager().removeDatabase(origin, name);
}
if (m_client) {
m_client->dispatchDidModifyOrigin(origin);
m_client->dispatchDidModifyDatabase(origin, name);
m_client->dispatchDidDeleteDatabase();
}
doneDeletingDatabase(origin, name);
return true;
}
bool DatabaseTracker::deleteDatabaseFile(SecurityOrigin* origin, const String& name)
{
String fullPath = fullPathForDatabase(origin, name, false);
if (fullPath.isEmpty())
return true;
#ifndef NDEBUG
{
MutexLocker lockDatabase(m_databaseGuard);
ASSERT(deletingDatabase(origin, name) || deletingOrigin(origin));
}
#endif
Vector<RefPtr<Database> > deletedDatabases;
{
MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
if (m_openDatabaseMap) {
DatabaseNameMap* nameMap = m_openDatabaseMap->get(origin);
if (nameMap && nameMap->size()) {
DatabaseSet* databaseSet = nameMap->get(name);
if (databaseSet && databaseSet->size()) {
DatabaseSet::const_iterator end = databaseSet->end();
for (DatabaseSet::const_iterator it = databaseSet->begin(); it != end; ++it)
deletedDatabases.append(*it);
}
}
}
}
for (unsigned i = 0; i < deletedDatabases.size(); ++i)
deletedDatabases[i]->markAsDeletedAndClose();
SQLiteDatabase database;
if (database.open(fullPath))
return SQLiteFileSystem::truncateDatabaseFile(database.sqlite3Handle());
return false;
}
void DatabaseTracker::originsDidChange()
{
ASSERT(WebThreadIsLockedOrDisabled());
Vector<RefPtr<SecurityOrigin> > changedOrigins;
{
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(false);
if (!m_database.isOpen())
return;
if (!m_quotaMap)
return;
typedef HashSet<RefPtr<SecurityOrigin>, SecurityOriginHash> OriginSet;
OriginSet origins;
QuotaMap::const_iterator quotaMapEnd = m_quotaMap->end();
for (QuotaMap::const_iterator quotaMapIt = m_quotaMap->begin(); quotaMapIt != quotaMapEnd; ++quotaMapIt)
origins.add(quotaMapIt->first);
SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
if (statement.prepare() != SQLResultOk)
return;
int result;
while ((result = statement.step()) == SQLResultRow) {
RefPtr<SecurityOrigin> origin = SecurityOrigin::createFromDatabaseIdentifier(statement.getColumnText(0));
if (m_quotaMap->contains(origin)) {
ASSERT(origins.contains(origin));
origins.remove(origin);
} else {
m_quotaMap->set(origin, statement.getColumnInt64(1));
changedOrigins.append(origin);
}
}
if (result != SQLResultDone)
LOG_ERROR("Failed to read in all origins from the database");
else {
OriginSet::const_iterator originEnd = origins.end();
for (OriginSet::const_iterator originIt = origins.begin(); originIt != originEnd; ++originIt) {
RefPtr<SecurityOrigin> origin = *originIt;
Vector<String> databaseNames;
if (!databaseNamesForOriginNoLock(origin.get(), databaseNames) || !databaseNames.isEmpty())
continue;
m_quotaMap->remove(origin);
{
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
originQuotaManager().removeOrigin(origin.get());
}
changedOrigins.append(origin);
}
}
if (m_quotaMap->isEmpty() && m_database.isOpen())
m_database.close();
}
if (!m_client)
return;
for (size_t i = 0; i < changedOrigins.size(); ++i)
m_client->dispatchDidModifyOrigin(changedOrigins[i].get());
}
void DatabaseTracker::removeDeletedOpenedDatabases()
{
ASSERT(WebThreadIsLockedOrDisabled());
{
MutexLocker lockDatabase(m_databaseGuard);
openTrackerDatabase(false);
}
if (!m_database.isOpen())
return;
Vector<RefPtr<Database> > deletedDatabases;
typedef HashMap<RefPtr<SecurityOrigin>, Vector<String> > DeletedDatabaseMap;
DeletedDatabaseMap deletedDatabaseMap;
{
MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
if (m_openDatabaseMap) {
DatabaseOriginMap::const_iterator originMapEnd = m_openDatabaseMap->end();
for (DatabaseOriginMap::const_iterator originMapIt = m_openDatabaseMap->begin(); originMapIt != originMapEnd; ++originMapIt) {
RefPtr<SecurityOrigin> origin = originMapIt->first;
DatabaseNameMap* databaseNameMap = originMapIt->second;
Vector<String> deletedDatabaseNamesForThisOrigin;
DatabaseNameMap::const_iterator dbNameMapEnd = databaseNameMap->end();
for (DatabaseNameMap::const_iterator dbNameMapIt = databaseNameMap->begin(); dbNameMapIt != dbNameMapEnd; ++dbNameMapIt) {
String databaseName = dbNameMapIt->first;
String databaseFileName;
SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
if (statement.prepare() == SQLResultOk) {
statement.bindText(1, origin->databaseIdentifier());
statement.bindText(2, databaseName);
if (statement.step() == SQLResultRow)
databaseFileName = statement.getColumnText(0);
statement.finalize();
}
bool foundDeletedDatabase = false;
DatabaseSet* databaseSet = dbNameMapIt->second;
DatabaseSet::const_iterator dbEnd = databaseSet->end();
for (DatabaseSet::const_iterator dbIt = databaseSet->begin(); dbIt != dbEnd; ++dbIt) {
Database* db = *dbIt;
if (db->deleted())
continue;
if (databaseFileName.isNull() || databaseFileName != pathGetFileName(db->fileName())) {
deletedDatabases.append(db);
foundDeletedDatabase = true;
}
}
if (foundDeletedDatabase && databaseFileName.isNull())
deletedDatabaseNamesForThisOrigin.append(databaseName);
}
if (!deletedDatabaseNamesForThisOrigin.isEmpty())
deletedDatabaseMap.set(origin, deletedDatabaseNamesForThisOrigin);
}
}
}
for (unsigned i = 0; i < deletedDatabases.size(); ++i)
deletedDatabases[i]->markAsDeletedAndClose();
DeletedDatabaseMap::const_iterator end = deletedDatabaseMap.end();
for (DeletedDatabaseMap::const_iterator it = deletedDatabaseMap.begin(); it != end; ++it) {
SecurityOrigin* origin = it->first.get();
if (m_client)
m_client->dispatchDidModifyOrigin(origin);
const Vector<String>& databaseNames = it->second;
for (unsigned i = 0; i < databaseNames.size(); ++i) {
{
Locker<OriginQuotaManager> quotaManagerLocker(originQuotaManager());
originQuotaManager().removeDatabase(origin, databaseNames[i]);
}
if (m_client)
m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
}
}
}
static bool isZeroByteFile(const String& path)
{
long long size = 0;
return getFileSize(path, size) && !size;
}
bool DatabaseTracker::deleteDatabaseFileIfEmpty(const String& path)
{
if (!isZeroByteFile(path))
return false;
SQLiteDatabase database;
if (!database.open(path))
return false;
SQLiteStatement lockStatement(database, "PRAGMA locking_mode=EXCLUSIVE;");
if (lockStatement.prepare() != SQLResultOk)
return false;
int result = lockStatement.step();
if (result != SQLResultRow && result != SQLResultDone)
return false;
lockStatement.finalize();
SQLiteStatement readStatement(database, "SELECT * FROM sqlite_master LIMIT 1;");
if (readStatement.prepare() != SQLResultOk)
return false;
if (readStatement.step() != SQLResultDone)
return false;
readStatement.finalize();
if (!isZeroByteFile(path))
return false;
return SQLiteFileSystem::deleteDatabaseFile(path);
}
Mutex& DatabaseTracker::openDatabaseMutex()
{
DEFINE_STATIC_LOCAL(Mutex, mutex, ());
return mutex;
}
void DatabaseTracker::emptyDatabaseFilesRemovalTaskWillBeScheduled()
{
openDatabaseMutex().lock();
}
void DatabaseTracker::emptyDatabaseFilesRemovalTaskDidFinish()
{
openDatabaseMutex().unlock();
}
void DatabaseTracker::setDatabasesPaused(bool paused)
{
ASSERT(WebThreadIsLockedOrDisabled() || WebThreadIsCurrent());
MutexLocker openDatabaseMapLock(m_openDatabaseMapGuard);
if (!m_openDatabaseMap)
return;
HashSet<DatabaseThread*> handledThreads;
DatabaseOriginMap::iterator i = m_openDatabaseMap.get()->begin();
DatabaseOriginMap::iterator end = m_openDatabaseMap.get()->end();
for (; i != end; ++i) {
DatabaseNameMap* databaseNameMap = i->second;
DatabaseNameMap::iterator j = databaseNameMap->begin();
DatabaseNameMap::iterator dbNameMapEnd = databaseNameMap->end();
for (; j != dbNameMapEnd; ++j) {
DatabaseSet* databaseSet = j->second;
DatabaseSet::iterator k = databaseSet->begin();
DatabaseSet::iterator dbSetEnd = databaseSet->end();
for (; k != dbSetEnd; ++k) {
if (!(*k)->opened())
continue;
ScriptExecutionContext* context = (*k)->scriptExecutionContext();
if (context) {
DatabaseThread* thread = context->databaseThread();
if (!handledThreads.contains(thread)) {
handledThreads.add(thread);
thread->setPaused(paused);
}
}
}
}
}
}
void DatabaseTracker::setClient(DatabaseTrackerClient* client)
{
m_client = client;
}
static Mutex& notificationMutex()
{
DEFINE_STATIC_LOCAL(Mutex, mutex, ());
return mutex;
}
typedef Vector<pair<RefPtr<SecurityOrigin>, String> > NotificationQueue;
static NotificationQueue& notificationQueue()
{
DEFINE_STATIC_LOCAL(NotificationQueue, queue, ());
return queue;
}
void DatabaseTracker::scheduleNotifyDatabaseChanged(SecurityOrigin* origin, const String& name)
{
MutexLocker locker(notificationMutex());
notificationQueue().append(pair<RefPtr<SecurityOrigin>, String>(origin->threadsafeCopy(), name.crossThreadString()));
scheduleForNotification();
}
static bool notificationScheduled = false;
void DatabaseTracker::scheduleForNotification()
{
ASSERT(!notificationMutex().tryLock());
if (!notificationScheduled) {
callOnMainThread(DatabaseTracker::notifyDatabasesChanged, 0);
notificationScheduled = true;
}
}
void DatabaseTracker::notifyDatabasesChanged(void*)
{
DatabaseTracker& theTracker(tracker());
NotificationQueue notifications;
{
MutexLocker locker(notificationMutex());
notifications.swap(notificationQueue());
notificationScheduled = false;
}
if (!theTracker.m_client)
return;
for (unsigned i = 0; i < notifications.size(); ++i)
theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first.get(), notifications[i].second);
}
} #endif