| Index: chrome/browser/sync/syncable/directory_backing_store.cc
|
| ===================================================================
|
| --- chrome/browser/sync/syncable/directory_backing_store.cc (revision 0)
|
| +++ chrome/browser/sync/syncable/directory_backing_store.cc (revision 0)
|
| @@ -0,0 +1,657 @@
|
| +// Copyright (c) 2009 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "chrome/browser/sync/syncable/directory_backing_store.h"
|
| +
|
| +#ifdef OS_MACOSX
|
| +#include <CoreFoundation/CoreFoundation.h>
|
| +#elif defined(OS_LINUX)
|
| +#include <glib.h>
|
| +#endif
|
| +
|
| +#include <string>
|
| +
|
| +#include "base/hash_tables.h"
|
| +#include "base/logging.h"
|
| +#include "chrome/browser/sync/protocol/service_constants.h"
|
| +#include "chrome/browser/sync/syncable/syncable-inl.h"
|
| +#include "chrome/browser/sync/syncable/syncable_columns.h"
|
| +#include "chrome/browser/sync/util/crypto_helpers.h"
|
| +#include "chrome/browser/sync/util/path_helpers.h"
|
| +#include "chrome/browser/sync/util/query_helpers.h"
|
| +#include "third_party/sqlite/preprocessed/sqlite3.h"
|
| +
|
| +// If sizeof(time_t) != sizeof(int32) we need to alter or expand the sqlite
|
| +// datatype.
|
| +COMPILE_ASSERT(sizeof(time_t) == sizeof(int32), time_t_is_not_int32);
|
| +
|
| +using std::string;
|
| +
|
| +namespace syncable {
|
| +
|
| +// This just has to be big enough to hold an UPDATE or
|
| +// INSERT statement that modifies all the columns in the entry table.
|
| +static const string::size_type kUpdateStatementBufferSize = 2048;
|
| +
|
| +// Increment this version whenever updating DB tables.
|
| +static const int32 kCurrentDBVersion = 67;
|
| +
|
| +// TODO(sync): remove
|
| +static void PathNameMatch16(sqlite3_context *context, int argc,
|
| + sqlite3_value **argv) {
|
| + const PathString pathspec(reinterpret_cast<const PathChar*>
|
| + (sqlite3_value_text16(argv[0])), sqlite3_value_bytes16(argv[0]) / 2);
|
| +
|
| + const void* name_text = sqlite3_value_text16(argv[1]);
|
| + int name_bytes = sqlite3_value_bytes16(argv[1]);
|
| + // If the text is null, we need to avoid the PathString constructor.
|
| + if (name_text != NULL) {
|
| + // Have to copy to append a terminating 0 anyway.
|
| + const PathString name(reinterpret_cast<const PathChar*>
|
| + (sqlite3_value_text16(argv[1])),
|
| + sqlite3_value_bytes16(argv[1]) / 2);
|
| + sqlite3_result_int(context, PathNameMatch(name, pathspec));
|
| + } else {
|
| + sqlite3_result_int(context, PathNameMatch(PathString(), pathspec));
|
| + }
|
| +}
|
| +
|
| +// Sqlite allows setting of the escape character in an ESCAPE clause and
|
| +// this character is passed in as a third character to the like function.
|
| +// See: http://www.sqlite.org/lang_expr.html
|
| +static void PathNameMatch16WithEscape(sqlite3_context *context,
|
| + int argc, sqlite3_value **argv) {
|
| + // Never seen this called, but just in case.
|
| + LOG(FATAL) << "PathNameMatch16WithEscape() not implemented";
|
| +}
|
| +
|
| +static void RegisterPathNameCollate(sqlite3* dbhandle) {
|
| +#ifdef OS_WINDOWS
|
| + const int collate = SQLITE_UTF16;
|
| +#else
|
| + const int collate = SQLITE_UTF8;
|
| +#endif
|
| + CHECK(SQLITE_OK == sqlite3_create_collation(dbhandle, "PATHNAME", collate,
|
| + NULL, &ComparePathNames16));
|
| +}
|
| +
|
| +// Replace the LIKE operator with our own implementation that
|
| +// does file spec matching like "*.txt".
|
| +static void RegisterPathNameMatch(sqlite3* dbhandle) {
|
| + // We only register this on Windows. We use the normal sqlite
|
| + // matching function on mac/linux.
|
| + // note that the function PathNameMatch() does a simple ==
|
| + // comparison on mac, so that would have to be fixed if
|
| + // we really wanted to use PathNameMatch on mac/linux w/ the
|
| + // same pattern strings as we do on windows.
|
| +#ifdef OS_WINDOWS
|
| + CHECK(SQLITE_OK == sqlite3_create_function(dbhandle, "like",
|
| + 2, SQLITE_ANY, NULL, &PathNameMatch16, NULL, NULL));
|
| + CHECK(SQLITE_OK == sqlite3_create_function(dbhandle, "like",
|
| + 3, SQLITE_ANY, NULL, &PathNameMatch16WithEscape, NULL, NULL));
|
| +#endif // OS_WINDOWS
|
| +}
|
| +
|
| +static inline bool IsSqliteErrorOurFault(int result) {
|
| + switch (result) {
|
| + case SQLITE_MISMATCH:
|
| + case SQLITE_CONSTRAINT:
|
| + case SQLITE_MISUSE:
|
| + case SQLITE_RANGE:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +namespace {
|
| +// This small helper class reduces the amount of code in the table upgrade code
|
| +// below and also CHECKs as soon as there's an issue.
|
| +class StatementExecutor {
|
| + public:
|
| + explicit StatementExecutor(sqlite3* dbhandle) : dbhandle_(dbhandle) {
|
| + result_ = SQLITE_DONE;
|
| + }
|
| + int Exec(const char* query) {
|
| + if (SQLITE_DONE != result_)
|
| + return result_;
|
| + result_ = ::Exec(dbhandle_, query);
|
| + CHECK(!IsSqliteErrorOurFault(result_)) << query;
|
| + return result_;
|
| + }
|
| + template <typename T1>
|
| + int Exec(const char* query, T1 arg1) {
|
| + if (SQLITE_DONE != result_)
|
| + return result_;
|
| + result_ = ::Exec(dbhandle_, query, arg1);
|
| + CHECK(!IsSqliteErrorOurFault(result_)) << query;
|
| + return result_;
|
| + }
|
| + int result() {
|
| + return result_;
|
| + }
|
| + void set_result(int result) {
|
| + result_ = result;
|
| + CHECK(!IsSqliteErrorOurFault(result_)) << result_;
|
| + }
|
| + bool healthy() const {
|
| + return SQLITE_DONE == result_;
|
| + }
|
| + private:
|
| + sqlite3* dbhandle_;
|
| + int result_;
|
| + DISALLOW_COPY_AND_ASSIGN(StatementExecutor);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +static string GenerateCacheGUID() {
|
| + return Generate128BitRandomHexString();
|
| +}
|
| +
|
| +// Iterate over the fields of |entry| and bind dirty ones to |statement| for
|
| +// updating. Returns the number of args bound.
|
| +static int BindDirtyFields(const EntryKernel& entry, sqlite3_stmt* statement) {
|
| + int index = 1;
|
| + int i = 0;
|
| + for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
|
| + if (entry.dirty[i])
|
| + BindArg(statement, entry.ref(static_cast<Int64Field>(i)), index++);
|
| + }
|
| + for ( ; i < ID_FIELDS_END; ++i) {
|
| + if (entry.dirty[i])
|
| + BindArg(statement, entry.ref(static_cast<IdField>(i)), index++);
|
| + }
|
| + for ( ; i < BIT_FIELDS_END; ++i) {
|
| + if (entry.dirty[i])
|
| + BindArg(statement, entry.ref(static_cast<BitField>(i)), index++);
|
| + }
|
| + for ( ; i < STRING_FIELDS_END; ++i) {
|
| + if (entry.dirty[i])
|
| + BindArg(statement, entry.ref(static_cast<StringField>(i)), index++);
|
| + }
|
| + for ( ; i < BLOB_FIELDS_END; ++i) {
|
| + if (entry.dirty[i])
|
| + BindArg(statement, entry.ref(static_cast<BlobField>(i)), index++);
|
| + }
|
| + return index - 1;
|
| +}
|
| +
|
| +// The caller owns the returned EntryKernel*.
|
| +static EntryKernel* UnpackEntry(sqlite3_stmt* statement) {
|
| + EntryKernel* result = NULL;
|
| + int query_result = sqlite3_step(statement);
|
| + if (SQLITE_ROW == query_result) {
|
| + result = new EntryKernel;
|
| + CHECK(sqlite3_column_count(statement) == static_cast<int>(FIELD_COUNT));
|
| + int i = 0;
|
| + for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
|
| + result->ref(static_cast<Int64Field>(i)) =
|
| + sqlite3_column_int64(statement, i);
|
| + }
|
| + for ( ; i < ID_FIELDS_END; ++i) {
|
| + GetColumn(statement, i, &result->ref(static_cast<IdField>(i)));
|
| + }
|
| + for ( ; i < BIT_FIELDS_END; ++i) {
|
| + result->ref(static_cast<BitField>(i)) =
|
| + (0 != sqlite3_column_int(statement, i));
|
| + }
|
| + for ( ; i < STRING_FIELDS_END; ++i) {
|
| + GetColumn(statement, i, &result->ref(static_cast<StringField>(i)));
|
| + }
|
| + for ( ; i < BLOB_FIELDS_END; ++i) {
|
| + GetColumn(statement, i, &result->ref(static_cast<BlobField>(i)));
|
| + }
|
| + ZeroFields(result, i);
|
| + } else {
|
| + CHECK(SQLITE_DONE == query_result);
|
| + result = NULL;
|
| + }
|
| + return result;
|
| +}
|
| +
|
| +static bool StepDone(sqlite3_stmt* statement, const char* failed_call) {
|
| + int result = sqlite3_step(statement);
|
| + if (SQLITE_DONE == result && SQLITE_OK == (result = sqlite3_reset(statement)))
|
| + return true;
|
| + // Some error code.
|
| + LOG(WARNING) << failed_call << " failed with result " << result;
|
| + CHECK(!IsSqliteErrorOurFault(result));
|
| + return false;
|
| +}
|
| +
|
| +static string ComposeCreateTableColumnSpecs(const ColumnSpec* begin,
|
| + const ColumnSpec* end) {
|
| + string query;
|
| + query.reserve(kUpdateStatementBufferSize);
|
| + char separator = '(';
|
| + for (const ColumnSpec* column = begin; column != end; ++column) {
|
| + query.push_back(separator);
|
| + separator = ',';
|
| + query.append(column->name);
|
| + query.push_back(' ');
|
| + query.append(column->spec);
|
| + }
|
| + query.push_back(')');
|
| + return query;
|
| +}
|
| +
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +// DirectoryBackingStore implementation.
|
| +
|
| +DirectoryBackingStore::DirectoryBackingStore(const PathString& dir_name,
|
| + const PathString& backing_filepath)
|
| + : dir_name_(dir_name), backing_filepath_(backing_filepath),
|
| + load_dbhandle_(NULL), save_dbhandle_(NULL) {
|
| +}
|
| +
|
| +DirectoryBackingStore::~DirectoryBackingStore() {
|
| + if (NULL != load_dbhandle_) {
|
| + sqlite3_close(load_dbhandle_);
|
| + load_dbhandle_ = NULL;
|
| + }
|
| + if (NULL != save_dbhandle_) {
|
| + sqlite3_close(save_dbhandle_);
|
| + save_dbhandle_ = NULL;
|
| + }
|
| +}
|
| +
|
| +bool DirectoryBackingStore::OpenAndConfigureHandleHelper(
|
| + sqlite3** handle) const {
|
| + if (SQLITE_OK == SqliteOpen(backing_filepath_.c_str(), handle)) {
|
| + sqlite3_busy_timeout(*handle, kDirectoryBackingStoreBusyTimeoutMs);
|
| + RegisterPathNameCollate(*handle);
|
| + RegisterPathNameMatch(*handle);
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +DirOpenResult DirectoryBackingStore::Load(MetahandlesIndex* entry_bucket,
|
| + ExtendedAttributes* xattrs_bucket,
|
| + Directory::KernelLoadInfo* kernel_load_info) {
|
| + DCHECK(load_dbhandle_ == NULL);
|
| + if (!OpenAndConfigureHandleHelper(&load_dbhandle_))
|
| + return FAILED_OPEN_DATABASE;
|
| +
|
| + DirOpenResult result = InitializeTables();
|
| + if (OPENED != result)
|
| + return result;
|
| +
|
| + DropDeletedEntries();
|
| + LoadEntries(entry_bucket);
|
| + LoadExtendedAttributes(xattrs_bucket);
|
| + LoadInfo(kernel_load_info);
|
| +
|
| + sqlite3_close(load_dbhandle_);
|
| + load_dbhandle_ = NULL; // No longer used.
|
| +
|
| + return OPENED;
|
| +}
|
| +
|
| +bool DirectoryBackingStore::SaveChanges(
|
| + const Directory::SaveChangesSnapshot& snapshot) {
|
| + bool disk_full = false;
|
| + sqlite3* dbhandle = LazyGetSaveHandle();
|
| + {
|
| + {
|
| + ScopedStatement begin(PrepareQuery(dbhandle,
|
| + "BEGIN EXCLUSIVE TRANSACTION"));
|
| + if (!StepDone(begin.get(), "BEGIN")) {
|
| + disk_full = true;
|
| + goto DoneDBTransaction;
|
| + }
|
| + }
|
| +
|
| + for (OriginalEntries::const_iterator i = snapshot.dirty_metas.begin();
|
| + !disk_full && i != snapshot.dirty_metas.end(); ++i) {
|
| + DCHECK(i->dirty.any());
|
| + disk_full = !SaveEntryToDB(*i);
|
| + }
|
| +
|
| + for (ExtendedAttributes::const_iterator i = snapshot.dirty_xattrs.begin();
|
| + !disk_full && i != snapshot.dirty_xattrs.end(); ++i) {
|
| + DCHECK(i->second.dirty);
|
| + if (i->second.is_deleted) {
|
| + disk_full = !DeleteExtendedAttributeFromDB(i);
|
| + } else {
|
| + disk_full = !SaveExtendedAttributeToDB(i);
|
| + }
|
| + }
|
| +
|
| + if (!disk_full && (Directory::KERNEL_SHARE_INFO_DIRTY ==
|
| + snapshot.kernel_info_status)) {
|
| + const Directory::PersistedKernelInfo& info = snapshot.kernel_info;
|
| + ScopedStatement update(PrepareQuery(dbhandle, "UPDATE share_info "
|
| + "SET last_sync_timestamp = ?, initial_sync_ended = ?, "
|
| + "store_birthday = ?, "
|
| + "next_id = ?",
|
| + info.last_sync_timestamp,
|
| + info.initial_sync_ended,
|
| + info.store_birthday,
|
| + info.next_id));
|
| + disk_full = !(StepDone(update.get(), "UPDATE share_info")
|
| + && 1 == sqlite3_changes(dbhandle));
|
| + }
|
| + if (disk_full) {
|
| + ExecOrDie(dbhandle, "ROLLBACK TRANSACTION");
|
| + } else {
|
| + ScopedStatement end_transaction(PrepareQuery(dbhandle,
|
| + "COMMIT TRANSACTION"));
|
| + disk_full = !StepDone(end_transaction.get(), "COMMIT TRANSACTION");
|
| + }
|
| + }
|
| +
|
| + DoneDBTransaction:
|
| + return !disk_full;
|
| +}
|
| +
|
| +DirOpenResult DirectoryBackingStore::InitializeTables() {
|
| + StatementExecutor se(load_dbhandle_);
|
| + if (SQLITE_DONE != se.Exec("BEGIN EXCLUSIVE TRANSACTION")) {
|
| + return FAILED_DISK_FULL;
|
| + }
|
| + int version_on_disk = 0;
|
| +
|
| + if (DoesTableExist(load_dbhandle_, "share_version")) {
|
| + ScopedStatement version_query(
|
| + PrepareQuery(load_dbhandle_, "SELECT data from share_version"));
|
| + int query_result = sqlite3_step(version_query.get());
|
| + if (SQLITE_ROW == query_result) {
|
| + version_on_disk = sqlite3_column_int(version_query.get(), 0);
|
| + }
|
| + version_query.reset(NULL);
|
| + }
|
| + if (version_on_disk != kCurrentDBVersion) {
|
| + if (version_on_disk > kCurrentDBVersion) {
|
| + ExecOrDie(load_dbhandle_, "END TRANSACTION");
|
| + return FAILED_NEWER_VERSION;
|
| + }
|
| + LOG(INFO) << "Old/null sync database, version " << version_on_disk;
|
| + // Delete the existing database (if any), and create a freshone.
|
| + if (se.healthy()) {
|
| + DropAllTables();
|
| + se.set_result(CreateTables());
|
| + }
|
| + }
|
| + if (SQLITE_DONE == se.result()) {
|
| + {
|
| + ScopedStatement statement(PrepareQuery(load_dbhandle_,
|
| + "SELECT db_create_version, db_create_time FROM share_info"));
|
| + CHECK(SQLITE_ROW == sqlite3_step(statement.get()));
|
| + PathString db_create_version;
|
| + int db_create_time;
|
| + GetColumn(statement.get(), 0, &db_create_version);
|
| + GetColumn(statement.get(), 1, &db_create_time);
|
| + statement.reset(0);
|
| + LOG(INFO) << "DB created at " << db_create_time << " by version " <<
|
| + db_create_version;
|
| + }
|
| + // COMMIT TRANSACTION rolls back on failure.
|
| + if (SQLITE_DONE == Exec(load_dbhandle_, "COMMIT TRANSACTION"))
|
| + return OPENED;
|
| + } else {
|
| + ExecOrDie(load_dbhandle_, "ROLLBACK TRANSACTION");
|
| + }
|
| + return FAILED_DISK_FULL;
|
| +}
|
| +
|
| +void DirectoryBackingStore::LoadEntries(MetahandlesIndex* entry_bucket) {
|
| + string select;
|
| + select.reserve(kUpdateStatementBufferSize);
|
| + select.append("SELECT");
|
| + const char* joiner = " ";
|
| + // Be explicit in SELECT order to match up with UnpackEntry.
|
| + for (int i = BEGIN_FIELDS; i < BEGIN_FIELDS + FIELD_COUNT; ++i) {
|
| + select.append(joiner);
|
| + select.append(ColumnName(i));
|
| + joiner = ", ";
|
| + }
|
| + select.append(" FROM metas ");
|
| + ScopedStatement statement(PrepareQuery(load_dbhandle_, select.c_str()));
|
| + base::hash_set<int> handles;
|
| + while (EntryKernel* kernel = UnpackEntry(statement.get())) {
|
| + DCHECK(handles.insert(kernel->ref(META_HANDLE)).second); // Only in debug.
|
| + entry_bucket->insert(kernel);
|
| + }
|
| +}
|
| +
|
| +void DirectoryBackingStore::LoadExtendedAttributes(
|
| + ExtendedAttributes* xattrs_bucket) {
|
| + ScopedStatement statement(PrepareQuery(load_dbhandle_,
|
| + "SELECT metahandle, key, value FROM extended_attributes"));
|
| + int step_result = sqlite3_step(statement.get());
|
| + while (SQLITE_ROW == step_result) {
|
| + int64 metahandle;
|
| + PathString path_string_key;
|
| + ExtendedAttributeValue val;
|
| + val.is_deleted = false;
|
| + GetColumn(statement.get(), 0, &metahandle);
|
| + GetColumn(statement.get(), 1, &path_string_key);
|
| + GetColumn(statement.get(), 2, &(val.value));
|
| + ExtendedAttributeKey key(metahandle, path_string_key);
|
| + xattrs_bucket->insert(std::make_pair(key, val));
|
| + step_result = sqlite3_step(statement.get());
|
| + }
|
| + CHECK(SQLITE_DONE == step_result);
|
| +}
|
| +
|
| +void DirectoryBackingStore::LoadInfo(Directory::KernelLoadInfo* info) {
|
| + ScopedStatement query(PrepareQuery(load_dbhandle_,
|
| + "SELECT last_sync_timestamp, initial_sync_ended, "
|
| + "store_birthday, next_id, cache_guid "
|
| + "FROM share_info"));
|
| + CHECK(SQLITE_ROW == sqlite3_step(query.get()));
|
| + GetColumn(query.get(), 0, &info->kernel_info.last_sync_timestamp);
|
| + GetColumn(query.get(), 1, &info->kernel_info.initial_sync_ended);
|
| + GetColumn(query.get(), 2, &info->kernel_info.store_birthday);
|
| + GetColumn(query.get(), 3, &info->kernel_info.next_id);
|
| + GetColumn(query.get(), 4, &info->cache_guid);
|
| + query.reset(PrepareQuery(load_dbhandle_,
|
| + "SELECT MAX(metahandle) FROM metas"));
|
| + CHECK(SQLITE_ROW == sqlite3_step(query.get()));
|
| + GetColumn(query.get(), 0, &info->max_metahandle);
|
| +}
|
| +
|
| +bool DirectoryBackingStore::SaveEntryToDB(const EntryKernel& entry) {
|
| + return entry.ref(IS_NEW) ? SaveNewEntryToDB(entry) : UpdateEntryToDB(entry);
|
| +}
|
| +
|
| +bool DirectoryBackingStore::SaveNewEntryToDB(const EntryKernel& entry) {
|
| + DCHECK(save_dbhandle_);
|
| + // TODO(timsteele): Should use INSERT OR REPLACE and eliminate one of
|
| + // the SaveNew / UpdateEntry code paths.
|
| + string query;
|
| + query.reserve(kUpdateStatementBufferSize);
|
| + query.append("INSERT INTO metas ");
|
| + string values;
|
| + values.reserve(kUpdateStatementBufferSize);
|
| + values.append("VALUES ");
|
| + const char* separator = "( ";
|
| + int i = 0;
|
| + for (i = BEGIN_FIELDS; i < BLOB_FIELDS_END; ++i) {
|
| + if (entry.dirty[i]) {
|
| + query.append(separator);
|
| + values.append(separator);
|
| + separator = ", ";
|
| + query.append(ColumnName(i));
|
| + values.append("?");
|
| + }
|
| + }
|
| + query.append(" ) ");
|
| + values.append(" )");
|
| + query.append(values);
|
| + ScopedStatement const statement(PrepareQuery(save_dbhandle_, query.c_str()));
|
| + BindDirtyFields(entry, statement.get());
|
| + return StepDone(statement.get(), "SaveNewEntryToDB()") &&
|
| + 1 == sqlite3_changes(save_dbhandle_);
|
| +}
|
| +
|
| +bool DirectoryBackingStore::UpdateEntryToDB(const EntryKernel& entry) {
|
| + DCHECK(save_dbhandle_);
|
| + string query;
|
| + query.reserve(kUpdateStatementBufferSize);
|
| + query.append("UPDATE metas ");
|
| + const char* separator = "SET ";
|
| + int i;
|
| + for (i = BEGIN_FIELDS; i < BLOB_FIELDS_END; ++i) {
|
| + if (entry.dirty[i]) {
|
| + query.append(separator);
|
| + separator = ", ";
|
| + query.append(ColumnName(i));
|
| + query.append(" = ? ");
|
| + }
|
| + }
|
| + query.append("WHERE metahandle = ?");
|
| + ScopedStatement const statement(PrepareQuery(save_dbhandle_, query.c_str()));
|
| + const int var_count = BindDirtyFields(entry, statement.get());
|
| + BindArg(statement.get(), entry.ref(META_HANDLE), var_count + 1);
|
| + return StepDone(statement.get(), "UpdateEntryToDB()") &&
|
| + 1 == sqlite3_changes(save_dbhandle_);
|
| +}
|
| +
|
| +bool DirectoryBackingStore::SaveExtendedAttributeToDB(
|
| + ExtendedAttributes::const_iterator i) {
|
| + DCHECK(save_dbhandle_);
|
| + ScopedStatement insert(PrepareQuery(save_dbhandle_,
|
| + "INSERT INTO extended_attributes "
|
| + "(metahandle, key, value) "
|
| + "values ( ?, ?, ? )",
|
| + i->first.metahandle, i->first.key, i->second.value));
|
| + return StepDone(insert.get(), "SaveExtendedAttributeToDB()")
|
| + && 1 == sqlite3_changes(LazyGetSaveHandle());
|
| +}
|
| +
|
| +bool DirectoryBackingStore::DeleteExtendedAttributeFromDB(
|
| + ExtendedAttributes::const_iterator i) {
|
| + DCHECK(save_dbhandle_);
|
| + ScopedStatement delete_attribute(PrepareQuery(save_dbhandle_,
|
| + "DELETE FROM extended_attributes "
|
| + "WHERE metahandle = ? AND key = ? ",
|
| + i->first.metahandle, i->first.key));
|
| + if (!StepDone(delete_attribute.get(), "DeleteExtendedAttributeFromDB()")) {
|
| + LOG(ERROR) << "DeleteExtendedAttributeFromDB(),StepDone() failed "
|
| + << "for metahandle: " << i->first.metahandle << " key: "
|
| + << i->first.key;
|
| + return false;
|
| + }
|
| + // The attribute may have never been saved to the database if it was
|
| + // created and then immediately deleted. So don't check that we
|
| + // deleted exactly 1 row.
|
| + return true;
|
| +}
|
| +
|
| +void DirectoryBackingStore::DropDeletedEntries() {
|
| + static const char delete_extended_attributes[] =
|
| + "DELETE FROM extended_attributes WHERE metahandle IN "
|
| + "(SELECT metahandle from death_row)";
|
| + static const char delete_metas[] = "DELETE FROM metas WHERE metahandle IN "
|
| + "(SELECT metahandle from death_row)";
|
| + // Put all statements into a transaction for better performance
|
| + ExecOrDie(load_dbhandle_, "BEGIN TRANSACTION");
|
| + ExecOrDie(load_dbhandle_, "CREATE TEMP TABLE death_row (metahandle BIGINT)");
|
| + ExecOrDie(load_dbhandle_, "INSERT INTO death_row "
|
| + "SELECT metahandle from metas WHERE is_del > 0 "
|
| + " AND is_unsynced < 1"
|
| + " AND is_unapplied_update < 1");
|
| + StatementExecutor x(load_dbhandle_);
|
| + x.Exec(delete_extended_attributes);
|
| + x.Exec(delete_metas);
|
| + ExecOrDie(load_dbhandle_, "DROP TABLE death_row");
|
| + ExecOrDie(load_dbhandle_, "COMMIT TRANSACTION");
|
| +}
|
| +
|
| +void DirectoryBackingStore::SafeDropTable(const char* table_name) {
|
| + string query = "DROP TABLE IF EXISTS ";
|
| + query.append(table_name);
|
| + const char* tail;
|
| + sqlite3_stmt* statement = NULL;
|
| + if (SQLITE_OK == sqlite3_prepare(load_dbhandle_, query.data(),
|
| + query.size(), &statement, &tail)) {
|
| + CHECK(SQLITE_DONE == sqlite3_step(statement));
|
| + }
|
| + sqlite3_finalize(statement);
|
| +}
|
| +
|
| +int DirectoryBackingStore::CreateExtendedAttributeTable() {
|
| + SafeDropTable("extended_attributes");
|
| + LOG(INFO) << "CreateExtendedAttributeTable";
|
| + return Exec(load_dbhandle_, "CREATE TABLE extended_attributes("
|
| + "metahandle bigint, "
|
| + "key varchar(127), "
|
| + "value blob, "
|
| + "PRIMARY KEY(metahandle, key) ON CONFLICT REPLACE)");
|
| +}
|
| +
|
| +void DirectoryBackingStore::DropAllTables() {
|
| + SafeDropTable("metas");
|
| + SafeDropTable("share_info");
|
| + SafeDropTable("share_version");
|
| + SafeDropTable("extended_attributes");
|
| +}
|
| +
|
| +int DirectoryBackingStore::CreateTables() {
|
| + LOG(INFO) << "First run, creating tables";
|
| + // Create two little tables share_version and share_info
|
| + int result = Exec(load_dbhandle_, "CREATE TABLE share_version ("
|
| + "id VARCHAR(128) primary key, data INT)");
|
| + result = SQLITE_DONE != result ? result :
|
| + Exec(load_dbhandle_, "INSERT INTO share_version VALUES(?, ?)",
|
| + dir_name_, kCurrentDBVersion);
|
| + result = SQLITE_DONE != result ? result :
|
| + Exec(load_dbhandle_, "CREATE TABLE share_info ("
|
| + "id VARCHAR(128) primary key, "
|
| + "last_sync_timestamp INT, "
|
| + "name VARCHAR(128), "
|
| + // Gets set if the syncer ever gets updates from the
|
| + // server and the server returns 0. Lets us detect the
|
| + // end of the initial sync.
|
| + "initial_sync_ended BIT default 0, "
|
| + "store_birthday VARCHAR(256), "
|
| + "db_create_version VARCHAR(128), "
|
| + "db_create_time int, "
|
| + "next_id bigint default -2, "
|
| + "cache_guid VARCHAR(32))");
|
| + result = SQLITE_DONE != result ? result :
|
| + Exec(load_dbhandle_, "INSERT INTO share_info VALUES"
|
| + "(?, " // id
|
| + "0, " // last_sync_timestamp
|
| + "?, " // name
|
| + "?, " // initial_sync_ended
|
| + "?, " // store_birthday
|
| + "?, " // db_create_version
|
| + "?, " // db_create_time
|
| + "-2, " // next_id
|
| + "?)", // cache_guid
|
| + dir_name_, // id
|
| + dir_name_, // name
|
| + false, // initial_sync_ended
|
| + "", // store_birthday
|
| + SYNC_ENGINE_VERSION_STRING, // db_create_version
|
| + static_cast<int32>(time(0)), // db_create_time
|
| + GenerateCacheGUID()); // cache_guid
|
| + // Create the big metas table.
|
| + string query = "CREATE TABLE metas " + ComposeCreateTableColumnSpecs
|
| + (g_metas_columns, g_metas_columns + ARRAYSIZE(g_metas_columns));
|
| + result = SQLITE_DONE != result ? result : Exec(load_dbhandle_, query.c_str());
|
| + // Insert the entry for the root into the metas table.
|
| + const int64 now = Now();
|
| + result = SQLITE_DONE != result ? result :
|
| + Exec(load_dbhandle_, "INSERT INTO metas "
|
| + "( id, metahandle, is_dir, ctime, mtime) "
|
| + "VALUES ( \"r\", 1, 1, ?, ?)",
|
| + now, now);
|
| + result = SQLITE_DONE != result ? result : CreateExtendedAttributeTable();
|
| + return result;
|
| +}
|
| +
|
| +sqlite3* DirectoryBackingStore::LazyGetSaveHandle() {
|
| + if (!save_dbhandle_ && !OpenAndConfigureHandleHelper(&save_dbhandle_)) {
|
| + DCHECK(FALSE) << "Unable to open handle for saving";
|
| + return NULL;
|
| + }
|
| + return save_dbhandle_;
|
| +}
|
| +
|
| +} // namespace syncable
|
|
|
| Property changes on: chrome\browser\sync\syncable\directory_backing_store.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|