Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(747)

Unified Diff: chrome/browser/sync/syncable/syncable.cc

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/sync/syncable/syncable.cc
===================================================================
--- chrome/browser/sync/syncable/syncable.cc (revision 0)
+++ chrome/browser/sync/syncable/syncable.cc (revision 0)
@@ -0,0 +1,2002 @@
+// 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/syncable.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
+#ifdef OS_MACOSX
+#include <CoreFoundation/CoreFoundation.h>
+#elif defined(OS_LINUX)
+#include <glib.h>
+#elif defined(OS_WINDOWS)
+#include <shlwapi.h> // for PathMatchSpec
+#endif
+
+#include <algorithm>
+#include <functional>
+#include <iomanip>
+#include <iterator>
+#include <set>
+#include <string>
+
+#include "base/hash_tables.h"
+#include "base/logging.h"
+#include "base/perftimer.h"
+#include "base/scoped_ptr.h"
+#include "base/time.h"
+#include "chrome/browser/sync/engine/syncer.h"
+#include "chrome/browser/sync/engine/syncer_util.h"
+#include "chrome/browser/sync/protocol/service_constants.h"
+#include "chrome/browser/sync/syncable/directory_backing_store.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/syncable-inl.h"
+#include "chrome/browser/sync/syncable/syncable_changes_version.h"
+#include "chrome/browser/sync/syncable/syncable_columns.h"
+#include "chrome/browser/sync/util/character_set_converters.h"
+#include "chrome/browser/sync/util/compat-file.h"
+#include "chrome/browser/sync/util/crypto_helpers.h"
+#include "chrome/browser/sync/util/event_sys-inl.h"
+#include "chrome/browser/sync/util/fast_dump.h"
+#include "chrome/browser/sync/util/path_helpers.h"
+
+namespace {
+enum InvariantCheckLevel {
+ OFF = 0,
+ VERIFY_IN_MEMORY = 1,
+ FULL_DB_VERIFICATION = 2
+};
+
+static const InvariantCheckLevel kInvariantCheckLevel = VERIFY_IN_MEMORY;
+
+// Max number of milliseconds to spend checking syncable entry invariants
+static const int kInvariantCheckMaxMs = 50;
+} // namespace
+
+// 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 browser_sync::FastDump;
+using browser_sync::SyncerUtil;
+using std::string;
+
+
+namespace syncable {
+
+int64 Now() {
+#ifdef OS_WINDOWS
+ FILETIME filetime;
+ SYSTEMTIME systime;
+ GetSystemTime(&systime);
+ SystemTimeToFileTime(&systime, &filetime);
+ // MSDN recommends converting via memcpy like this.
+ LARGE_INTEGER n;
+ memcpy(&n, &filetime, sizeof(filetime));
+ return n.QuadPart;
+#elif (defined(OS_LINUX) || defined(OS_MACOSX))
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return static_cast<int64>(tv.tv_sec);
+#else
+#error NEED OS SPECIFIC Now() implementation
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Compare functions and hashes for the indices.
+
+// Callback for sqlite3
+int ComparePathNames16(void*, int a_bytes, const void* a, int b_bytes,
+ const void* b) {
+#ifdef OS_WINDOWS
+ DCHECK_EQ(0, a_bytes % 2);
+ DCHECK_EQ(0, b_bytes % 2);
+ int result = CompareString(LOCALE_INVARIANT, NORM_IGNORECASE,
+ static_cast<const PathChar*>(a), a_bytes / 2,
+ static_cast<const PathChar*>(b), b_bytes / 2);
+ CHECK(0 != result) << "Error comparing strings: " << GetLastError();
+ return result - 2; // Convert to -1, 0, 1
+#elif defined(OS_LINUX)
+// misnomer for Linux. These are already utf8 bit strings.
+ gchar *case_folded_a;
+ gchar *case_folded_b;
+ GError *err = NULL;
+ case_folded_a = g_utf8_casefold(reinterpret_cast<const gchar*>(a), a_bytes);
+ CHECK(case_folded_a != NULL) << "g_utf8_casefold failed";
+ case_folded_b = g_utf8_casefold(reinterpret_cast<const gchar*>(b), b_bytes);
+ CHECK(case_folded_b != NULL) << "g_utf8_casefold failed";
+ gint result = g_utf8_collate(case_folded_a, case_folded_b);
+ g_free(case_folded_a);
+ g_free(case_folded_b);
+ if (result < 0) return -1;
+ if (result > 0) return 1;
+ return 0;
+#elif defined(OS_MACOSX)
+ CFStringRef a_str;
+ CFStringRef b_str;
+ a_str = CFStringCreateWithBytes(NULL, reinterpret_cast<const UInt8*>(a),
+ a_bytes, kCFStringEncodingUTF8, FALSE);
+ b_str = CFStringCreateWithBytes(NULL, reinterpret_cast<const UInt8*>(b),
+ b_bytes, kCFStringEncodingUTF8, FALSE);
+ CFComparisonResult res;
+ res = CFStringCompare(a_str, b_str, kCFCompareCaseInsensitive);
+ CFRelease(a_str);
+ CFRelease(b_str);
+ return res;
+#else
+#error no ComparePathNames16() for your OS
+#endif
+}
+
+template <Int64Field field_index>
+class SameField {
+ public:
+ inline bool operator()(const syncable::EntryKernel* a,
+ const syncable::EntryKernel* b) const {
+ return a->ref(field_index) == b->ref(field_index);
+ }
+};
+
+template <Int64Field field_index>
+class HashField {
+ public:
+ inline size_t operator()(const syncable::EntryKernel* a) const {
+ return hasher_(a->ref(field_index));
+ }
+ base::hash_set<int64> hasher_;
+};
+
+// TODO(ncarter): Rename!
+int ComparePathNames(const PathString& a, const PathString& b) {
+ const size_t val_size = sizeof(PathString::value_type);
+ return ComparePathNames16(NULL, a.size() * val_size, a.data(),
+ b.size() * val_size, b.data());
+}
+
+class LessParentIdAndNames {
+ public:
+ bool operator() (const syncable::EntryKernel* a,
+ const syncable::EntryKernel* b) const {
+ if (a->ref(PARENT_ID) != b->ref(PARENT_ID))
+ return a->ref(PARENT_ID) < b->ref(PARENT_ID);
+ return ComparePathNames(a->ref(NAME), b->ref(NAME)) < 0;
+ }
+};
+
+bool LessPathNames::operator() (const PathString& a,
+ const PathString& b) const {
+ return ComparePathNames(a, b) < 0;
+}
+
+// static
+Name Name::FromEntryKernel(EntryKernel* kernel) {
+ PathString& sync_name_ref = kernel->ref(UNSANITIZED_NAME).empty() ?
+ kernel->ref(NAME) : kernel->ref(UNSANITIZED_NAME);
+ return Name(kernel->ref(NAME), sync_name_ref, kernel->ref(NON_UNIQUE_NAME));
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Directory
+
+static const DirectoryChangeEvent kShutdownChangesEvent =
+ { DirectoryChangeEvent::SHUTDOWN, 0, 0 };
+
+void DestroyThreadNodeKey(void* vnode) {
+ ThreadNode* const node = reinterpret_cast<ThreadNode*>(vnode);
+ CHECK(!node->in_list)
+ << "\nThread exited while holding the transaction mutex!\n" << *node;
+ delete node;
+}
+
+Directory::Kernel::Kernel(const PathString& db_path,
+ const PathString& name,
+ const KernelLoadInfo& info)
+: db_path(db_path),
+ refcount(1),
+ name_(name),
+ metahandles_index(new Directory::MetahandlesIndex),
+ ids_index(new Directory::IdsIndex),
+ parent_id_and_names_index(new Directory::ParentIdAndNamesIndex),
+ extended_attributes(new ExtendedAttributes),
+ unapplied_update_metahandles(new MetahandleSet),
+ unsynced_metahandles(new MetahandleSet),
+ channel(new Directory::Channel(syncable::DIRECTORY_DESTROYED)),
+ changes_channel(new Directory::ChangesChannel(kShutdownChangesEvent)),
+ last_sync_timestamp_(info.kernel_info.last_sync_timestamp),
+ initial_sync_ended_(info.kernel_info.initial_sync_ended),
+ store_birthday_(info.kernel_info.store_birthday),
+ next_id(info.kernel_info.next_id),
+ cache_guid_(info.cache_guid),
+ next_metahandle(info.max_metahandle + 1) {
+ info_status_ = Directory::KERNEL_SHARE_INFO_VALID;
+ CHECK(0 == pthread_mutex_init(&mutex, NULL));
+ CHECK(0 == pthread_key_create(&thread_node_key, &DestroyThreadNodeKey));
+}
+
+inline void DeleteEntry(EntryKernel* kernel) {
+ delete kernel;
+}
+
+void Directory::Kernel::AddRef() {
+ base::subtle::NoBarrier_AtomicIncrement(&refcount, 1);
+}
+
+void Directory::Kernel::Release() {
+ if (!base::subtle::NoBarrier_AtomicIncrement(&refcount, -1))
+ delete this;
+}
+
+Directory::Kernel::~Kernel() {
+ CHECK(0 == refcount);
+ delete channel;
+ delete changes_channel;
+ CHECK(0 == pthread_mutex_destroy(&mutex));
+ pthread_key_delete(thread_node_key);
+ delete unsynced_metahandles;
+ delete unapplied_update_metahandles;
+ delete extended_attributes;
+ delete parent_id_and_names_index;
+ delete ids_index;
+ for_each(metahandles_index->begin(), metahandles_index->end(), DeleteEntry);
+ delete metahandles_index;
+}
+
+Directory::Directory() : kernel_(NULL), store_(NULL) {
+}
+
+Directory::~Directory() {
+ Close();
+}
+
+BOOL PathNameMatch(const PathString& pathname, const PathString& pathspec) {
+#ifdef OS_WINDOWS
+ // NB If we go Vista only this is easier:
+ // http://msdn2.microsoft.com/en-us/library/ms628611.aspx
+
+ // PathMatchSpec strips spaces from the start of pathspec, so we compare those
+ // ourselves.
+ const PathChar* pathname_ptr = pathname.c_str();
+ const PathChar* pathspec_ptr = pathspec.c_str();
+
+ while (*pathname_ptr == ' ' && *pathspec_ptr == ' ')
+ ++pathname_ptr, ++pathspec_ptr;
+
+ // if we have more inital spaces in the pathspec than in the pathname then the
+ // result from PathMatchSpec will be erronous
+ if (*pathspec_ptr == ' ')
+ return FALSE;
+
+ // PathMatchSpec also gets "confused" when there are ';' characters in name or
+ // in spec. So, if we match (f.i.) ";" with ";" PathMatchSpec will return
+ // FALSE (which is wrong). Luckily for us, we can easily fix this by
+ // substituting ';' with ':' which is illegal character in file name and
+ // we're not going to see it there. With ':' in path name and spec
+ // PathMatchSpec works fine.
+ if ((NULL == wcschr(pathname_ptr, L';')) &&
+ (NULL == wcschr(pathspec_ptr, L';'))) {
+ // No ';' in file name and in spec. Just pass it as it is.
+ return ::PathMatchSpec(pathname_ptr, pathspec_ptr);
+ }
+
+ // We need to subst ';' with ':' in both, name and spec
+ PathString name_subst(pathname_ptr);
+ PathString spec_subst(pathspec_ptr);
+
+ PathString::size_type index = name_subst.find(L';');
+ while (PathString::npos != index) {
+ name_subst[index] = L':';
+ index = name_subst.find(L';', index + 1);
+ }
+
+ index = spec_subst.find(L';');
+ while (PathString::npos != index) {
+ spec_subst[index] = L':';
+ index = spec_subst.find(L';', index + 1);
+ }
+
+ return ::PathMatchSpec(name_subst.c_str(), spec_subst.c_str());
+#else
+ return 0 == ComparePathNames(pathname, pathspec);
+#endif
+}
+
+DirOpenResult Directory::Open(const PathString& file_path,
+ const PathString& name) {
+ const DirOpenResult result = OpenImpl(file_path, name);
+ if (OPENED != result)
+ Close();
+ return result;
+}
+
+void Directory::InitializeIndices() {
+ MetahandlesIndex::iterator it = kernel_->metahandles_index->begin();
+ for (; it != kernel_->metahandles_index->end(); ++it) {
+ EntryKernel* entry = *it;
+ if (!entry->ref(IS_DEL))
+ kernel_->parent_id_and_names_index->insert(entry);
+ kernel_->ids_index->insert(entry);
+ if (entry->ref(IS_UNSYNCED))
+ kernel_->unsynced_metahandles->insert(entry->ref(META_HANDLE));
+ if (entry->ref(IS_UNAPPLIED_UPDATE))
+ kernel_->unapplied_update_metahandles->insert(entry->ref(META_HANDLE));
+ }
+}
+
+DirectoryBackingStore* Directory::CreateBackingStore(
+ const PathString& dir_name, const PathString& backing_filepath) {
+ return new DirectoryBackingStore(dir_name, backing_filepath);
+}
+
+DirOpenResult Directory::OpenImpl(const PathString& file_path,
+ const PathString& name) {
+ DCHECK_EQ(static_cast<DirectoryBackingStore*>(NULL), store_);
+ const PathString db_path = ::GetFullPath(file_path);
+ store_ = CreateBackingStore(name, db_path);
+
+ KernelLoadInfo info;
+ // Temporary indicies before kernel_ initialized in case Load fails. We 0(1)
+ // swap these later.
+ MetahandlesIndex metas_bucket;
+ ExtendedAttributes xattrs_bucket;
+ DirOpenResult result = store_->Load(&metas_bucket, &xattrs_bucket, &info);
+ if (OPENED != result)
+ return result;
+
+ kernel_ = new Kernel(db_path, name, info);
+ kernel_->metahandles_index->swap(metas_bucket);
+ kernel_->extended_attributes->swap(xattrs_bucket);
+ InitializeIndices();
+ return OPENED;
+}
+
+void Directory::Close() {
+ if (store_)
+ delete store_;
+ store_ = NULL;
+ if (kernel_) {
+ bool del = !base::subtle::NoBarrier_AtomicIncrement(&kernel_->refcount, -1);
+ DCHECK(del) << "Kernel should only have a single ref";
+ if (del)
+ delete kernel_;
+ kernel_ = NULL;
+ }
+}
+
+EntryKernel* Directory::GetEntryById(const Id& id) {
+ ScopedKernelLock lock(this);
+ return GetEntryById(id, &lock);
+}
+
+EntryKernel* Directory::GetEntryById(const Id& id,
+ ScopedKernelLock* const lock) {
+ DCHECK(kernel_);
+ // First look up in memory
+ kernel_->needle.ref(ID) = id;
+ IdsIndex::iterator id_found = kernel_->ids_index->find(&kernel_->needle);
+ if (id_found != kernel_->ids_index->end()) {
+ // Found it in memory. Easy.
+ return *id_found;
+ }
+ return NULL;
+}
+
+EntryKernel* Directory::GetEntryByTag(const PathString& tag) {
+ ScopedKernelLock lock(this);
+ DCHECK(kernel_);
+ // We don't currently keep a separate index for the tags. Since tags
+ // only exist for server created items that are the first items
+ // to be created in a store, they should have small metahandles.
+ // So, we just iterate over the items in sorted metahandle order,
+ // looking for a match.
+ MetahandlesIndex& set = *kernel_->metahandles_index;
+ for (MetahandlesIndex::iterator i = set.begin(); i != set.end(); ++i) {
+ if ((*i)->ref(SINGLETON_TAG) == tag) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+EntryKernel* Directory::GetEntryByHandle(const int64 metahandle) {
+ ScopedKernelLock lock(this);
+ return GetEntryByHandle(metahandle, &lock);
+}
+
+EntryKernel* Directory::GetEntryByHandle(const int64 metahandle,
+ ScopedKernelLock* lock) {
+ // Look up in memory
+ kernel_->needle.ref(META_HANDLE) = metahandle;
+ MetahandlesIndex::iterator found =
+ kernel_->metahandles_index->find(&kernel_->needle);
+ if (found != kernel_->metahandles_index->end()) {
+ // Found it in memory. Easy.
+ return *found;
+ }
+ return NULL;
+}
+
+EntryKernel* Directory::GetChildWithName(const Id& parent_id,
+ const PathString& name) {
+ ScopedKernelLock lock(this);
+ return GetChildWithName(parent_id, name, &lock);
+}
+
+// Will return child entry if the folder is opened,
+// otherwise it will return NULL.
+EntryKernel* Directory::GetChildWithName(const Id& parent_id,
+ const PathString& name,
+ ScopedKernelLock* const lock) {
+ PathString dbname = name;
+ EntryKernel* parent = GetEntryById(parent_id, lock);
+ if (parent == NULL)
+ return NULL;
+ return GetChildWithNameImpl(parent_id, dbname, lock);
+}
+
+// Will return child entry even when the folder is not
+// opened. This is used by syncer to apply update when folder is closed.
+EntryKernel* Directory::GetChildWithDBName(const Id& parent_id,
+ const PathString& name) {
+ ScopedKernelLock lock(this);
+ return GetChildWithNameImpl(parent_id, name, &lock);
+}
+
+EntryKernel* Directory::GetChildWithNameImpl(const Id& parent_id,
+ const PathString& name,
+ ScopedKernelLock* const lock) {
+ // First look up in memory:
+ kernel_->needle.ref(NAME) = name;
+ kernel_->needle.ref(PARENT_ID) = parent_id;
+ ParentIdAndNamesIndex::iterator found =
+ kernel_->parent_id_and_names_index->find(&kernel_->needle);
+ if (found != kernel_->parent_id_and_names_index->end()) {
+ // Found it in memory. Easy.
+ return *found;
+ }
+ return NULL;
+}
+
+// An interface to specify the details of which children
+// GetChildHandles() is looking for.
+struct PathMatcher {
+ explicit PathMatcher(const Id& parent_id) : parent_id_(parent_id) { }
+ virtual ~PathMatcher() { }
+ enum MatchType {
+ NO_MATCH,
+ MATCH,
+ // Means we found the only entry we're looking for in
+ // memory so we don't need to check the DB.
+ EXACT_MATCH
+ };
+ virtual MatchType PathMatches(const PathString& path) = 0;
+ typedef Directory::ParentIdAndNamesIndex Index;
+ virtual Index::iterator lower_bound(Index* index) = 0;
+ virtual Index::iterator upper_bound(Index* index) = 0;
+ const Id parent_id_;
+ EntryKernel needle_;
+};
+
+// Matches all children.
+struct AllPathsMatcher : public PathMatcher {
+ explicit AllPathsMatcher(const Id& parent_id) : PathMatcher(parent_id) {
+ }
+ virtual MatchType PathMatches(const PathString& path) {
+ return MATCH;
+ }
+ virtual Index::iterator lower_bound(Index* index) {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME).clear();
+ return index->lower_bound(&needle_);
+ }
+
+ virtual Index::iterator upper_bound(Index* index) {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME).clear();
+ Index::iterator i = index->upper_bound(&needle_),
+ end = index->end();
+ while (i != end && (*i)->ref(PARENT_ID) == parent_id_)
+ ++i;
+ return i;
+ }
+};
+
+// Matches an exact filename only; no wildcards.
+struct ExactPathMatcher : public PathMatcher {
+ ExactPathMatcher(const PathString& pathspec, const Id& parent_id)
+ : PathMatcher(parent_id), pathspec_(pathspec) {
+ }
+ virtual MatchType PathMatches(const PathString& path) {
+ return 0 == ComparePathNames(path, pathspec_) ? EXACT_MATCH : NO_MATCH;
+ }
+ virtual Index::iterator lower_bound(Index* index) {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME) = pathspec_;
+ return index->lower_bound(&needle_);
+ }
+ virtual Index::iterator upper_bound(Index* index) {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME) = pathspec_;
+ return index->upper_bound(&needle_);
+ }
+ const PathString pathspec_;
+};
+
+// Matches a pathspec with wildcards.
+struct PartialPathMatcher : public PathMatcher {
+ PartialPathMatcher(const PathString& pathspec,
+ PathString::size_type wildcard, const Id& parent_id)
+ : PathMatcher(parent_id), pathspec_(pathspec) {
+ if (0 == wildcard)
+ return;
+ lesser_.assign(pathspec_.data(), wildcard);
+ greater_.assign(pathspec_.data(), wildcard);
+ // Increment the last letter of greater so we can then less than
+ // compare to it.
+ PathString::size_type i = greater_.size() - 1;
+ do {
+ if (greater_[i] == std::numeric_limits<PathString::value_type>::max()) {
+ greater_.resize(i); // Try the preceding character.
+ if (0 == i--)
+ break;
+ } else {
+ greater_[i] += 1;
+ }
+ // Yes, there are cases where incrementing a character
+ // actually decreases its position in the sort. Example: 9 -> :
+ } while (ComparePathNames(lesser_, greater_) >= 0);
+ }
+
+ virtual MatchType PathMatches(const PathString& path) {
+ return PathNameMatch(path, pathspec_) ? MATCH : NO_MATCH;
+ }
+
+ virtual Index::iterator lower_bound(Index* index) {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME) = lesser_;
+ return index->lower_bound(&needle_);
+ }
+ virtual Index::iterator upper_bound(Index* index) {
+ if (greater_.empty()) {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME).clear();
+ Index::iterator i = index->upper_bound(&needle_),
+ end = index->end();
+ while (i != end && (*i)->ref(PARENT_ID) == parent_id_)
+ ++i;
+ return i;
+ } else {
+ needle_.ref(PARENT_ID) = parent_id_;
+ needle_.ref(NAME) = greater_;
+ return index->lower_bound(&needle_);
+ }
+ }
+
+ const PathString pathspec_;
+ PathString lesser_;
+ PathString greater_;
+};
+
+
+void Directory::GetChildHandles(BaseTransaction* trans, const Id& parent_id,
+ Directory::ChildHandles* result) {
+ AllPathsMatcher matcher(parent_id);
+ return GetChildHandlesImpl(trans, parent_id, &matcher, result);
+}
+
+void Directory::GetChildHandlesImpl(BaseTransaction* trans, const Id& parent_id,
+ PathMatcher* matcher,
+ Directory::ChildHandles* result) {
+ CHECK(this == trans->directory());
+ result->clear();
+ {
+ ScopedKernelLock lock(this);
+ ParentIdAndNamesIndex* const index =
+ kernel_->parent_id_and_names_index;
+ typedef ParentIdAndNamesIndex::iterator iterator;
+ for (iterator i = matcher->lower_bound(index),
+ end = matcher->upper_bound(index); i != end; ++i) {
+ // root's parent_id is NULL in the db but 0 in memory, so
+ // have avoid listing the root as its own child.
+ if ((*i)->ref(ID) == (*i)->ref(PARENT_ID))
+ continue;
+ PathMatcher::MatchType match = matcher->PathMatches((*i)->ref(NAME));
+ if (PathMatcher::NO_MATCH == match)
+ continue;
+ result->push_back((*i)->ref(META_HANDLE));
+ if (PathMatcher::EXACT_MATCH == match)
+ return;
+ }
+ }
+}
+
+EntryKernel* Directory::GetRootEntry() {
+ return GetEntryById(Id());
+}
+
+EntryKernel* Directory::GetEntryByPath(const PathString& path) {
+ CHECK(kernel_);
+ EntryKernel* result = GetRootEntry();
+ CHECK(result) << "There should always be a root node.";
+ for (PathSegmentIterator<PathString> i(path), end;
+ i != end && NULL != result; ++i) {
+ result = GetChildWithName(result->ref(ID), *i);
+ }
+ return result;
+}
+
+void ZeroFields(EntryKernel* entry, int first_field) {
+ int i = first_field;
+ // Note that bitset<> constructor sets all bits to zero, and strings
+ // initialize to empty.
+ for ( ; i < INT64_FIELDS_END; ++i)
+ entry->ref(static_cast<Int64Field>(i)) = 0;
+ for ( ; i < ID_FIELDS_END; ++i)
+ entry->ref(static_cast<IdField>(i)).Clear();
+ for ( ; i < BIT_FIELDS_END; ++i)
+ entry->ref(static_cast<BitField>(i)) = false;
+ if (i < BLOB_FIELDS_END)
+ i = BLOB_FIELDS_END;
+}
+
+void Directory::InsertEntry(EntryKernel* entry) {
+ ScopedKernelLock lock(this);
+ InsertEntry(entry, &lock);
+}
+
+void Directory::InsertEntry(EntryKernel* entry, ScopedKernelLock* lock) {
+ DCHECK(NULL != lock);
+ CHECK(NULL != entry);
+ static const char error[] = "Entry already in memory index.";
+ CHECK(kernel_->metahandles_index->insert(entry).second) << error;
+ if (!entry->ref(IS_DEL))
+ CHECK(kernel_->parent_id_and_names_index->insert(entry).second) << error;
+ CHECK(kernel_->ids_index->insert(entry).second) << error;
+}
+
+bool Directory::Undelete(EntryKernel* const entry) {
+ DCHECK(entry->ref(IS_DEL));
+ ScopedKernelLock lock(this);
+ if (NULL != GetChildWithName(entry->ref(PARENT_ID), entry->ref(NAME), &lock))
+ return false; // Would have duplicated existing entry.
+ entry->ref(IS_DEL) = false;
+ entry->dirty[IS_DEL] = true;
+ CHECK(kernel_->parent_id_and_names_index->insert(entry).second);
+ return true;
+}
+
+bool Directory::Delete(EntryKernel* const entry) {
+ DCHECK(!entry->ref(IS_DEL));
+ entry->ref(IS_DEL) = true;
+ entry->dirty[IS_DEL] = true;
+ ScopedKernelLock lock(this);
+ CHECK(1 == kernel_->parent_id_and_names_index->erase(entry));
+ return true;
+}
+
+bool Directory::ReindexId(EntryKernel* const entry, const Id& new_id) {
+ ScopedKernelLock lock(this);
+ if (NULL != GetEntryById(new_id, &lock))
+ return false;
+ CHECK(1 == kernel_->ids_index->erase(entry));
+ entry->ref(ID) = new_id;
+ CHECK(kernel_->ids_index->insert(entry).second);
+ return true;
+}
+
+bool Directory::ReindexParentIdAndName(EntryKernel* const entry,
+ const Id& new_parent_id,
+ const PathString& new_name) {
+ ScopedKernelLock lock(this);
+ PathString new_indexed_name = new_name;
+ if (entry->ref(IS_DEL)) {
+ entry->ref(PARENT_ID) = new_parent_id;
+ entry->ref(NAME) = new_indexed_name;
+ return true;
+ }
+
+ // check for a case changing rename
+ if (entry->ref(PARENT_ID) == new_parent_id &&
+ 0 == ComparePathNames(entry->ref(NAME), new_indexed_name)) {
+ entry->ref(NAME) = new_indexed_name;
+ } else {
+ if (NULL != GetChildWithName(new_parent_id, new_indexed_name, &lock))
+ return false;
+ CHECK(1 == kernel_->parent_id_and_names_index->erase(entry));
+ entry->ref(PARENT_ID) = new_parent_id;
+ entry->ref(NAME) = new_indexed_name;
+ CHECK(kernel_->parent_id_and_names_index->insert(entry).second);
+ }
+ return true;
+}
+
+// static
+bool Directory::SafeToPurgeFromMemory(const EntryKernel* const entry) {
+ return entry->ref(IS_DEL) && !entry->dirty.any() && !entry->ref(SYNCING) &&
+ !entry->ref(IS_UNAPPLIED_UPDATE) && !entry->ref(IS_UNSYNCED);
+}
+
+void Directory::TakeSnapshotForSaveChanges(SaveChangesSnapshot* snapshot) {
+ ReadTransaction trans(this, __FILE__, __LINE__);
+ ScopedKernelLock lock(this);
+ // Deep copy dirty entries from kernel_->metahandles_index into snapshot and
+ // clear dirty flags.
+ for (MetahandlesIndex::iterator i = kernel_->metahandles_index->begin();
+ i != kernel_->metahandles_index->end(); ++i) {
+ EntryKernel* entry = *i;
+ if (!entry->dirty.any())
+ continue;
+ snapshot->dirty_metas.insert(snapshot->dirty_metas.end(), *entry);
+ entry->dirty.reset();
+ // TODO(timsteele): The previous *windows only* SaveChanges code path seems
+ // to have a bug in that the IS_NEW bit is not rolled back if the entire DB
+ // transaction is rolled back, due to the "recent" windows optimization of
+ // using a ReadTransaction rather than a WriteTransaction in SaveChanges.
+ // This bit is only used to decide whether we should sqlite INSERT or
+ // UPDATE, and if we are INSERTing we make sure to dirty all the fields so
+ // as to overwrite the database default values. For now, this is rectified
+ // by flipping the bit to false here (note that the snapshot will contain
+ // the "original" value), and then resetting it on failure in
+ // HandleSaveChangesFailure, where "failure" is defined as "the DB
+ // "transaction was rolled back". This is safe because the only user of this
+ // bit is in fact SaveChanges, which enforces mutually exclusive access by
+ // way of save_changes_mutex_. The TODO is to consider abolishing this bit
+ // in favor of using a sqlite INSERT OR REPLACE, which could(would?) imply
+ // that all bits need to be written rather than just the dirty ones in
+ // the BindArg helper function.
+ entry->ref(IS_NEW) = false;
+ }
+
+ // Do the same for extended attributes.
+ for (ExtendedAttributes::iterator i = kernel_->extended_attributes->begin();
+ i != kernel_->extended_attributes->end(); ++i) {
+ if (!i->second.dirty)
+ continue;
+ snapshot->dirty_xattrs[i->first] = i->second;
+ i->second.dirty = false;
+ }
+
+ // Fill kernel_info_status and kernel_info.
+ PersistedKernelInfo& info = snapshot->kernel_info;
+ info.initial_sync_ended = kernel_->initial_sync_ended_;
+ info.last_sync_timestamp = kernel_->last_sync_timestamp_;
+ // To avoid duplicates when the process crashes, we record the next_id to be
+ // greater magnitude than could possibly be reached before the next save
+ // changes. In other words, it's effectively impossible for the user to
+ // generate 65536 new bookmarks in 3 seconds.
+ info.next_id = kernel_->next_id - 65536;
+ info.store_birthday = kernel_->store_birthday_;
+ snapshot->kernel_info_status = kernel_->info_status_;
+ // This one we reset on failure.
+ kernel_->info_status_ = KERNEL_SHARE_INFO_VALID;
+}
+
+bool Directory::SaveChanges() {
+ bool success = false;
+ DCHECK(store_);
+ PThreadScopedLock<PThreadMutex> lock(&kernel_->save_changes_mutex);
+ // Snapshot and save.
+ SaveChangesSnapshot snapshot;
+ TakeSnapshotForSaveChanges(&snapshot);
+ success = store_->SaveChanges(snapshot);
+
+ // Handle success or failure.
+ if (success)
+ VacuumAfterSaveChanges(snapshot);
+ else
+ HandleSaveChangesFailure(snapshot);
+ return success;
+}
+
+void Directory::VacuumAfterSaveChanges(const SaveChangesSnapshot& snapshot) {
+ // Need a write transaction as we are about to permanently purge entries.
+ WriteTransaction trans(this, VACUUM_AFTER_SAVE, __FILE__, __LINE__);
+ ScopedKernelLock lock(this);
+ kernel_->flushed_metahandles_.Push(0); // Begin flush marker
+ // Now drop everything we can out of memory.
+ for (OriginalEntries::const_iterator i = snapshot.dirty_metas.begin();
+ i != snapshot.dirty_metas.end(); ++i) {
+ kernel_->needle.ref(META_HANDLE) = i->ref(META_HANDLE);
+ MetahandlesIndex::iterator found =
+ kernel_->metahandles_index->find(&kernel_->needle);
+ EntryKernel* entry = (found == kernel_->metahandles_index->end() ?
+ NULL : *found);
+ if (entry && SafeToPurgeFromMemory(entry)) {
+ // We now drop deleted metahandles that are up to date on both the client
+ // and the server.
+ size_t num_erased = 0;
+ kernel_->flushed_metahandles_.Push(entry->ref(META_HANDLE));
+ num_erased = kernel_->ids_index->erase(entry);
+ DCHECK_EQ(1, num_erased);
+ num_erased = kernel_->metahandles_index->erase(entry);
+ DCHECK_EQ(1, num_erased);
+ delete entry;
+ }
+ }
+
+ ExtendedAttributes::const_iterator i = snapshot.dirty_xattrs.begin();
+ while (i != snapshot.dirty_xattrs.end()) {
+ ExtendedAttributeKey key(i->first.metahandle, i->first.key);
+ ExtendedAttributes::iterator found =
+ kernel_->extended_attributes->find(key);
+ if (found == kernel_->extended_attributes->end() ||
+ found->second.dirty || !i->second.is_deleted) {
+ ++i;
+ } else {
+ kernel_->extended_attributes->erase(found);
+ }
+ }
+}
+
+void Directory::HandleSaveChangesFailure(const SaveChangesSnapshot& snapshot) {
+ ScopedKernelLock lock(this);
+ kernel_->info_status_ = KERNEL_SHARE_INFO_DIRTY;
+
+ // Because we cleared dirty bits on the real entries when taking the snapshot,
+ // we should make sure the fact that the snapshot was not persisted gets
+ // reflected in the entries. Not doing this would mean if no other changes
+ // occur to the same fields of the entries in dirty_metas some changes could
+ // end up being lost, if they also failed to be committed to the server.
+ // Setting the bits ensures that SaveChanges will at least try again later.
+ for (OriginalEntries::const_iterator i = snapshot.dirty_metas.begin();
+ i != snapshot.dirty_metas.end(); ++i) {
+ kernel_->needle.ref(META_HANDLE) = i->ref(META_HANDLE);
+ MetahandlesIndex::iterator found =
+ kernel_->metahandles_index->find(&kernel_->needle);
+ if (found != kernel_->metahandles_index->end()) {
+ (*found)->dirty |= i->dirty;
+ (*found)->ref(IS_NEW) = i->ref(IS_NEW);
+ }
+ }
+
+ for (ExtendedAttributes::const_iterator i = snapshot.dirty_xattrs.begin();
+ i != snapshot.dirty_xattrs.end(); ++i) {
+ ExtendedAttributeKey key(i->first.metahandle, i->first.key);
+ ExtendedAttributes::iterator found =
+ kernel_->extended_attributes->find(key);
+ if (found != kernel_->extended_attributes->end())
+ found->second.dirty = true;
+ }
+}
+
+int64 Directory::last_sync_timestamp() const {
+ ScopedKernelLock lock(this);
+ return kernel_->last_sync_timestamp_;
+}
+
+void Directory::set_last_sync_timestamp(int64 timestamp) {
+ ScopedKernelLock lock(this);
+ if (kernel_->last_sync_timestamp_ == timestamp)
+ return;
+ kernel_->last_sync_timestamp_ = timestamp;
+ kernel_->info_status_ = KERNEL_SHARE_INFO_DIRTY;
+}
+
+bool Directory::initial_sync_ended() const {
+ ScopedKernelLock lock(this);
+ return kernel_->initial_sync_ended_;
+}
+
+void Directory::set_initial_sync_ended(bool x) {
+ ScopedKernelLock lock(this);
+ if (kernel_->initial_sync_ended_ == x)
+ return;
+ kernel_->initial_sync_ended_ = x;
+ kernel_->info_status_ = KERNEL_SHARE_INFO_DIRTY;
+}
+
+string Directory::store_birthday() const {
+ ScopedKernelLock lock(this);
+ return kernel_->store_birthday_;
+}
+
+void Directory::set_store_birthday(string store_birthday) {
+ ScopedKernelLock lock(this);
+ if (kernel_->store_birthday_ == store_birthday)
+ return;
+ kernel_->store_birthday_ = store_birthday;
+ kernel_->info_status_ = KERNEL_SHARE_INFO_DIRTY;
+}
+
+string Directory::cache_guid() const {
+ // No need to lock since nothing ever writes to it after load.
+ return kernel_->cache_guid_;
+}
+
+void Directory::GetAllMetaHandles(BaseTransaction* trans,
+ MetahandleSet* result) {
+ result->clear();
+ ScopedKernelLock lock(this);
+ MetahandlesIndex::iterator i;
+ for (i = kernel_->metahandles_index->begin();
+ i != kernel_->metahandles_index->end();
+ ++i) {
+ result->insert((*i)->ref(META_HANDLE));
+ }
+}
+
+void Directory::GetUnsyncedMetaHandles(BaseTransaction* trans,
+ UnsyncedMetaHandles* result) {
+ result->clear();
+ ScopedKernelLock lock(this);
+ copy(kernel_->unsynced_metahandles->begin(),
+ kernel_->unsynced_metahandles->end(), back_inserter(*result));
+}
+
+void Directory::GetAllExtendedAttributes(BaseTransaction* trans,
+ int64 metahandle,
+ std::set<ExtendedAttribute>* result) {
+ AttributeKeySet keys;
+ GetExtendedAttributesList(trans, metahandle, &keys);
+ AttributeKeySet::iterator iter;
+ for (iter = keys.begin(); iter != keys.end(); ++iter) {
+ ExtendedAttributeKey key(metahandle, *iter);
+ ExtendedAttribute extended_attribute(trans, GET_BY_HANDLE, key);
+ CHECK(extended_attribute.good());
+ result->insert(extended_attribute);
+ }
+}
+
+void Directory::GetExtendedAttributesList(BaseTransaction* trans,
+ int64 metahandle, AttributeKeySet* result) {
+ ExtendedAttributes::iterator iter;
+ for (iter = kernel_->extended_attributes->begin();
+ iter != kernel_->extended_attributes->end(); ++iter) {
+ if (iter->first.metahandle == metahandle) {
+ if (!iter->second.is_deleted)
+ result->insert(iter->first.key);
+ }
+ }
+}
+
+void Directory::DeleteAllExtendedAttributes(WriteTransaction* trans,
+ int64 metahandle) {
+ AttributeKeySet keys;
+ GetExtendedAttributesList(trans, metahandle, &keys);
+ AttributeKeySet::iterator iter;
+ for (iter = keys.begin(); iter != keys.end(); ++iter) {
+ ExtendedAttributeKey key(metahandle, *iter);
+ MutableExtendedAttribute attribute(trans, GET_BY_HANDLE, key);
+ // This flags the attribute for deletion during SaveChanges. At that time
+ // any deleted attributes are purged from disk and memory.
+ attribute.delete_attribute();
+ }
+}
+
+int64 Directory::unsynced_entity_count() const {
+ ScopedKernelLock lock(this);
+ return kernel_->unsynced_metahandles->size();
+}
+
+void Directory::GetUnappliedUpdateMetaHandles(BaseTransaction* trans,
+ UnappliedUpdateMetaHandles* result) {
+ result->clear();
+ ScopedKernelLock lock(this);
+ copy(kernel_->unapplied_update_metahandles->begin(),
+ kernel_->unapplied_update_metahandles->end(),
+ back_inserter(*result));
+}
+
+
+class IdFilter {
+ public:
+ virtual ~IdFilter() { }
+ virtual bool ShouldConsider(const Id& id) const = 0;
+};
+
+
+class FullScanFilter : public IdFilter {
+ public:
+ virtual bool ShouldConsider(const Id& id) const {
+ return true;
+ }
+};
+
+class SomeIdsFilter : public IdFilter {
+ public:
+ virtual bool ShouldConsider(const Id& id) const {
+ return binary_search(ids_.begin(), ids_.end(), id);
+ }
+ std::vector<Id> ids_;
+};
+
+void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans,
+ const OriginalEntries* originals) {
+ MetahandleSet handles;
+ SomeIdsFilter filter;
+ filter.ids_.reserve(originals->size());
+ for (OriginalEntries::const_iterator i = originals->begin(),
+ end = originals->end(); i != end; ++i) {
+ Entry e(trans, GET_BY_HANDLE, i->ref(META_HANDLE));
+ CHECK(e.good());
+ filter.ids_.push_back(e.Get(ID));
+ handles.insert(i->ref(META_HANDLE));
+ }
+ std::sort(filter.ids_.begin(), filter.ids_.end());
+ CheckTreeInvariants(trans, handles, filter);
+}
+
+void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans,
+ bool full_scan) {
+ // TODO(timsteele): This is called every time a WriteTransaction finishes.
+ // The performance hit is substantial given that we now examine every single
+ // syncable entry. Need to redesign this.
+ MetahandleSet handles;
+ GetAllMetaHandles(trans, &handles);
+ if (full_scan) {
+ FullScanFilter fullfilter;
+ CheckTreeInvariants(trans, handles, fullfilter);
+ } else {
+ SomeIdsFilter filter;
+ MetahandleSet::iterator i;
+ for (i = handles.begin() ; i != handles.end() ; ++i) {
+ Entry e(trans, GET_BY_HANDLE, *i);
+ CHECK(e.good());
+ filter.ids_.push_back(e.Get(ID));
+ }
+ sort(filter.ids_.begin(), filter.ids_.end());
+ CheckTreeInvariants(trans, handles, filter);
+ }
+}
+
+void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans,
+ const MetahandleSet& handles,
+ const IdFilter& idfilter) {
+ int64 max_ms = kInvariantCheckMaxMs;
+ if (max_ms < 0)
+ max_ms = std::numeric_limits<int64>::max();
+ PerfTimer check_timer;
+ MetahandleSet::const_iterator i;
+ int entries_done = 0;
+ for (i = handles.begin() ; i != handles.end() ; ++i) {
+ int64 metahandle = *i;
+ Entry e(trans, GET_BY_HANDLE, metahandle);
+ CHECK(e.good());
+ syncable::Id id = e.Get(ID);
+ syncable::Id parentid = e.Get(PARENT_ID);
+
+ if (id.IsRoot()) {
+ CHECK(e.Get(IS_DIR)) << e;
+ CHECK(parentid.IsRoot()) << e;
+ CHECK(!e.Get(IS_UNSYNCED)) << e;
+ ++entries_done;
+ continue;
+ }
+ if (!e.Get(IS_DEL)) {
+ CHECK(id != parentid) << e;
+ CHECK(!e.Get(NAME).empty()) << e;
+ int safety_count = handles.size() + 1;
+ while (!parentid.IsRoot()) {
+ if (!idfilter.ShouldConsider(parentid))
+ break;
+ Entry parent(trans, GET_BY_ID, parentid);
+ CHECK(parent.good()) << e;
+ CHECK(parent.Get(IS_DIR)) << parent << e;
+ CHECK(!parent.Get(IS_DEL)) << parent << e;
+ CHECK(handles.end() != handles.find(parent.Get(META_HANDLE)))
+ << e << parent;
+ parentid = parent.Get(PARENT_ID);
+ CHECK(--safety_count >= 0) << e << parent;
+ }
+ }
+ int64 base_version = e.Get(BASE_VERSION);
+ int64 server_version = e.Get(SERVER_VERSION);
+ if (CHANGES_VERSION == base_version || 0 == base_version) {
+ if (e.Get(IS_UNAPPLIED_UPDATE)) {
+ // Unapplied new item.
+ CHECK(e.Get(IS_DEL)) << e;
+ CHECK(id.ServerKnows()) << e;
+ } else {
+ // Uncommitted item.
+ if (!e.Get(IS_DEL)) {
+ CHECK(e.Get(IS_UNSYNCED)) << e;
+ }
+ CHECK(0 == server_version) << e;
+ CHECK(!id.ServerKnows()) << e;
+ }
+ } else {
+ CHECK(id.ServerKnows());
+ }
+ ++entries_done;
+ int64 elapsed_ms = check_timer.Elapsed().InMilliseconds();
+ if (elapsed_ms > max_ms) {
+ LOG(INFO) << "Cutting Invariant check short after " << elapsed_ms << "ms."
+ " Processed " << entries_done << "/" << handles.size() << " entries";
+ return;
+ }
+ }
+ // I did intend to add a check here to ensure no entries had been pulled into
+ // memory by this function, but we can't guard against another ReadTransaction
+ // pulling entries into RAM
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// ScopedKernelLocks
+
+ScopedKernelLock::ScopedKernelLock(const Directory* dir)
+ : dir_(const_cast<Directory*>(dir)) {
+ // Swap out the dbhandle to enforce the "No IO while holding kernel
+ // lock" rule.
+ // HA!! Yeah right. What about your pre-cached queries :P
+ pthread_mutex_lock(&dir->kernel_->mutex);
+}
+ScopedKernelLock::~ScopedKernelLock() {
+ pthread_mutex_unlock(&dir_->kernel_->mutex);
+}
+
+ScopedKernelUnlock::ScopedKernelUnlock(ScopedKernelLock* lock)
+ : lock_(lock) {
+ pthread_mutex_unlock(&lock->dir_->kernel_->mutex);
+}
+ScopedKernelUnlock::~ScopedKernelUnlock() {
+ pthread_mutex_lock(&lock_->dir_->kernel_->mutex);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Transactions
+#if defined LOG_ALL || !defined NDEBUG
+static const bool kLoggingInfo = true;
+#else
+static const bool kLoggingInfo = false;
+#endif
+
+ThreadNode* BaseTransaction::MakeThreadNode() {
+ ThreadNode* node = reinterpret_cast<ThreadNode*>
+ (pthread_getspecific(dirkernel_->thread_node_key));
+ if (NULL == node) {
+ node = new ThreadNode;
+ node->id = GetCurrentThreadId();
+ pthread_setspecific(dirkernel_->thread_node_key, node);
+ } else if (node->in_list) {
+ logging::LogMessage(source_file_, line_, logging::LOG_FATAL).stream()
+ << " Recursive Lock attempt by thread id " << node->id << "." << std::endl
+ << "Already entered transaction at " << node->file << ":" << node->line;
+ }
+ node->file = source_file_;
+ node->line = line_;
+ node->wait_started = base::TimeTicks::Now();
+ return node;
+}
+
+void BaseTransaction::Lock(ThreadCounts* const thread_counts,
+ ThreadNode* node, TransactionClass tclass) {
+ ScopedTransactionLock lock(&dirkernel_->transaction_mutex);
+ // Increment the waiters count.
+ node->tclass = tclass;
+ thread_counts->waiting += 1;
+ node->Insert(&thread_counts->waiting_headtail);
+
+ // Block until we can own the reader/writer lock
+ bool ready = 1 == thread_counts->waiting;
+ while (true) {
+ if (ready) {
+ if (0 == thread_counts->active) {
+ // We can take the lock because there is no contention.
+ break;
+ } else if (READ == tclass
+ && READ == thread_counts->active_headtail.next->tclass) {
+ // We can take the lock because reads can run simultaneously.
+ break;
+ }
+ }
+ // Wait to be woken up and check again.
+ node->wake_up = false;
+ do {
+ CHECK(0 == pthread_cond_wait(&node->condvar.condvar_,
+ &dirkernel_->transaction_mutex.mutex_));
+ } while (!node->wake_up);
+ ready = true;
+ }
+
+ // Move from the list of waiters to the list of active.
+ thread_counts->waiting -= 1;
+ thread_counts->active += 1;
+ CHECK(WRITE != tclass || 1 == thread_counts->active);
+ node->Remove();
+ node->Insert(&thread_counts->active_headtail);
+ if (WRITE == tclass)
+ node->current_write_trans = static_cast<WriteTransaction*>(this);
+}
+
+void BaseTransaction::AfterLock(ThreadNode* node) {
+ time_acquired_ = base::TimeTicks::Now();
+
+ const base::TimeDelta elapsed = time_acquired_ - node->wait_started;
+ if (kLoggingInfo && elapsed.InMilliseconds() > 200) {
+ logging::LogMessage(source_file_, line_, logging::LOG_INFO).stream()
+ << name_ << " transaction waited "
+ << elapsed.InSecondsF() << " seconds.";
+ }
+}
+
+void BaseTransaction::Init(ThreadCounts* const thread_counts,
+ TransactionClass tclass) {
+ ThreadNode* const node = MakeThreadNode();
+ Lock(thread_counts, node, tclass);
+ AfterLock(node);
+}
+
+BaseTransaction::BaseTransaction(Directory* directory, const char* name,
+ const char* source_file, int line)
+ : directory_(directory), dirkernel_(directory->kernel_), name_(name),
+ source_file_(source_file), line_(line) {
+}
+
+void BaseTransaction::UnlockAndLog(ThreadCounts* const thread_counts,
+ OriginalEntries* originals_arg) {
+ scoped_ptr<OriginalEntries> originals(originals_arg);
+ const base::TimeDelta elapsed = base::TimeTicks::Now() - time_acquired_;
+ if (kLoggingInfo && elapsed.InMilliseconds() > 50) {
+ logging::LogMessage(source_file_, line_, logging::LOG_INFO).stream()
+ << name_ << " transaction completed in " << elapsed.InSecondsF()
+ << " seconds.";
+ }
+
+ {
+ ScopedTransactionLock lock(&dirkernel_->transaction_mutex);
+ // Let go of the reader/writer lock
+ thread_counts->active -= 1;
+ ThreadNode* const node = reinterpret_cast<ThreadNode*>
+ (pthread_getspecific(dirkernel_->thread_node_key));
+ CHECK(node != NULL);
+ node->Remove();
+ node->current_write_trans = NULL;
+ if (0 == thread_counts->active) {
+ // Wake up a waiting thread, FIFO
+ if (dirkernel_->thread_counts.waiting > 0) {
+ ThreadNode* const headtail =
+ &dirkernel_->thread_counts.waiting_headtail;
+ ThreadNode* node = headtail->next;
+ node->wake_up = true;
+ CHECK(0 == pthread_cond_signal(&node->condvar.condvar_));
+ if (READ == node->tclass) do {
+ // Wake up all consecutive readers.
+ node = node->next;
+ if (node == headtail)
+ break;
+ if (READ != node->tclass)
+ break;
+ node->wake_up = true;
+ CHECK(0 == pthread_cond_signal(&node->condvar.condvar_));
+ } while (true);
+ }
+ }
+ if (NULL == originals.get() || originals->empty())
+ return;
+ dirkernel_->changes_channel_mutex.Lock();
+ // Tell listeners to calculate changes while we still have the mutex.
+ DirectoryChangeEvent event = { DirectoryChangeEvent::CALCULATE_CHANGES,
+ originals.get(), this, writer_ };
+ dirkernel_->changes_channel->NotifyListeners(event);
+ }
+ DirectoryChangeEvent event = { DirectoryChangeEvent::TRANSACTION_COMPLETE,
+ NULL, NULL, INVALID };
+ dirkernel_->changes_channel->NotifyListeners(event);
+ dirkernel_->changes_channel_mutex.Unlock();
+}
+
+ReadTransaction::ReadTransaction(Directory* directory, const char* file,
+ int line)
+ : BaseTransaction(directory, "Read", file, line) {
+ Init(&dirkernel_->thread_counts, READ);
+ writer_ = INVALID;
+}
+
+ReadTransaction::ReadTransaction(const ScopedDirLookup& scoped_dir,
+ const char* file, int line)
+ : BaseTransaction(scoped_dir.operator -> (), "Read", file, line) {
+ Init(&dirkernel_->thread_counts, READ);
+ writer_ = INVALID;
+}
+
+ReadTransaction::~ReadTransaction() {
+ UnlockAndLog(&dirkernel_->thread_counts, NULL);
+}
+
+WriteTransaction::WriteTransaction(Directory* directory, WriterTag writer,
+ const char* file, int line)
+ : BaseTransaction(directory, "Write", file, line), skip_destructor_(false),
+ originals_(new OriginalEntries) {
+ Init(&dirkernel_->thread_counts, WRITE);
+ writer_ = writer;
+}
+
+WriteTransaction::WriteTransaction(const ScopedDirLookup& scoped_dir,
+ WriterTag writer, const char* file, int line)
+ : BaseTransaction(scoped_dir.operator -> (), "Write", file, line),
+ skip_destructor_(false), originals_(new OriginalEntries) {
+ Init(&dirkernel_->thread_counts, WRITE);
+ writer_ = writer;
+}
+
+WriteTransaction::WriteTransaction(Directory* directory, const char* name,
+ WriterTag writer,
+ const char* file, int line,
+ bool skip_destructor,
+ OriginalEntries* originals)
+ : BaseTransaction(directory, name, file, line),
+ skip_destructor_(skip_destructor), originals_(originals) {
+ writer_ = writer;
+}
+
+void WriteTransaction::SaveOriginal(EntryKernel* entry) {
+ if (NULL == entry)
+ return;
+ OriginalEntries::iterator i = originals_->lower_bound(*entry);
+ if (i == originals_->end() ||
+ i->ref(META_HANDLE) != entry->ref(META_HANDLE)) {
+ originals_->insert(i, *entry);
+ }
+}
+
+WriteTransaction::~WriteTransaction() {
+ if (skip_destructor_)
+ return;
+ if (OFF != kInvariantCheckLevel) {
+ const bool full_scan = (FULL_DB_VERIFICATION == kInvariantCheckLevel);
+ if (full_scan)
+ directory()->CheckTreeInvariants(this, full_scan);
+ else
+ directory()->CheckTreeInvariants(this, originals_);
+ }
+ UnlockAndLog(&dirkernel_->thread_counts, originals_);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Entry
+
+Entry::Entry(BaseTransaction* trans, GetById, const Id& id)
+ : basetrans_(trans) {
+ kernel_ = trans->directory()->GetEntryById(id);
+}
+
+Entry::Entry(BaseTransaction* trans, GetByTag, const PathString& tag)
+ : basetrans_(trans) {
+ kernel_ = trans->directory()->GetEntryByTag(tag);
+}
+
+Entry::Entry(BaseTransaction* trans, GetByHandle, int64 metahandle)
+ : basetrans_(trans) {
+ kernel_ = trans->directory()->GetEntryByHandle(metahandle);
+}
+
+Entry::Entry(BaseTransaction* trans, GetByPath, const PathString& path)
+ : basetrans_(trans) {
+ kernel_ = trans->directory()->GetEntryByPath(path);
+}
+
+Entry::Entry(BaseTransaction* trans, GetByParentIdAndName, const Id& parentid,
+ const PathString& name)
+ : basetrans_(trans) {
+ kernel_ = trans->directory()->GetChildWithName(parentid, name);
+}
+
+Entry::Entry(BaseTransaction* trans, GetByParentIdAndDBName, const Id& parentid,
+ const PathString& name)
+ : basetrans_(trans) {
+ kernel_ = trans->directory()->GetChildWithDBName(parentid, name);
+}
+
+
+Directory* Entry::dir() const {
+ return basetrans_->directory();
+}
+
+PathString Entry::Get(StringField field) const {
+ DCHECK(kernel_);
+ return kernel_->ref(field);
+}
+
+void Entry::GetAllExtendedAttributes(BaseTransaction* trans,
+ std::set<ExtendedAttribute> *result) {
+ dir()->GetAllExtendedAttributes(trans, kernel_->ref(META_HANDLE), result);
+}
+
+void Entry::GetExtendedAttributesList(BaseTransaction* trans,
+ AttributeKeySet* result) {
+ dir()->GetExtendedAttributesList(trans, kernel_->ref(META_HANDLE), result);
+}
+
+void Entry::DeleteAllExtendedAttributes(WriteTransaction *trans) {
+ dir()->DeleteAllExtendedAttributes(trans, kernel_->ref(META_HANDLE));
+}
+
+///////////////////////////////////////////////////////////////////////////
+// MutableEntry
+
+MutableEntry::MutableEntry(WriteTransaction* trans, Create,
+ const Id& parent_id, const PathString& name)
+ : Entry(trans) {
+ if (NULL != trans->directory()->GetChildWithName(parent_id, name)) {
+ kernel_ = NULL; // would have duplicated an existing entry.
+ return;
+ }
+ Init(trans, parent_id, name);
+}
+
+
+void MutableEntry::Init(WriteTransaction* trans, const Id& parent_id,
+ const PathString& name) {
+ kernel_ = new EntryKernel;
+ ZeroFields(kernel_, BEGIN_FIELDS);
+ kernel_->ref(ID) = trans->directory_->NextId();
+ kernel_->dirty[ID] = true;
+ kernel_->ref(META_HANDLE) = trans->directory_->NextMetahandle();
+ kernel_->dirty[META_HANDLE] = true;
+ kernel_->ref(PARENT_ID) = parent_id;
+ kernel_->dirty[PARENT_ID] = true;
+ kernel_->ref(NAME) = name;
+ kernel_->dirty[NAME] = true;
+ kernel_->ref(NON_UNIQUE_NAME) = name;
+ kernel_->dirty[NON_UNIQUE_NAME] = true;
+ kernel_->ref(IS_NEW) = true;
+ const int64 now = Now();
+ kernel_->ref(CTIME) = now;
+ kernel_->dirty[CTIME] = true;
+ kernel_->ref(MTIME) = now;
+ kernel_->dirty[MTIME] = true;
+ // We match the database defaults here
+ kernel_->ref(BASE_VERSION) = CHANGES_VERSION;
+ trans->directory()->InsertEntry(kernel_);
+ // Because this entry is new, it was originally deleted.
+ kernel_->ref(IS_DEL) = true;
+ trans->SaveOriginal(kernel_);
+ kernel_->ref(IS_DEL) = false;
+}
+
+MutableEntry::MutableEntry(WriteTransaction* trans, CreateNewUpdateItem,
+ const Id& id)
+ : Entry(trans) {
+ Entry same_id(trans, GET_BY_ID, id);
+ if (same_id.good()) {
+ kernel_ = NULL; // already have an item with this ID.
+ return;
+ }
+ kernel_ = new EntryKernel;
+ ZeroFields(kernel_, BEGIN_FIELDS);
+ kernel_->ref(ID) = id;
+ kernel_->dirty[ID] = true;
+ kernel_->ref(META_HANDLE) = trans->directory_->NextMetahandle();
+ kernel_->dirty[META_HANDLE] = true;
+ kernel_->ref(IS_DEL) = true;
+ kernel_->dirty[IS_DEL] = true;
+ kernel_->ref(IS_NEW) = true;
+ // We match the database defaults here
+ kernel_->ref(BASE_VERSION) = CHANGES_VERSION;
+ trans->directory()->InsertEntry(kernel_);
+ trans->SaveOriginal(kernel_);
+}
+
+MutableEntry::MutableEntry(WriteTransaction* trans, GetById, const Id& id)
+ : Entry(trans, GET_BY_ID, id) {
+ trans->SaveOriginal(kernel_);
+}
+
+MutableEntry::MutableEntry(WriteTransaction* trans, GetByHandle,
+ int64 metahandle)
+ : Entry(trans, GET_BY_HANDLE, metahandle) {
+ trans->SaveOriginal(kernel_);
+}
+
+MutableEntry::MutableEntry(WriteTransaction* trans, GetByPath,
+ const PathString& path)
+ : Entry(trans, GET_BY_PATH, path) {
+ trans->SaveOriginal(kernel_);
+}
+
+MutableEntry::MutableEntry(WriteTransaction* trans, GetByParentIdAndName,
+ const Id& parentid, const PathString& name)
+ : Entry(trans, GET_BY_PARENTID_AND_NAME, parentid, name) {
+ trans->SaveOriginal(kernel_);
+}
+
+MutableEntry::MutableEntry(WriteTransaction* trans, GetByParentIdAndDBName,
+ const Id& parentid, const PathString& name)
+ : Entry(trans, GET_BY_PARENTID_AND_DBNAME, parentid, name) {
+ trans->SaveOriginal(kernel_);
+}
+
+bool MutableEntry::PutIsDel(bool is_del) {
+ DCHECK(kernel_);
+ if (is_del == kernel_->ref(IS_DEL))
+ return true;
+ if (is_del) {
+ UnlinkFromOrder();
+ if (!dir()->Delete(kernel_))
+ return false;
+ return true;
+ } else {
+ if (!dir()->Undelete(kernel_))
+ return false;
+ PutPredecessor(Id()); // Restores position to the 0th index.
+ return true;
+ }
+}
+
+bool MutableEntry::Put(Int64Field field, const int64& value) {
+ DCHECK(kernel_);
+ if (kernel_->ref(field) != value) {
+ kernel_->ref(field) = value;
+ kernel_->dirty[static_cast<int>(field)] = true;
+ }
+ return true;
+}
+
+bool MutableEntry::Put(IdField field, const Id& value) {
+ DCHECK(kernel_);
+ if (kernel_->ref(field) != value) {
+ if (ID == field) {
+ if (!dir()->ReindexId(kernel_, value))
+ return false;
+ } else if (PARENT_ID == field) {
+ if (!dir()->ReindexParentIdAndName(kernel_, value, kernel_->ref(NAME)))
+ return false;
+ } else {
+ kernel_->ref(field) = value;
+ }
+ kernel_->dirty[static_cast<int>(field)] = true;
+ }
+ return true;
+}
+
+WriteTransaction* MutableEntry::trans() const {
+ // We are in a mutable entry, so we must be in a write transaction.
+ // Maybe we could keep a pointer to the transaction in MutableEntry.
+ ThreadNode* node = reinterpret_cast<ThreadNode*>
+ (pthread_getspecific(dir()->kernel_->thread_node_key));
+ return node->current_write_trans;
+}
+
+bool MutableEntry::Put(BaseVersion field, int64 value) {
+ DCHECK(kernel_);
+ if (kernel_->ref(field) != value) {
+ kernel_->ref(field) = value;
+ kernel_->dirty[static_cast<int>(field)] = true;
+ }
+ return true;
+}
+
+bool MutableEntry::Put(StringField field, const PathString& value) {
+ return PutImpl(field, value);
+}
+
+bool MutableEntry::PutImpl(StringField field, const PathString& value) {
+ DCHECK(kernel_);
+ if (kernel_->ref(field) != value) {
+ if (NAME == field) {
+ if (!dir()->ReindexParentIdAndName(kernel_, kernel_->ref(PARENT_ID),
+ value))
+ return false;
+ } else {
+ kernel_->ref(field) = value;
+ }
+ kernel_->dirty[static_cast<int>(field)] = true;
+ }
+ return true;
+}
+
+bool MutableEntry::Put(IndexedBitField field, bool value) {
+ DCHECK(kernel_);
+ if (kernel_->ref(field) != value) {
+ MetahandleSet* index;
+ if (IS_UNSYNCED == field)
+ index = dir()->kernel_->unsynced_metahandles;
+ else
+ index = dir()->kernel_->unapplied_update_metahandles;
+
+ ScopedKernelLock lock(dir());
+ if (value)
+ CHECK(index->insert(kernel_->ref(META_HANDLE)).second);
+ else
+ CHECK(1 == index->erase(kernel_->ref(META_HANDLE)));
+ kernel_->ref(field) = value;
+ kernel_->dirty[static_cast<int>(field)] = true;
+ }
+ return true;
+}
+
+// Avoids temporary collision in index when renaming a bookmark
+// to another folder.
+bool MutableEntry::PutParentIdAndName(const Id& parent_id,
+ const Name& name) {
+ DCHECK(kernel_);
+ const bool parent_id_changes = parent_id != kernel_->ref(PARENT_ID);
+ bool db_name_changes = name.db_value() != kernel_->ref(NAME);
+ if (parent_id_changes || db_name_changes) {
+ if (!dir()->ReindexParentIdAndName(kernel_, parent_id,
+ name.db_value()))
+ return false;
+ }
+ Put(UNSANITIZED_NAME, name.GetUnsanitizedName());
+ Put(NON_UNIQUE_NAME, name.non_unique_value());
+ if (db_name_changes)
+ kernel_->dirty[NAME] = true;
+ if (parent_id_changes) {
+ kernel_->dirty[PARENT_ID] = true;
+ PutPredecessor(Id()); // Put in the 0th position.
+ }
+ return true;
+}
+
+void MutableEntry::UnlinkFromOrder() {
+ Id old_previous = Get(PREV_ID);
+ Id old_next = Get(NEXT_ID);
+
+ // Self-looping signifies that this item is not in the order. If
+ // we were to set these to 0, we could get into trouble because
+ // this node might look like the first node in the ordering.
+ Put(NEXT_ID, Get(ID));
+ Put(PREV_ID, Get(ID));
+
+ if (!old_previous.IsRoot()) {
+ if (old_previous == old_next) {
+ // Note previous == next doesn't imply previous == next == Get(ID). We
+ // could have prev==next=="c-XX" and Get(ID)=="sX..." if an item was added
+ // and deleted before receiving the server ID in the commit response.
+ CHECK((old_next == Get(ID)) || !old_next.ServerKnows());
+ return; // Done if we were already self-looped (hence unlinked).
+ }
+ MutableEntry previous_entry(trans(), GET_BY_ID, old_previous);
+ CHECK(previous_entry.good());
+ previous_entry.Put(NEXT_ID, old_next);
+ }
+
+ if (!old_next.IsRoot()) {
+ MutableEntry next_entry(trans(), GET_BY_ID, old_next);
+ CHECK(next_entry.good());
+ next_entry.Put(PREV_ID, old_previous);
+ }
+}
+
+bool MutableEntry::PutPredecessor(const Id& predecessor_id) {
+ // TODO(ncarter): Maybe there should be an independent HAS_POSITION bit?
+ if (!Get(IS_BOOKMARK_OBJECT))
+ return true;
+ UnlinkFromOrder();
+
+ if (Get(IS_DEL)) {
+ DCHECK(predecessor_id.IsNull());
+ return true;
+ }
+
+ // This is classic insert-into-doubly-linked-list from CS 101 and your last
+ // job interview. An "IsRoot" Id signifies the head or tail.
+ Id successor_id;
+ if (!predecessor_id.IsRoot()) {
+ MutableEntry predecessor(trans(), GET_BY_ID, predecessor_id);
+ CHECK(predecessor.good());
+ if (predecessor.Get(PARENT_ID) != Get(PARENT_ID))
+ return false;
+ successor_id = predecessor.Get(NEXT_ID);
+ predecessor.Put(NEXT_ID, Get(ID));
+ } else {
+ syncable::Directory* dir = trans()->directory();
+ successor_id = dir->GetFirstChildId(trans(), Get(PARENT_ID));
+ }
+ if (!successor_id.IsRoot()) {
+ MutableEntry successor(trans(), GET_BY_ID, successor_id);
+ CHECK(successor.good());
+ if (successor.Get(PARENT_ID) != Get(PARENT_ID))
+ return false;
+ successor.Put(PREV_ID, Get(ID));
+ }
+ DCHECK(predecessor_id != Get(ID));
+ DCHECK(successor_id != Get(ID));
+ Put(PREV_ID, predecessor_id);
+ Put(NEXT_ID, successor_id);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// High-level functions
+
+int64 Directory::NextMetahandle() {
+ ScopedKernelLock lock(this);
+ int64 metahandle = (kernel_->next_metahandle)++;
+ return metahandle;
+}
+
+// Always returns a client ID that is the string representation of a negative
+// number.
+Id Directory::NextId() {
+ int64 result;
+ {
+ ScopedKernelLock lock(this);
+ result = (kernel_->next_id)--;
+ kernel_->info_status_ = KERNEL_SHARE_INFO_DIRTY;
+ }
+ DCHECK_LT(result, 0);
+ return Id::CreateFromClientString(Int64ToString(result));
+}
+
+Id Directory::GetChildWithNullIdField(IdField field,
+ BaseTransaction* trans,
+ const Id& parent_id) {
+ // This query is O(number of children), which should be acceptable
+ // when this method is used as the first step in enumerating the children of
+ // a node. But careless use otherwise could potentially result in
+ // O((number of children)^2) performance.
+ ChildHandles child_handles;
+ GetChildHandles(trans, parent_id, &child_handles);
+ ChildHandles::const_iterator it;
+ for (it = child_handles.begin(); it != child_handles.end(); ++it) {
+ Entry e(trans, GET_BY_HANDLE, *it);
+ CHECK(e.good());
+ if (e.Get(field).IsRoot())
+ return e.Get(ID);
+ }
+
+ return Id();
+}
+
+Id Directory::GetFirstChildId(BaseTransaction* trans,
+ const Id& parent_id) {
+ return GetChildWithNullIdField(PREV_ID, trans, parent_id);
+}
+
+Id Directory::GetLastChildId(BaseTransaction* trans,
+ const Id& parent_id) {
+ return GetChildWithNullIdField(NEXT_ID, trans, parent_id);
+}
+
+ExtendedAttribute::ExtendedAttribute(BaseTransaction* trans, GetByHandle,
+ const ExtendedAttributeKey& key) {
+ Directory::Kernel* const kernel = trans->directory()->kernel_;
+ ScopedKernelLock lock(trans->directory());
+ Init(trans, kernel, &lock, key);
+}
+
+bool ExtendedAttribute::Init(BaseTransaction* trans,
+ Directory::Kernel* const kernel,
+ ScopedKernelLock* lock,
+ const ExtendedAttributeKey& key) {
+ i_ = kernel->extended_attributes->find(key);
+ good_ = kernel->extended_attributes->end() != i_;
+ return good_;
+}
+
+MutableExtendedAttribute::MutableExtendedAttribute(
+ WriteTransaction* trans, GetByHandle,
+ const ExtendedAttributeKey& key) :
+ ExtendedAttribute(trans, GET_BY_HANDLE, key) {
+}
+
+MutableExtendedAttribute::MutableExtendedAttribute(
+ WriteTransaction* trans, Create, const ExtendedAttributeKey& key) {
+ Directory::Kernel* const kernel = trans->directory()->kernel_;
+ ScopedKernelLock lock(trans->directory());
+ if (!Init(trans, kernel, &lock, key)) {
+ ExtendedAttributeValue val;
+ val.dirty = true;
+ i_ = kernel->extended_attributes->insert(std::make_pair(key, val)).first;
+ good_ = true;
+ }
+}
+
+bool IsLegalNewParent(BaseTransaction* trans, const Id& entry_id,
+ const Id& new_parent_id) {
+ if (entry_id.IsRoot())
+ return false;
+ // we have to ensure that the entry is not an ancestor of the new parent.
+ Id ancestor_id = new_parent_id;
+ while (!ancestor_id.IsRoot()) {
+ if (entry_id == ancestor_id)
+ return false;
+ Entry new_parent(trans, GET_BY_ID, ancestor_id);
+ CHECK(new_parent.good());
+ ancestor_id = new_parent.Get(PARENT_ID);
+ }
+ return true;
+}
+
+// returns -1 if s contains any non [0-9] characters
+static int PathStringToInteger(PathString s) {
+ PathString::const_iterator i = s.begin();
+ for (; i != s.end(); ++i) {
+ if (PathString::npos == PathString(PSTR("0123456789")).find(*i))
+ return -1;
+ }
+ return
+#if !PATHSTRING_IS_STD_STRING
+ _wtoi
+#else
+ atoi
+#endif
+ (s.c_str());
+}
+
+static PathString IntegerToPathString(int i) {
+ const size_t kBufSize = 25;
+ PathChar buf[kBufSize];
+#if !PATHSTRING_IS_STD_STRING
+ const int radix = 10;
+ _itow(i, buf, radix);
+#else
+ snprintf(buf, kBufSize, "%d", i);
+#endif
+ return buf;
+}
+
+// appends ~1 to the end of 's' unless there is already ~#, in which case
+// it just increments the number
+static PathString FixBasenameInCollision(const PathString s) {
+ PathString::size_type last_tilde = s.find_last_of(PSTR('~'));
+ if (PathString::npos == last_tilde) return s + PSTR("~1");
+ if (s.size() == (last_tilde + 1)) return s + PSTR("1");
+ // we have ~, but not necessarily ~# (for some number >= 0). check for that
+ int n;
+ if ((n = PathStringToInteger(s.substr(last_tilde + 1))) != -1) {
+ n++;
+ PathString pre_number = s.substr(0, last_tilde + 1);
+ return pre_number + IntegerToPathString(n);
+ } else {
+ // we have a ~, but not a number following it, so we'll add another
+ // ~ and this time, a number
+ return s + PSTR("~1");
+ }
+}
+
+void DBName::MakeNoncollidingForEntry(BaseTransaction* trans,
+ const Id& parent_id,
+ Entry *e) {
+ const PathString& desired_name = *this;
+ CHECK(!desired_name.empty());
+ PathString::size_type first_dot = desired_name.find_first_of(PSTR('.'));
+ if (PathString::npos == first_dot)
+ first_dot = desired_name.size();
+ PathString basename = desired_name.substr(0, first_dot);
+ PathString dotextension = desired_name.substr(first_dot);
+ CHECK(basename + dotextension == desired_name);
+ for (;;) {
+ // check for collision
+ PathString testname = basename + dotextension;
+ Entry same_path_entry(trans, GET_BY_PARENTID_AND_DBNAME,
+ parent_id, testname);
+ if (!same_path_entry.good() || (e && same_path_entry.Get(ID) == e->Get(ID)))
+ break;
+ // there was a collision, so fix the name
+ basename = FixBasenameInCollision(basename);
+ }
+ // Set our value to the new value. This invalidates desired_name.
+ PathString new_value = basename + dotextension;
+ swap(new_value);
+}
+
+PathString GetFullPath(BaseTransaction* trans, const Entry& e) {
+ PathString result;
+#ifdef STL_MSVC
+ result.reserve(MAX_PATH);
+#endif
+ ReverseAppend(e.Get(NAME), &result);
+ Id id = e.Get(PARENT_ID);
+ while (!id.IsRoot()) {
+ result.push_back(kPathSeparator[0]);
+ Entry ancestor(trans, GET_BY_ID, id);
+ if (!ancestor.good()) {
+ // This can happen if the parent folder got deleted before the entry.
+ LOG(WARNING) << "Cannot get full path of " << e
+ << "\nbecause an ancestor folder has been deleted.";
+ result.clear();
+ return result;
+ }
+ ReverseAppend(ancestor.Get(NAME), &result);
+ id = ancestor.Get(PARENT_ID);
+ }
+ result.push_back(kPathSeparator[0]);
+ reverse(result.begin(), result.end());
+ return result;
+}
+
+const Blob* GetExtendedAttributeValue(const Entry& e,
+ const PathString& attribute_name) {
+ ExtendedAttributeKey key(e.Get(META_HANDLE), attribute_name);
+ ExtendedAttribute extended_attribute(e.trans(), GET_BY_HANDLE, key);
+ if (extended_attribute.good() && !extended_attribute.is_deleted())
+ return &extended_attribute.value();
+ return NULL;
+}
+
+// This function sets only the flags needed to get this entry to sync.
+void MarkForSyncing(syncable::MutableEntry* e) {
+ DCHECK_NE(static_cast<MutableEntry*>(NULL), e);
+ DCHECK(!e->IsRoot()) << "We shouldn't mark a permanent object for syncing.";
+ e->Put(IS_UNSYNCED, true);
+ e->Put(SYNCING, false);
+}
+
+} // namespace syncable
+
+namespace {
+ class DumpSeparator {
+ } separator;
+ class DumpColon {
+ } colon;
+} // namespace
+
+inline FastDump& operator<<(FastDump& dump, const DumpSeparator&) {
+ dump.out_->sputn(", ", 2);
+ return dump;
+}
+
+inline FastDump& operator<<(FastDump& dump, const DumpColon&) {
+ dump.out_->sputn(": ", 2);
+ return dump;
+}
+
+std::ostream& operator<<(std::ostream& stream, const syncable::Entry& entry) {
+ // Using ostreams directly here is dreadfully slow, because a mutex is
+ // acquired for every <<. Users noticed it spiking CPU.
+ using browser_sync::ToUTF8;
+ using syncable::BitField;
+ using syncable::BitTemp;
+ using syncable::BlobField;
+ using syncable::EntryKernel;
+ using syncable::g_metas_columns;
+ using syncable::IdField;
+ using syncable::Int64Field;
+ using syncable::StringField;
+ using syncable::BEGIN_FIELDS;
+ using syncable::BIT_FIELDS_END;
+ using syncable::BIT_TEMPS_BEGIN;
+ using syncable::BIT_TEMPS_END;
+ using syncable::BLOB_FIELDS_END;
+ using syncable::INT64_FIELDS_END;
+ using syncable::ID_FIELDS_END;
+ using syncable::STRING_FIELDS_END;
+
+ int i;
+ FastDump s(&stream);
+ EntryKernel* const kernel = entry.kernel_;
+ for (i = BEGIN_FIELDS; i < INT64_FIELDS_END; ++i) {
+ s << g_metas_columns[i].name << colon
+ << kernel->ref(static_cast<Int64Field>(i)) << separator;
+ }
+ for ( ; i < ID_FIELDS_END; ++i) {
+ s << g_metas_columns[i].name << colon
+ << kernel->ref(static_cast<IdField>(i)) << separator;
+ }
+ s << "Flags: ";
+ for ( ; i < BIT_FIELDS_END; ++i) {
+ if (kernel->ref(static_cast<BitField>(i)))
+ s << g_metas_columns[i].name << separator;
+ }
+ for ( ; i < STRING_FIELDS_END; ++i) {
+ ToUTF8 field(kernel->ref(static_cast<StringField>(i)));
+ s << g_metas_columns[i].name << colon << field.get_string() << separator;
+ }
+ for ( ; i < BLOB_FIELDS_END; ++i) {
+ s << g_metas_columns[i].name << colon
+ << kernel->ref(static_cast<BlobField>(i)) << separator;
+ }
+ s << "TempFlags: ";
+ for ( ; i < BIT_TEMPS_END; ++i) {
+ if (kernel->ref(static_cast<BitTemp>(i)))
+ s << "#" << i - BIT_TEMPS_BEGIN << separator;
+ }
+ return stream;
+}
+
+std::ostream& operator<<(std::ostream& s, const syncable::Blob& blob) {
+ for (syncable::Blob::const_iterator i = blob.begin(); i != blob.end(); ++i)
+ s << std::hex << std::setw(2)
+ << std::setfill('0') << static_cast<unsigned int>(*i);
+ return s << std::dec;
+}
+
+FastDump& operator<<(FastDump& dump, const syncable::Blob& blob) {
+ if (blob.empty())
+ return dump;
+ string buffer(HexEncode(&blob[0], blob.size()));
+ dump.out_->sputn(buffer.c_str(), buffer.size());
+ return dump;
+}
+
+std::ostream& operator<<(std::ostream& s, const syncable::ThreadNode& node) {
+ s << "thread id: " << std::hex << node.id << "\n"
+ << "file: " << node.file << "\n"
+ << "line: " << std::dec << node.line << "\n"
+ << "wait_started: " << node.wait_started.ToInternalValue();
+ return s;
+}
Property changes on: chrome\browser\sync\syncable\syncable.cc
___________________________________________________________________
Added: svn:eol-style
+ LF

Powered by Google App Engine
This is Rietveld 408576698