📄 databasetracker.cpp
字号:
/* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */#include "config.h"#include "DatabaseTracker.h"#include "ChromeClient.h"#include "Database.h"#include "DatabaseTrackerClient.h"#include "Document.h"#include "FileSystem.h"#include "Logging.h"#include "OriginQuotaManager.h"#include "Page.h"#include "SecurityOrigin.h"#include "SecurityOriginHash.h"#include "SQLiteStatement.h"#include <wtf/MainThread.h>#include <wtf/StdLibExtras.h>using namespace std;namespace WebCore {OriginQuotaManager& DatabaseTracker::originQuotaManager(){ populateOrigins(); ASSERT(m_quotaManager); return *m_quotaManager;}DatabaseTracker& DatabaseTracker::tracker(){ DEFINE_STATIC_LOCAL(DatabaseTracker, tracker, ()); return tracker;}DatabaseTracker::DatabaseTracker() : m_client(0) , m_proposedDatabase(0)#ifndef NDEBUG , m_thread(currentThread())#endif{}void DatabaseTracker::setDatabaseDirectoryPath(const String& path){ ASSERT(currentThread() == m_thread); ASSERT(!m_database.isOpen()); m_databaseDirectoryPath = path;}const String& DatabaseTracker::databaseDirectoryPath() const{ ASSERT(currentThread() == m_thread); return m_databaseDirectoryPath;}String DatabaseTracker::trackerDatabasePath() const{ ASSERT(currentThread() == m_thread); if (m_databaseDirectoryPath.isEmpty()) return String(); return pathByAppendingComponent(m_databaseDirectoryPath, "Databases.db");}void DatabaseTracker::openTrackerDatabase(bool createIfDoesNotExist){ ASSERT(currentThread() == m_thread); if (m_database.isOpen()) return; String databasePath = trackerDatabasePath(); if (databasePath.isEmpty()) return; if (!createIfDoesNotExist && !fileExists(databasePath)) return; makeAllDirectories(m_databaseDirectoryPath); if (!m_database.open(databasePath)) { // FIXME: What do do here? return; } 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);")) { // FIXME: and here } } 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);")) { // FIXME: and here } }}bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize){ ASSERT(currentThread() == m_thread); // Populate the origins before we establish a database; this guarantees that quotaForOrigin // can run on the database thread later. populateOrigins(); SecurityOrigin* origin = document->securityOrigin(); // Since we're imminently opening a database within this Document's origin, make sure this origin is being tracked by the QuotaTracker // by fetching it's current usage now unsigned long long usage = usageForOrigin(origin); // If a database already exists, ignore the passed-in estimated size and say it's OK. if (hasEntryForDatabase(origin, name)) return true; // If the database will fit, allow its creation. unsigned long long requirement = usage + max(1UL, estimatedSize); if (requirement < usage) return false; // If the estimated size is so big it causes an overflow, don't allow creation. if (requirement <= quotaForOrigin(origin)) return true; // Give the chrome client a chance to increase the quota. // Temporarily make the details of the proposed database available, so the client can get at them. Page* page = document->page(); if (!page) return false; pair<SecurityOrigin*, DatabaseDetails> details(origin, DatabaseDetails(name, displayName, estimatedSize, 0)); m_proposedDatabase = &details; page->chrome()->client()->exceededDatabaseQuota(document->frame(), name); m_proposedDatabase = 0; // If the database will fit now, allow its creation. return requirement <= quotaForOrigin(origin);}bool DatabaseTracker::hasEntryForOrigin(SecurityOrigin* origin){ ASSERT(currentThread() == m_thread); populateOrigins(); MutexLocker lockQuotaMap(m_quotaMapGuard); return m_quotaMap->contains(origin);}bool DatabaseTracker::hasEntryForDatabase(SecurityOrigin* origin, const String& databaseIdentifier){ ASSERT(currentThread() == m_thread); 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;}String DatabaseTracker::originPath(SecurityOrigin* origin) const{ ASSERT(currentThread() == m_thread); if (m_databaseDirectoryPath.isEmpty()) return String(); return pathByAppendingComponent(m_databaseDirectoryPath, origin->databaseIdentifier());}String DatabaseTracker::fullPathForDatabase(SecurityOrigin* origin, const String& name, bool createIfNotExists){ ASSERT(currentThread() == m_thread); if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name) return String(); String originIdentifier = origin->databaseIdentifier(); String originPath = this->originPath(origin); // Make sure the path for this SecurityOrigin exists if (createIfNotExists && !makeAllDirectories(originPath)) return String(); // See if we have a path for this database yet openTrackerDatabase(false); 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 pathByAppendingComponent(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", origin->databaseIdentifier().ascii().data(), name.ascii().data()); return String(); } statement.finalize(); SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';"); // FIXME: More informative error handling here, even though these steps should never fail if (sequenceStatement.prepare() != SQLResultOk) return String(); result = sequenceStatement.step(); // This has a range of 2^63 and starts at 0 for every time a user resets Safari - // I can't imagine it'd over overflow int64_t seq = 0; if (result == SQLResultRow) { seq = sequenceStatement.getColumnInt64(0); } else if (result != SQLResultDone) return String(); sequenceStatement.finalize(); String filename; do { ++seq; filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq)); } while (fileExists(filename)); if (!addDatabase(origin, name, String::format("%016llx.db", seq))) return String(); // If this origin's quota is being tracked (open handle to a database in this origin), add this new database // to the quota manager now { Locker<OriginQuotaManager> locker(originQuotaManager()); if (originQuotaManager().tracksOrigin(origin)) originQuotaManager().addDatabase(origin, name, filename); } return filename;}void DatabaseTracker::populateOrigins(){ if (m_quotaMap) return; ASSERT(currentThread() == m_thread); m_quotaMap.set(new QuotaMap); m_quotaManager.set(new OriginQuotaManager); openTrackerDatabase(false); if (!m_database.isOpen()) return; 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)); m_quotaMap->set(origin.get(), statement.getColumnInt64(1)); } if (result != SQLResultDone) LOG_ERROR("Failed to read in all origins from the database");}void DatabaseTracker::origins(Vector<RefPtr<SecurityOrigin> >& result){ ASSERT(currentThread() == m_thread); populateOrigins(); MutexLocker lockQuotaMap(m_quotaMapGuard); copyKeysToVector(*m_quotaMap, result);}bool DatabaseTracker::databaseNamesForOrigin(SecurityOrigin* origin, Vector<String>& resultVector){ ASSERT(currentThread() == m_thread); 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;}DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, SecurityOrigin* origin){ ASSERT(currentThread() == m_thread); if (m_proposedDatabase && m_proposedDatabase->first == origin && m_proposedDatabase->second.name() == name) return m_proposedDatabase->second; String originIdentifier = origin->databaseIdentifier(); 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(); } return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));}void DatabaseTracker::setDatabaseDetails(SecurityOrigin* origin, const String& name, const String& displayName, unsigned long estimatedSize){ ASSERT(currentThread() == m_thread); String originIdentifier = origin->databaseIdentifier(); int64_t guid = 0; 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 { // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker // 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 // So we'll print an error instead 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);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -