Mozilla/mozilla/toolkit/components/url-classifier/src/nsUrlClassifierDBService.cpp
sayrer%gmail.com c79ea01225 bug 321024. Crash in nsCryptoHash during shutdown of nsUrlClassifierDBService. Patch by Tony Chang <tony@ponderer.org>. r=darin
git-svn-id: svn://10.0.0.236/trunk@224952 18797224-902f-48f8-a5cc-f745e15eee43
2007-04-24 17:41:44 +00:00

1029 lines
33 KiB
C++

//* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Url Classifier code
*
* The Initial Developer of the Original Code is
* Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Tony Chang <tony@ponderer.org> (original author)
* Brett Wilson <brettw@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsAutoLock.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsIDirectoryService.h"
#include "nsIObserverService.h"
#include "nsIProperties.h"
#include "nsIProxyObjectManager.h"
#include "nsToolkitCompsCID.h"
#include "nsUrlClassifierDBService.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
#include "nsXPCOMStrings.h"
#include "prlog.h"
#include "prprf.h"
// NSPR_LOG_MODULES=UrlClassifierDbService:5
#if defined(PR_LOGGING)
static const PRLogModuleInfo *gUrlClassifierDbServiceLog = nsnull;
#define LOG(args) PR_LOG(gUrlClassifierDbServiceLog, PR_LOG_DEBUG, args)
#else
#define LOG(args)
#endif
// Change filename each time we change the db schema.
#define DATABASE_FILENAME "urlclassifier2.sqlite"
// Singleton instance.
static nsUrlClassifierDBService* sUrlClassifierDBService;
// Thread that we do the updates on.
static nsIThread* gDbBackgroundThread = nsnull;
// Once we've committed to shutting down, don't do work in the background
// thread.
static PRBool gShuttingDownThread = PR_FALSE;
static const char* kNEW_TABLE_SUFFIX = "_new";
// This maps A-M to N-Z and N-Z to A-M. All other characters are left alone.
// Copied from mailnews/mime/src/mimetext.cpp
static const unsigned char kRot13Table[256] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 91, 92, 93, 94, 95, 96,
110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 97, 98,
99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 123, 124, 125, 126,
127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141,
142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156,
157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171,
172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186,
187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201,
202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216,
217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231,
232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246,
247, 248, 249, 250, 251, 252, 253, 254, 255 };
// Does an in place rotation of the line
static void
Rot13Line(nsCString &line)
{
nsCString::iterator start, end;
line.BeginWriting(start);
line.EndWriting(end);
while (start != end) {
*start = kRot13Table[NS_STATIC_CAST(PRInt32, *start)];
++start;
}
}
// -------------------------------------------------------------------------
// Actual worker implemenatation
class nsUrlClassifierDBServiceWorker : public nsIUrlClassifierDBServiceWorker
{
public:
nsUrlClassifierDBServiceWorker();
NS_DECL_ISUPPORTS
NS_DECL_NSIURLCLASSIFIERDBSERVICE
NS_DECL_NSIURLCLASSIFIERDBSERVICEWORKER
private:
// No subclassing
~nsUrlClassifierDBServiceWorker();
// Disallow copy constructor
nsUrlClassifierDBServiceWorker(nsUrlClassifierDBServiceWorker&);
// Table names have hyphens in them, which SQL doesn't allow,
// so we convert them to underscores.
void GetDbTableName(const nsACString& aTableName, nsCString* aDbTableName);
// Try to open the db, DATABASE_FILENAME.
nsresult OpenDb();
// Create a table in the db if it doesn't exist.
nsresult MaybeCreateTable(const nsCString& aTableName);
// Drop a table if it exists.
nsresult MaybeDropTable(const nsCString& aTableName);
// If this is not an update request, swap the new table
// in for the old table.
nsresult MaybeSwapTables(const nsCString& aVersionLine);
// Parse a version string of the form [table-name #.###] or
// [table-name #.### update] and return the table name and
// whether or not it's an update.
nsresult ParseVersionString(const nsCSubstring& aLine,
nsCString* aTableName,
PRBool* aIsUpdate);
// Handle a new table line of the form [table-name #.####]. We create the
// table if it doesn't exist and set the aTableName, aUpdateStatement,
// and aDeleteStatement.
nsresult ProcessNewTable(const nsCSubstring& aLine,
nsCString* aTableName,
mozIStorageStatement** aUpdateStatement,
mozIStorageStatement** aDeleteStatement);
// Handle an add or remove line. We execute additional update or delete
// statements.
nsresult ProcessUpdateTable(const nsCSubstring& aLine,
const nsCString& aTableName,
mozIStorageStatement* aUpdateStatement,
mozIStorageStatement* aDeleteStatement);
// Holds a connection to the Db. We lazily initialize this because it has
// to be created in the background thread (currently mozStorageConnection
// isn't thread safe).
mozIStorageConnection* mConnection;
// True if we're in the middle of a streaming update.
PRBool mHasPendingUpdate;
// For incremental updates, keep track of tables that have been updated.
// When finish() is called, we go ahead and pass these update lines to
// the callback.
nsTArray<nsCString> mTableUpdateLines;
// We receive data in small chunks that may be broken in the middle of
// a line. So we save the last partial line here.
nsCString mPendingStreamUpdate;
};
NS_IMPL_THREADSAFE_ISUPPORTS1(nsUrlClassifierDBServiceWorker,
nsIUrlClassifierDBServiceWorker)
nsUrlClassifierDBServiceWorker::nsUrlClassifierDBServiceWorker()
: mConnection(nsnull), mHasPendingUpdate(PR_FALSE), mTableUpdateLines()
{
}
nsUrlClassifierDBServiceWorker::~nsUrlClassifierDBServiceWorker()
{
NS_ASSERTION(mConnection == nsnull,
"Db connection not closed, leaking memory! Call CloseDb "
"to close the connection.");
}
// Lookup a key in the db.
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::Exists(const nsACString& tableName,
const nsACString& key,
nsIUrlClassifierCallback *c)
{
if (gShuttingDownThread)
return NS_ERROR_NOT_INITIALIZED;
nsresult rv = OpenDb();
if (NS_FAILED(rv)) {
NS_ERROR("Unable to open database");
return NS_ERROR_FAILURE;
}
nsCAutoString dbTableName;
GetDbTableName(tableName, &dbTableName);
nsCOMPtr<mozIStorageStatement> selectStatement;
nsCAutoString statement;
statement.AssignLiteral("SELECT value FROM ");
statement.Append(dbTableName);
statement.AppendLiteral(" WHERE key = ?1");
rv = mConnection->CreateStatement(statement,
getter_AddRefs(selectStatement));
nsAutoString value;
// If CreateStatment failed, this probably means the table doesn't exist.
// That's ok, we just return an emptry string.
if (NS_SUCCEEDED(rv)) {
nsCString keyROT13(key);
Rot13Line(keyROT13);
rv = selectStatement->BindUTF8StringParameter(0, keyROT13);
NS_ENSURE_SUCCESS(rv, rv);
PRBool hasMore = PR_FALSE;
rv = selectStatement->ExecuteStep(&hasMore);
// If the table has any columns, take the first value.
if (NS_SUCCEEDED(rv) && hasMore) {
selectStatement->GetString(0, value);
}
}
c->HandleEvent(NS_ConvertUTF16toUTF8(value));
return NS_OK;
}
// We get a comma separated list of table names. For each table that doesn't
// exist, we return it in a comma separated list via the callback.
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::CheckTables(const nsACString & tableNames,
nsIUrlClassifierCallback *c)
{
if (gShuttingDownThread)
return NS_ERROR_NOT_INITIALIZED;
nsresult rv = OpenDb();
if (NS_FAILED(rv)) {
NS_ERROR("Unable to open database");
return NS_ERROR_FAILURE;
}
nsCAutoString changedTables;
// tablesNames is a comma separated list, so get each table name out for
// checking.
PRUint32 cur = 0;
PRInt32 next;
while (cur < tableNames.Length()) {
next = tableNames.FindChar(',', cur);
if (kNotFound == next) {
next = tableNames.Length();
}
const nsCSubstring &tableName = Substring(tableNames, cur, next - cur);
cur = next + 1;
nsCString dbTableName;
GetDbTableName(tableName, &dbTableName);
PRBool exists;
nsresult rv = mConnection->TableExists(dbTableName, &exists);
NS_ENSURE_SUCCESS(rv, rv);
if (!exists) {
if (changedTables.Length() > 0)
changedTables.Append(",");
changedTables.Append(tableName);
}
}
c->HandleEvent(changedTables);
return NS_OK;
}
// Do a batch update of the database. After we complete processing a table,
// we call the callback with the table line.
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::UpdateTables(const nsACString& updateString,
nsIUrlClassifierCallback *c)
{
if (gShuttingDownThread)
return NS_ERROR_NOT_INITIALIZED;
LOG(("Updating tables\n"));
nsresult rv = OpenDb();
if (NS_FAILED(rv)) {
NS_ERROR("Unable to open database");
return NS_ERROR_FAILURE;
}
rv = mConnection->BeginTransaction();
NS_ASSERTION(NS_SUCCEEDED(rv), "Unable to begin transaction");
// Split the update string into lines
PRUint32 cur = 0;
PRInt32 next;
PRInt32 count = 0;
nsCAutoString dbTableName;
nsCAutoString lastTableLine;
nsCOMPtr<mozIStorageStatement> updateStatement;
nsCOMPtr<mozIStorageStatement> deleteStatement;
while(cur < updateString.Length() &&
(next = updateString.FindChar('\n', cur)) != kNotFound) {
const nsCSubstring &line = Substring(updateString, cur, next - cur);
cur = next + 1; // prepare for next run
// Skip blank lines
if (line.Length() == 0)
continue;
count++;
if ('[' == line[0]) {
rv = ProcessNewTable(line, &dbTableName,
getter_AddRefs(updateStatement),
getter_AddRefs(deleteStatement));
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed table line");
if (NS_SUCCEEDED(rv)) {
// If it's a new table, we may have completed a table.
// Go ahead and post the completion to the UI thread and db.
if (lastTableLine.Length() > 0) {
// If it was a new table, we need to swap in the new table.
rv = MaybeSwapTables(lastTableLine);
if (NS_SUCCEEDED(rv)) {
mConnection->CommitTransaction();
c->HandleEvent(lastTableLine);
} else {
// failed to swap, rollback
mConnection->RollbackTransaction();
}
mConnection->BeginTransaction();
}
lastTableLine.Assign(line);
}
} else {
rv = ProcessUpdateTable(line, dbTableName, updateStatement,
deleteStatement);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed update line");
}
}
LOG(("Num update lines: %d\n", count));
rv = MaybeSwapTables(lastTableLine);
if (NS_SUCCEEDED(rv)) {
mConnection->CommitTransaction();
c->HandleEvent(lastTableLine);
} else {
// failed to swap, rollback
mConnection->RollbackTransaction();
}
LOG(("Finishing table update\n"));
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::Update(const nsACString& chunk)
{
LOG(("Update from Stream."));
nsresult rv = OpenDb();
if (NS_FAILED(rv)) {
NS_ERROR("Unable to open database");
return NS_ERROR_FAILURE;
}
nsCAutoString updateString(mPendingStreamUpdate);
updateString.Append(chunk);
nsCOMPtr<mozIStorageStatement> updateStatement;
nsCOMPtr<mozIStorageStatement> deleteStatement;
nsCAutoString dbTableName;
// If we're not in the middle of an update, we start a new transaction.
// Otherwise, we need to pick up where we left off.
if (!mHasPendingUpdate) {
mConnection->BeginTransaction();
mHasPendingUpdate = PR_TRUE;
} else {
PRUint32 numTables = mTableUpdateLines.Length();
if (numTables > 0) {
const nsCSubstring &line = Substring(
mTableUpdateLines[numTables - 1], 0);
rv = ProcessNewTable(line, &dbTableName,
getter_AddRefs(updateStatement),
getter_AddRefs(deleteStatement));
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed table line");
}
}
PRUint32 cur = 0;
PRInt32 next;
while(cur < updateString.Length() &&
(next = updateString.FindChar('\n', cur)) != kNotFound) {
const nsCSubstring &line = Substring(updateString, cur, next - cur);
cur = next + 1; // prepare for next run
// Skip blank lines
if (line.Length() == 0)
continue;
if ('[' == line[0]) {
rv = ProcessNewTable(line, &dbTableName,
getter_AddRefs(updateStatement),
getter_AddRefs(deleteStatement));
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed table line");
if (NS_SUCCEEDED(rv)) {
// Add the line to our array of table lines.
mTableUpdateLines.AppendElement(line);
}
} else {
rv = ProcessUpdateTable(line, dbTableName, updateStatement,
deleteStatement);
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "malformed update line");
}
}
// Save the remaining string fragment.
mPendingStreamUpdate = Substring(updateString, cur);
LOG(("pending stream update: %s", mPendingStreamUpdate.get()));
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::Finish(nsIUrlClassifierCallback *c)
{
if (!mHasPendingUpdate)
return NS_OK;
if (gShuttingDownThread) {
mConnection->RollbackTransaction();
return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv = NS_OK;
for (PRUint32 i = 0; i < mTableUpdateLines.Length(); ++i) {
rv = MaybeSwapTables(mTableUpdateLines[i]);
if (NS_FAILED(rv)) {
break;
}
}
if (NS_SUCCEEDED(rv)) {
LOG(("Finish, committing transaction"));
mConnection->CommitTransaction();
// Send update information to main thread.
for (PRUint32 i = 0; i < mTableUpdateLines.Length(); ++i) {
c->HandleEvent(mTableUpdateLines[i]);
}
} else {
LOG(("Finish failed (swap table error?), rolling back transaction"));
mConnection->RollbackTransaction();
}
mTableUpdateLines.Clear();
mPendingStreamUpdate.Truncate();
mHasPendingUpdate = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::CancelStream()
{
if (!mHasPendingUpdate)
return NS_OK;
LOG(("CancelStream, rolling back transaction"));
mConnection->RollbackTransaction();
mTableUpdateLines.Clear();
mPendingStreamUpdate.Truncate();
mHasPendingUpdate = PR_FALSE;
return NS_OK;
}
// Allows the main thread to delete the connection which may be in
// a background thread.
// XXX This could be turned into a single shutdown event so the logic
// is simpler in nsUrlClassifierDBService::Shutdown.
NS_IMETHODIMP
nsUrlClassifierDBServiceWorker::CloseDb()
{
if (mConnection != nsnull) {
NS_RELEASE(mConnection);
LOG(("urlclassifier db closed\n"));
}
return NS_OK;
}
nsresult
nsUrlClassifierDBServiceWorker::ProcessNewTable(
const nsCSubstring& aLine,
nsCString* aDbTableName,
mozIStorageStatement** aUpdateStatement,
mozIStorageStatement** aDeleteStatement)
{
// The line format is "[table-name #.####]" or "[table-name #.#### update]"
// The additional "update" in the header means that this is a diff.
// Otherwise, we should blow away the old table and start afresh.
PRBool isUpdate = PR_FALSE;
// If the version string is bad, give up.
nsresult rv = ParseVersionString(aLine, aDbTableName, &isUpdate);
NS_ENSURE_SUCCESS(rv, rv);
// If it's not an update, we dump the values into a new table. Once we're
// done with the table, we drop the original table and copy over the values
// from the old table into the new table.
if (!isUpdate)
aDbTableName->Append(kNEW_TABLE_SUFFIX);
// Create the table
rv = MaybeCreateTable(*aDbTableName);
if (NS_FAILED(rv))
return rv;
// insert statement
nsCAutoString statement;
statement.AssignLiteral("INSERT OR REPLACE INTO ");
statement.Append(*aDbTableName);
statement.AppendLiteral(" VALUES (?1, ?2)");
rv = mConnection->CreateStatement(statement, aUpdateStatement);
NS_ENSURE_SUCCESS(rv, rv);
// delete statement
statement.AssignLiteral("DELETE FROM ");
statement.Append(*aDbTableName);
statement.AppendLiteral(" WHERE key = ?1");
rv = mConnection->CreateStatement(statement, aDeleteStatement);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
nsUrlClassifierDBServiceWorker::ProcessUpdateTable(
const nsCSubstring& aLine,
const nsCString& aTableName,
mozIStorageStatement* aUpdateStatement,
mozIStorageStatement* aDeleteStatement)
{
// We should have seen a table name line by now.
if (aTableName.Length() == 0)
return NS_ERROR_FAILURE;
if (!aUpdateStatement || !aDeleteStatement) {
NS_NOTREACHED("Statements NULL but table is not");
return NS_ERROR_FAILURE;
}
// There should at least be an op char and a key
if (aLine.Length() < 2)
return NS_ERROR_FAILURE;
char op = aLine[0];
PRInt32 spacePos = aLine.FindChar('\t');
nsresult rv = NS_ERROR_FAILURE;
if ('+' == op && spacePos != kNotFound) {
// Insert operation of the form "+KEY\tVALUE"
const nsCSubstring &key = Substring(aLine, 1, spacePos - 1);
const nsCSubstring &value = Substring(aLine, spacePos + 1);
// We use ROT13 versions of keys to avoid antivirus utilities from
// flagging us as a virus.
nsCString keyROT13(key);
Rot13Line(keyROT13);
aUpdateStatement->BindUTF8StringParameter(0, keyROT13);
aUpdateStatement->BindUTF8StringParameter(1, value);
rv = aUpdateStatement->Execute();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to update");
} else if ('-' == op) {
// Remove operation of the form "-KEY"
nsCString keyROT13;
if (spacePos == kNotFound) {
// No trailing tab
const nsCSubstring &key = Substring(aLine, 1);
keyROT13.Assign(key);
} else {
// With trailing tab
const nsCSubstring &key = Substring(aLine, 1, spacePos - 1);
keyROT13.Assign(key);
}
Rot13Line(keyROT13);
aDeleteStatement->BindUTF8StringParameter(0, keyROT13);
rv = aDeleteStatement->Execute();
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Failed to delete");
}
return rv;
}
nsresult
nsUrlClassifierDBServiceWorker::OpenDb()
{
// Connection already open, don't do anything.
if (mConnection != nsnull)
return NS_OK;
LOG(("Opening db\n"));
// Compute database filename
nsCOMPtr<nsIFile> dbFile;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(dbFile));
NS_ENSURE_SUCCESS(rv, rv);
rv = dbFile->Append(NS_LITERAL_STRING(DATABASE_FILENAME));
NS_ENSURE_SUCCESS(rv, rv);
// open the connection
nsCOMPtr<mozIStorageService> storageService =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = storageService->OpenDatabase(dbFile, &mConnection);
if (rv == NS_ERROR_FILE_CORRUPTED) {
// delete the db and try opening again
rv = dbFile->Remove(PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
rv = storageService->OpenDatabase(dbFile, &mConnection);
}
return rv;
}
nsresult
nsUrlClassifierDBServiceWorker::MaybeCreateTable(const nsCString& aTableName)
{
LOG(("MaybeCreateTable %s\n", aTableName.get()));
nsCOMPtr<mozIStorageStatement> createStatement;
nsCString statement;
statement.Assign("CREATE TABLE IF NOT EXISTS ");
statement.Append(aTableName);
statement.Append(" (key TEXT PRIMARY KEY, value TEXT)");
nsresult rv = mConnection->CreateStatement(statement,
getter_AddRefs(createStatement));
NS_ENSURE_SUCCESS(rv, rv);
return createStatement->Execute();
}
nsresult
nsUrlClassifierDBServiceWorker::MaybeDropTable(const nsCString& aTableName)
{
LOG(("MaybeDropTable %s\n", aTableName.get()));
nsCAutoString statement("DROP TABLE IF EXISTS ");
statement.Append(aTableName);
return mConnection->ExecuteSimpleSQL(statement);
}
nsresult
nsUrlClassifierDBServiceWorker::MaybeSwapTables(const nsCString& aVersionLine)
{
if (aVersionLine.Length() == 0)
return NS_ERROR_FAILURE;
// Check to see if this was a full table update or not.
nsCAutoString tableName;
PRBool isUpdate;
nsresult rv = ParseVersionString(aVersionLine, &tableName, &isUpdate);
NS_ENSURE_SUCCESS(rv, rv);
// Updates don't require any fancy logic.
if (isUpdate)
return NS_OK;
// Not an update, so we need to swap tables by dropping the original table
// and copying in the values from the new table.
rv = MaybeDropTable(tableName);
NS_ENSURE_SUCCESS(rv, rv);
nsCAutoString newTableName(tableName);
newTableName.Append(kNEW_TABLE_SUFFIX);
// Bring over new table
nsCAutoString sql("ALTER TABLE ");
sql.Append(newTableName);
sql.Append(" RENAME TO ");
sql.Append(tableName);
rv = mConnection->ExecuteSimpleSQL(sql);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("tables swapped (%s)\n", tableName.get()));
return NS_OK;
}
// The line format is "[table-name #.####]" or "[table-name #.#### update]".
nsresult
nsUrlClassifierDBServiceWorker::ParseVersionString(const nsCSubstring& aLine,
nsCString* aTableName,
PRBool* aIsUpdate)
{
// Blank lines are not valid
if (aLine.Length() == 0)
return NS_ERROR_FAILURE;
// Max size for an update line (so we don't buffer overflow when sscanf'ing).
const PRUint32 MAX_LENGTH = 2048;
if (aLine.Length() > MAX_LENGTH)
return NS_ERROR_FAILURE;
nsCAutoString lineData(aLine);
char tableNameBuf[MAX_LENGTH], endChar = ' ';
PRInt32 majorVersion, minorVersion, numConverted;
// Use trailing endChar to make sure the update token gets parsed.
numConverted = PR_sscanf(lineData.get(), "[%s %d.%d update%c", tableNameBuf,
&majorVersion, &minorVersion, &endChar);
if (numConverted != 4 || endChar != ']') {
// Check to see if it's not an update request
numConverted = PR_sscanf(lineData.get(), "[%s %d.%d%c", tableNameBuf,
&majorVersion, &minorVersion, &endChar);
if (numConverted != 4 || endChar != ']')
return NS_ERROR_FAILURE;
*aIsUpdate = PR_FALSE;
} else {
// First sscanf worked, so it's an update string.
*aIsUpdate = PR_TRUE;
}
LOG(("Is update? %d\n", *aIsUpdate));
// Table header looks valid, go ahead and copy over the table name into the
// return variable.
GetDbTableName(nsCAutoString(tableNameBuf), aTableName);
return NS_OK;
}
void
nsUrlClassifierDBServiceWorker::GetDbTableName(const nsACString& aTableName,
nsCString* aDbTableName)
{
aDbTableName->Assign(aTableName);
aDbTableName->ReplaceChar('-', '_');
}
// -------------------------------------------------------------------------
// Proxy class implementation
NS_IMPL_THREADSAFE_ISUPPORTS2(nsUrlClassifierDBService,
nsIUrlClassifierDBService,
nsIObserver)
/* static */ nsUrlClassifierDBService*
nsUrlClassifierDBService::GetInstance()
{
if (!sUrlClassifierDBService) {
sUrlClassifierDBService = new nsUrlClassifierDBService();
if (!sUrlClassifierDBService)
return nsnull;
NS_ADDREF(sUrlClassifierDBService); // addref the global
if (NS_FAILED(sUrlClassifierDBService->Init())) {
NS_RELEASE(sUrlClassifierDBService);
return nsnull;
}
} else {
// Already exists, just add a ref
NS_ADDREF(sUrlClassifierDBService); // addref the return result
}
return sUrlClassifierDBService;
}
nsUrlClassifierDBService::nsUrlClassifierDBService()
{
}
nsUrlClassifierDBService::~nsUrlClassifierDBService()
{
sUrlClassifierDBService = nsnull;
}
nsresult
nsUrlClassifierDBService::Init()
{
#if defined(PR_LOGGING)
if (!gUrlClassifierDbServiceLog)
gUrlClassifierDbServiceLog = PR_NewLogModule("UrlClassifierDbService");
#endif
// Force the storage service to be created on the main thread.
nsresult rv;
nsCOMPtr<mozIStorageService> storageService =
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
// Start the background thread.
rv = NS_NewThread(&gDbBackgroundThread);
if (NS_FAILED(rv))
return rv;
mWorker = new nsUrlClassifierDBServiceWorker();
if (!mWorker)
return NS_ERROR_OUT_OF_MEMORY;
// Add an observer for shutdown
nsCOMPtr<nsIObserverService> observerService =
do_GetService("@mozilla.org/observer-service;1");
if (!observerService)
return NS_ERROR_FAILURE;
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
return NS_OK;
}
NS_IMETHODIMP
nsUrlClassifierDBService::Exists(const nsACString& tableName,
const nsACString& key,
nsIUrlClassifierCallback *c)
{
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// The proxy callback uses the current thread.
nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
rv = NS_GetProxyForObject(NS_PROXY_TO_CURRENT_THREAD,
NS_GET_IID(nsIUrlClassifierCallback),
c,
NS_PROXY_ASYNC,
getter_AddRefs(proxyCallback));
NS_ENSURE_SUCCESS(rv, rv);
// The actual worker uses the background thread.
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
NS_ENSURE_SUCCESS(rv, rv);
return proxy->Exists(tableName, key, proxyCallback);
}
NS_IMETHODIMP
nsUrlClassifierDBService::CheckTables(const nsACString & tableNames,
nsIUrlClassifierCallback *c)
{
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// The proxy callback uses the current thread.
nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
rv = NS_GetProxyForObject(NS_PROXY_TO_CURRENT_THREAD,
NS_GET_IID(nsIUrlClassifierCallback),
c,
NS_PROXY_ASYNC,
getter_AddRefs(proxyCallback));
NS_ENSURE_SUCCESS(rv, rv);
// The actual worker uses the background thread.
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
NS_ENSURE_SUCCESS(rv, rv);
return proxy->CheckTables(tableNames, proxyCallback);
}
NS_IMETHODIMP
nsUrlClassifierDBService::UpdateTables(const nsACString& updateString,
nsIUrlClassifierCallback *c)
{
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// The proxy callback uses the current thread.
nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
rv = NS_GetProxyForObject(NS_PROXY_TO_CURRENT_THREAD,
NS_GET_IID(nsIUrlClassifierCallback),
c,
NS_PROXY_ASYNC,
getter_AddRefs(proxyCallback));
NS_ENSURE_SUCCESS(rv, rv);
// The actual worker uses the background thread.
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
NS_ENSURE_SUCCESS(rv, rv);
return proxy->UpdateTables(updateString, proxyCallback);
}
NS_IMETHODIMP
nsUrlClassifierDBService::Update(const nsACString& aUpdateChunk)
{
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// The actual worker uses the background thread.
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
NS_ENSURE_SUCCESS(rv, rv);
return proxy->Update(aUpdateChunk);
}
NS_IMETHODIMP
nsUrlClassifierDBService::Finish(nsIUrlClassifierCallback *c)
{
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// The proxy callback uses the current thread.
nsCOMPtr<nsIUrlClassifierCallback> proxyCallback;
rv = NS_GetProxyForObject(NS_PROXY_TO_CURRENT_THREAD,
NS_GET_IID(nsIUrlClassifierCallback),
c,
NS_PROXY_ASYNC,
getter_AddRefs(proxyCallback));
NS_ENSURE_SUCCESS(rv, rv);
// The actual worker uses the background thread.
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
NS_ENSURE_SUCCESS(rv, rv);
return proxy->Finish(proxyCallback);
}
NS_IMETHODIMP
nsUrlClassifierDBService::CancelStream()
{
NS_ENSURE_TRUE(gDbBackgroundThread, NS_ERROR_NOT_INITIALIZED);
nsresult rv;
// The actual worker uses the background thread.
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
NS_ENSURE_SUCCESS(rv, rv);
return proxy->CancelStream();
}
NS_IMETHODIMP
nsUrlClassifierDBService::Observe(nsISupports *aSubject, const char *aTopic,
const PRUnichar *aData)
{
if (nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
LOG(("shutting down db service\n"));
Shutdown();
}
return NS_OK;
}
// Join the background thread if it exists.
nsresult
nsUrlClassifierDBService::Shutdown()
{
if (!gDbBackgroundThread)
return NS_OK;
nsresult rv;
// First close the db connection.
if (mWorker) {
nsCOMPtr<nsIUrlClassifierDBServiceWorker> proxy;
rv = NS_GetProxyForObject(gDbBackgroundThread,
NS_GET_IID(nsIUrlClassifierDBServiceWorker),
mWorker,
NS_PROXY_ASYNC,
getter_AddRefs(proxy));
if (NS_SUCCEEDED(rv)) {
rv = proxy->CloseDb();
NS_ASSERTION(NS_SUCCEEDED(rv), "failed to post close db event");
}
}
LOG(("joining background thread"));
gShuttingDownThread = PR_TRUE;
gDbBackgroundThread->Shutdown();
NS_RELEASE(gDbBackgroundThread);
return NS_OK;
}