| Index: chrome/browser/sync/engine/syncapi.cc
|
| ===================================================================
|
| --- chrome/browser/sync/engine/syncapi.cc (revision 0)
|
| +++ chrome/browser/sync/engine/syncapi.cc (revision 0)
|
| @@ -0,0 +1,1565 @@
|
| +// Copyright (c) 2006-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/engine/syncapi.h"
|
| +
|
| +#if defined(OS_WINDOWS)
|
| +#include <windows.h>
|
| +#include <iphlpapi.h>
|
| +#endif
|
| +
|
| +#include <iomanip>
|
| +#include <list>
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/at_exit.h"
|
| +#include "base/basictypes.h"
|
| +#include "base/scoped_ptr.h"
|
| +#include "base/string_util.h"
|
| +#include "chrome/browser/sync/engine/all_status.h"
|
| +#include "chrome/browser/sync/engine/auth_watcher.h"
|
| +#include "chrome/browser/sync/engine/change_reorder_buffer.h"
|
| +#include "chrome/browser/sync/engine/client_command_channel.h"
|
| +#include "chrome/browser/sync/engine/model_safe_worker.h"
|
| +#include "chrome/browser/sync/engine/net/gaia_authenticator.h"
|
| +#include "chrome/browser/sync/engine/net/server_connection_manager.h"
|
| +#include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h"
|
| +#include "chrome/browser/sync/engine/syncer.h"
|
| +#include "chrome/browser/sync/engine/syncer_thread.h"
|
| +#include "chrome/browser/sync/notifier/listener/talk_mediator.h"
|
| +#include "chrome/browser/sync/notifier/listener/talk_mediator_impl.h"
|
| +#include "chrome/browser/sync/protocol/service_constants.h"
|
| +#include "chrome/browser/sync/syncable/directory_manager.h"
|
| +#include "chrome/browser/sync/syncable/syncable.h"
|
| +#include "chrome/browser/sync/util/character_set_converters.h"
|
| +#include "chrome/browser/sync/util/closure.h"
|
| +#include "chrome/browser/sync/util/crypto_helpers.h"
|
| +#include "chrome/browser/sync/util/event_sys.h"
|
| +#include "chrome/browser/sync/util/path_helpers.h"
|
| +#include "chrome/browser/sync/util/pthread_helpers.h"
|
| +#include "chrome/browser/sync/util/user_settings.h"
|
| +#include "googleurl/src/gurl.h"
|
| +
|
| +using browser_sync::AllStatus;
|
| +using browser_sync::AllStatusEvent;
|
| +using browser_sync::AuthWatcher;
|
| +using browser_sync::AuthWatcherEvent;
|
| +using browser_sync::ClientCommandChannel;
|
| +using browser_sync::Syncer;
|
| +using browser_sync::SyncerEvent;
|
| +using browser_sync::SyncerStatus;
|
| +using browser_sync::SyncerThread;
|
| +using browser_sync::UserSettings;
|
| +using browser_sync::TalkMediator;
|
| +using browser_sync::TalkMediatorImpl;
|
| +using std::list;
|
| +using std::hex;
|
| +using std::string;
|
| +using std::vector;
|
| +using syncable::Directory;
|
| +using syncable::DirectoryManager;
|
| +
|
| +static const int kServerReachablePollingIntervalMsec = 60000 * 60;
|
| +static const int kThreadExitTimeoutMsec = 60000;
|
| +static const int kSSLPort = 443;
|
| +
|
| +// We shouldn't call InitLogFiles more than once since that will cause a crash.
|
| +// So we use a global state variable to avoid that. This doesn't work in case
|
| +// of multiple threads, and if some other part also tries to call InitLogFiles
|
| +// apart from this file. But this is okay for now since this is the only
|
| +// place we call InitLogFiles.
|
| +namespace {
|
| +static bool g_log_files_initialized = false;
|
| +static base::AtExitManager g_at_exit_manager; // Necessary for NewCallback
|
| +} // empty namespace
|
| +
|
| +struct ThreadParams {
|
| + browser_sync::ServerConnectionManager* conn_mgr;
|
| +#if defined(OS_WINDOWS)
|
| + HANDLE exit_flag;
|
| +#endif
|
| +};
|
| +
|
| +// This thread calls CheckServerReachable() whenever a change occurs
|
| +// in the table that maps IP addresses to interfaces, for example when
|
| +// the user unplugs his network cable.
|
| +void* AddressWatchThread(void* arg) {
|
| + NameCurrentThreadForDebugging("SyncEngine_AddressWatcher");
|
| + LOG(INFO) << "starting the address watch thread";
|
| + const ThreadParams* const params = reinterpret_cast<const ThreadParams*>(arg);
|
| +#if defined(OS_WINDOWS)
|
| + OVERLAPPED overlapped = {0};
|
| + overlapped.hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
|
| + HANDLE file;
|
| + DWORD rc = WAIT_OBJECT_0;
|
| + while (true) {
|
| + // Only call NotifyAddrChange() after the IP address has changed or if this
|
| + // is the first time through the loop.
|
| + if (WAIT_OBJECT_0 == rc) {
|
| + ResetEvent(overlapped.hEvent);
|
| + DWORD notify_result = NotifyAddrChange(&file, &overlapped);
|
| + if (ERROR_IO_PENDING != notify_result) {
|
| + LOG(ERROR) << "NotifyAddrChange() returned unexpected result "
|
| + << hex << notify_result;
|
| + break;
|
| + }
|
| + }
|
| + HANDLE events[] = { overlapped.hEvent, params->exit_flag };
|
| + rc = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE,
|
| + kServerReachablePollingIntervalMsec);
|
| +
|
| + // If the exit flag was signaled, the thread will exit.
|
| + if (WAIT_OBJECT_0 + 1 == rc)
|
| + break;
|
| +
|
| + params->conn_mgr->CheckServerReachable();
|
| + }
|
| + CloseHandle(overlapped.hEvent);
|
| +#else
|
| + // TODO(zork): Add this functionality to Linux.
|
| +#endif
|
| + LOG(INFO) << "The address watch thread has stopped";
|
| + return 0;
|
| +}
|
| +
|
| +namespace sync_api {
|
| +class ModelSafeWorkerBridge;
|
| +
|
| +static const PSTR_CHAR kBookmarkSyncUserSettingsDatabase[] =
|
| + PSTR("BookmarkSyncSettings.sqlite3");
|
| +static const PSTR_CHAR kDefaultNameForNewNodes[] = PSTR(" ");
|
| +
|
| +// The list of names which are reserved for use by the server.
|
| +static const char16* kForbiddenServerNames[] =
|
| + { STRING16(""), STRING16("."), STRING16("..") };
|
| +
|
| +//////////////////////////////////////////////////////////////////////////
|
| +// Static helper functions.
|
| +
|
| +// Helper function to look up the int64 metahandle of an object given the ID
|
| +// string.
|
| +static int64 IdToMetahandle(syncable::BaseTransaction* trans,
|
| + const syncable::Id& id) {
|
| + syncable::Entry entry(trans, syncable::GET_BY_ID, id);
|
| + if (!entry.good())
|
| + return kInvalidId;
|
| + return entry.Get(syncable::META_HANDLE);
|
| +}
|
| +
|
| +// Checks whether |name| is a server-illegal name followed by zero or more space
|
| +// characters. The three server-illegal names are the empty string, dot, and
|
| +// dot-dot. Very long names (>255 bytes in UTF-8 Normalization Form C) are
|
| +// also illegal, but are not considered here.
|
| +static bool IsNameServerIllegalAfterTrimming(const string16& name) {
|
| + size_t untrimmed_count = name.find_last_not_of(' ') + 1;
|
| + for (int i = 0; i < arraysize(kForbiddenServerNames); ++i) {
|
| + if (name.compare(0, untrimmed_count, kForbiddenServerNames[i]) == 0)
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +static bool EndsWithSpace(const string16& string) {
|
| + return !string.empty() && *string.rbegin() == ' ';
|
| +}
|
| +
|
| +static inline void String16ToPathString(const sync_char16 *in,
|
| + PathString *out) {
|
| + string16 in_str(in);
|
| +#if defined(OS_WINDOWS)
|
| + out->assign(in_str);
|
| +#else
|
| + UTF16ToUTF8(in_str.c_str(), in_str.length(), out);
|
| +#endif
|
| +}
|
| +
|
| +static inline void PathStringToString16(const PathString& in, string16* out) {
|
| +#if defined(OS_WINDOWS)
|
| + out->assign(in);
|
| +#else
|
| + UTF8ToUTF16(in.c_str(), in.length(), out);
|
| +#endif
|
| +}
|
| +
|
| +// When taking a name from the syncapi, append a space if it matches the
|
| +// pattern of a server-illegal name followed by zero or more spaces.
|
| +static void SyncAPINameToServerName(const sync_char16 *sync_api_name,
|
| + PathString* out) {
|
| + String16ToPathString(sync_api_name, out);
|
| + string16 sync_api_name_str(sync_api_name);
|
| + if (IsNameServerIllegalAfterTrimming(sync_api_name_str))
|
| + out->append(PSTR(" "));
|
| +}
|
| +
|
| +// In the reverse direction, if a server name matches the pattern of a
|
| +// server-illegal name followed by one or more spaces, remove the trailing
|
| +// space.
|
| +static void ServerNameToSyncAPIName(const PathString& server_name,
|
| + string16*out) {
|
| + string16 server_name_str;
|
| + PathStringToString16(server_name, &server_name_str);
|
| + if (IsNameServerIllegalAfterTrimming(server_name_str) &&
|
| + EndsWithSpace(server_name_str))
|
| + out->assign(server_name_str, 0, server_name_str.size() - 1);
|
| + else
|
| + out->assign(server_name_str);
|
| +}
|
| +
|
| +// A UserShare encapsulates the syncable pieces that represent an authenticated
|
| +// user and their data (share).
|
| +// This encompasses all pieces required to build transaction objects on the
|
| +// syncable share.
|
| +struct UserShare {
|
| + // The DirectoryManager itself, which is the parent of Transactions and can
|
| + // be shared across multiple threads (unlike Directory).
|
| + scoped_ptr<DirectoryManager> dir_manager;
|
| +
|
| + // The username of the sync user. This is empty until we have performed at
|
| + // least one successful GAIA authentication with this username, which means
|
| + // on first-run it is empty until an AUTH_SUCCEEDED event and on future runs
|
| + // it is set as soon as the client instructs us to authenticate for the last
|
| + // known valid user (AuthenticateForLastKnownUser()).
|
| + // Stored as a PathString to avoid string conversions each time a transaction
|
| + // is created.
|
| + PathString authenticated_name;
|
| +};
|
| +
|
| +////////////////////////////////////
|
| +// BaseNode member definitions.
|
| +
|
| +// BaseNode::BaseNodeInternal provides storage for member Get() functions that
|
| +// need to return pointers (e.g. strings).
|
| +struct BaseNode::BaseNodeInternal {
|
| + string16 url;
|
| + string16 title;
|
| + Directory::ChildHandles child_handles;
|
| + syncable::Blob favicon;
|
| +};
|
| +
|
| +BaseNode::BaseNode() : data_(new BaseNode::BaseNodeInternal) {}
|
| +
|
| +BaseNode::~BaseNode() {
|
| + delete data_;
|
| +}
|
| +
|
| +int64 BaseNode::GetParentId() const {
|
| + return IdToMetahandle(GetTransaction()->GetWrappedTrans(),
|
| + GetEntry()->Get(syncable::PARENT_ID));
|
| +}
|
| +
|
| +int64 BaseNode::GetId() const {
|
| + return GetEntry()->Get(syncable::META_HANDLE);
|
| +}
|
| +
|
| +bool BaseNode::GetIsFolder() const {
|
| + return GetEntry()->Get(syncable::IS_DIR);
|
| +}
|
| +
|
| +const sync_char16* BaseNode::GetTitle() const {
|
| + // Store the string in data_ so that the returned pointer is valid.
|
| + ServerNameToSyncAPIName(GetEntry()->GetName().non_unique_value(),
|
| + &data_->title);
|
| + return data_->title.c_str();
|
| +}
|
| +
|
| +const sync_char16* BaseNode::GetURL() const {
|
| + // Store the string in data_ so that the returned pointer is valid.
|
| + PathStringToString16(GetEntry()->Get(syncable::BOOKMARK_URL), &data_->url);
|
| + return data_->url.c_str();
|
| +}
|
| +
|
| +const int64* BaseNode::GetChildIds(size_t* child_count) const {
|
| + DCHECK(child_count);
|
| + Directory* dir = GetTransaction()->GetLookup();
|
| + dir->GetChildHandles(GetTransaction()->GetWrappedTrans(),
|
| + GetEntry()->Get(syncable::ID), &data_->child_handles);
|
| +
|
| + *child_count = data_->child_handles.size();
|
| + return (data_->child_handles.empty()) ? NULL : &data_->child_handles[0];
|
| +}
|
| +
|
| +int64 BaseNode::GetPredecessorId() const {
|
| + syncable::Id id_string = GetEntry()->Get(syncable::PREV_ID);
|
| + if (id_string.IsRoot())
|
| + return kInvalidId;
|
| + return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
|
| +}
|
| +
|
| +int64 BaseNode::GetSuccessorId() const {
|
| + syncable::Id id_string = GetEntry()->Get(syncable::NEXT_ID);
|
| + if (id_string.IsRoot())
|
| + return kInvalidId;
|
| + return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
|
| +}
|
| +
|
| +int64 BaseNode::GetFirstChildId() const {
|
| + syncable::Directory* dir = GetTransaction()->GetLookup();
|
| + syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans();
|
| + syncable::Id id_string =
|
| + dir->GetFirstChildId(trans, GetEntry()->Get(syncable::ID));
|
| + if (id_string.IsRoot())
|
| + return kInvalidId;
|
| + return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string);
|
| +}
|
| +
|
| +const unsigned char* BaseNode::GetFaviconBytes(size_t* size_in_bytes) {
|
| + data_->favicon = GetEntry()->Get(syncable::BOOKMARK_FAVICON);
|
| + *size_in_bytes = data_->favicon.size();
|
| + if (*size_in_bytes)
|
| + return &(data_->favicon[0]);
|
| + else
|
| + return NULL;
|
| +}
|
| +
|
| +int64 BaseNode::GetExternalId() const {
|
| + return GetEntry()->Get(syncable::LOCAL_EXTERNAL_ID);
|
| +}
|
| +
|
| +////////////////////////////////////
|
| +// WriteNode member definitions
|
| +void WriteNode::SetIsFolder(bool folder) {
|
| + if (entry_->Get(syncable::IS_DIR) == folder)
|
| + return; // Skip redundant changes.
|
| +
|
| + entry_->Put(syncable::IS_DIR, folder);
|
| + MarkForSyncing();
|
| +}
|
| +
|
| +void WriteNode::SetTitle(const sync_char16* title) {
|
| + PathString server_legal_name;
|
| + SyncAPINameToServerName(title, &server_legal_name);
|
| + syncable::SyncName sync_name(server_legal_name);
|
| + syncable::DBName db_name(sync_name.value());
|
| + db_name.MakeOSLegal();
|
| + db_name.MakeNoncollidingForEntry(transaction_->GetWrappedTrans(),
|
| + entry_->Get(syncable::PARENT_ID), entry_);
|
| +
|
| + syncable::Name new_name = syncable::Name::FromDBNameAndSyncName(db_name,
|
| + sync_name);
|
| + if (new_name == entry_->GetName())
|
| + return; // Skip redundant changes.
|
| +
|
| + entry_->PutName(new_name);
|
| + MarkForSyncing();
|
| +}
|
| +
|
| +void WriteNode::SetURL(const sync_char16* url) {
|
| + PathString url_string;
|
| + String16ToPathString(url, &url_string);
|
| + if (url_string == entry_->Get(syncable::BOOKMARK_URL))
|
| + return; // Skip redundant changes.
|
| +
|
| + entry_->Put(syncable::BOOKMARK_URL, url_string);
|
| + MarkForSyncing();
|
| +}
|
| +
|
| +void WriteNode::SetExternalId(int64 id) {
|
| + if (GetExternalId() != id)
|
| + entry_->Put(syncable::LOCAL_EXTERNAL_ID, id);
|
| +}
|
| +
|
| +WriteNode::WriteNode(WriteTransaction* transaction)
|
| + : entry_(NULL), transaction_(transaction) {
|
| + DCHECK(transaction);
|
| +}
|
| +
|
| +WriteNode::~WriteNode() {
|
| + delete entry_;
|
| +}
|
| +
|
| +// Find an existing node matching the ID |id|, and bind this WriteNode
|
| +// to it. Return true on success.
|
| +bool WriteNode::InitByIdLookup(int64 id) {
|
| + DCHECK(!entry_) << "Init called twice";
|
| + DCHECK_NE(id, kInvalidId);
|
| + entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
|
| + syncable::GET_BY_HANDLE, id);
|
| + return (entry_->good() && !entry_->Get(syncable::IS_DEL));
|
| +}
|
| +
|
| +// Create a new node with default properties, and bind this WriteNode to it.
|
| +// Return true on success.
|
| +bool WriteNode::InitByCreation(const BaseNode& parent,
|
| + const BaseNode* predecessor) {
|
| + DCHECK(!entry_) << "Init called twice";
|
| + // |predecessor| must be a child of |parent| or NULL.
|
| + if (predecessor && predecessor->GetParentId() != parent.GetId()) {
|
| + DCHECK(false);
|
| + return false;
|
| + }
|
| +
|
| + syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);
|
| +
|
| + // Start out with a dummy name, but make it unique. We expect
|
| + // the caller to set a meaningful name after creation.
|
| + syncable::DBName dummy(kDefaultNameForNewNodes);
|
| + dummy.MakeOSLegal();
|
| + dummy.MakeNoncollidingForEntry(transaction_->GetWrappedTrans(), parent_id,
|
| + NULL);
|
| +
|
| + entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
|
| + syncable::CREATE, parent_id, dummy);
|
| +
|
| + if (!entry_->good())
|
| + return false;
|
| +
|
| + // Entries are untitled folders by default.
|
| + entry_->Put(syncable::IS_DIR, true);
|
| + // TODO(ncarter): Naming this bit IS_BOOKMARK_OBJECT is a bit unfortunate,
|
| + // since the rest of SyncAPI is essentially bookmark-agnostic.
|
| + entry_->Put(syncable::IS_BOOKMARK_OBJECT, true);
|
| +
|
| + // Now set the predecessor, which sets IS_UNSYNCED as necessary.
|
| + PutPredecessor(predecessor);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +bool WriteNode::SetPosition(const BaseNode& new_parent,
|
| + const BaseNode* predecessor) {
|
| + // |predecessor| must be a child of |new_parent| or NULL.
|
| + if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
|
| + DCHECK(false);
|
| + return false;
|
| + }
|
| +
|
| + syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID);
|
| +
|
| + // Filter out redundant changes if both the parent and the predecessor match.
|
| + if (new_parent_id == entry_->Get(syncable::PARENT_ID)) {
|
| + const syncable::Id& old = entry_->Get(syncable::PREV_ID);
|
| + if ((!predecessor && old.IsRoot()) ||
|
| + (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) {
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + // Discard the old database name, derive a new database name from the sync
|
| + // name, and make it legal and unique.
|
| + syncable::Name name = syncable::Name::FromSyncName(GetEntry()->GetName());
|
| + name.db_value().MakeOSLegal();
|
| + name.db_value().MakeNoncollidingForEntry(GetTransaction()->GetWrappedTrans(),
|
| + new_parent_id, entry_);
|
| +
|
| + // Atomically change the parent and name. This will fail if it would
|
| + // introduce a cycle in the hierarchy.
|
| + if (!entry_->PutParentIdAndName(new_parent_id, name))
|
| + return false;
|
| +
|
| + // Now set the predecessor, which sets IS_UNSYNCED as necessary.
|
| + PutPredecessor(predecessor);
|
| +
|
| + return true;
|
| +}
|
| +
|
| +const syncable::Entry* WriteNode::GetEntry() const {
|
| + return entry_;
|
| +}
|
| +
|
| +const BaseTransaction* WriteNode::GetTransaction() const {
|
| + return transaction_;
|
| +}
|
| +
|
| +void WriteNode::Remove() {
|
| + entry_->Put(syncable::IS_DEL, true);
|
| + MarkForSyncing();
|
| +}
|
| +
|
| +void WriteNode::PutPredecessor(const BaseNode* predecessor) {
|
| + syncable::Id predecessor_id = predecessor ?
|
| + predecessor->GetEntry()->Get(syncable::ID) : syncable::Id();
|
| + entry_->PutPredecessor(predecessor_id);
|
| + // Mark this entry as unsynced, to wake up the syncer.
|
| + MarkForSyncing();
|
| +}
|
| +
|
| +void WriteNode::SetFaviconBytes(const unsigned char* bytes,
|
| + size_t size_in_bytes) {
|
| + syncable::Blob new_favicon(bytes, bytes + size_in_bytes);
|
| + if (new_favicon == entry_->Get(syncable::BOOKMARK_FAVICON))
|
| + return; // Skip redundant changes.
|
| +
|
| + entry_->Put(syncable::BOOKMARK_FAVICON, new_favicon);
|
| + MarkForSyncing();
|
| +}
|
| +
|
| +void WriteNode::MarkForSyncing() {
|
| + syncable::MarkForSyncing(entry_);
|
| +}
|
| +
|
| +//////////////////////////////////////////////////////////////////////////
|
| +// ReadNode member definitions
|
| +ReadNode::ReadNode(const BaseTransaction* transaction)
|
| + : entry_(NULL), transaction_(transaction) {
|
| + DCHECK(transaction);
|
| +}
|
| +
|
| +ReadNode::~ReadNode() {
|
| + delete entry_;
|
| +}
|
| +
|
| +void ReadNode::InitByRootLookup() {
|
| + DCHECK(!entry_) << "Init called twice";
|
| + syncable::BaseTransaction* trans = transaction_->GetWrappedTrans();
|
| + entry_ = new syncable::Entry(trans, syncable::GET_BY_ID, trans->root_id());
|
| + if (!entry_->good())
|
| + DCHECK(false) << "Could not lookup root node for reading.";
|
| +}
|
| +
|
| +bool ReadNode::InitByIdLookup(int64 id) {
|
| + DCHECK(!entry_) << "Init called twice";
|
| + DCHECK_NE(id, kInvalidId);
|
| + syncable::BaseTransaction* trans = transaction_->GetWrappedTrans();
|
| + entry_ = new syncable::Entry(trans, syncable::GET_BY_HANDLE, id);
|
| + if (!entry_->good())
|
| + return false;
|
| + if (entry_->Get(syncable::IS_DEL))
|
| + return false;
|
| + LOG_IF(WARNING, !entry_->Get(syncable::IS_BOOKMARK_OBJECT))
|
| + << "SyncAPI InitByIdLookup referencing non-bookmark object.";
|
| + return true;
|
| +}
|
| +
|
| +const syncable::Entry* ReadNode::GetEntry() const {
|
| + return entry_;
|
| +}
|
| +
|
| +const BaseTransaction* ReadNode::GetTransaction() const {
|
| + return transaction_;
|
| +}
|
| +
|
| +bool ReadNode::InitByTagLookup(const sync_char16* tag) {
|
| + DCHECK(!entry_) << "Init called twice";
|
| + PathString tag_string;
|
| + String16ToPathString(tag, &tag_string);
|
| + if (tag_string.empty())
|
| + return false;
|
| + syncable::BaseTransaction* trans = transaction_->GetWrappedTrans();
|
| + entry_ = new syncable::Entry(trans, syncable::GET_BY_TAG, tag_string);
|
| + if (!entry_->good())
|
| + return false;
|
| + if (entry_->Get(syncable::IS_DEL))
|
| + return false;
|
| + LOG_IF(WARNING, !entry_->Get(syncable::IS_BOOKMARK_OBJECT))
|
| + << "SyncAPI InitByTagLookup referencing non-bookmark object.";
|
| + return true;
|
| +}
|
| +
|
| +
|
| +//////////////////////////////////////////////////////////////////////////
|
| +// ReadTransaction member definitions
|
| +ReadTransaction::ReadTransaction(UserShare* share)
|
| + : BaseTransaction(share),
|
| + transaction_(NULL) {
|
| + transaction_ = new syncable::ReadTransaction(GetLookup(), __FILE__, __LINE__);
|
| +}
|
| +
|
| +ReadTransaction::~ReadTransaction() {
|
| + delete transaction_;
|
| +}
|
| +
|
| +syncable::BaseTransaction* ReadTransaction::GetWrappedTrans() const {
|
| + return transaction_;
|
| +}
|
| +
|
| +//////////////////////////////////////////////////////////////////////////
|
| +// WriteTransaction member definitions
|
| +WriteTransaction::WriteTransaction(UserShare* share)
|
| + : BaseTransaction(share),
|
| + transaction_(NULL) {
|
| + transaction_ = new syncable::WriteTransaction(GetLookup(), syncable::SYNCAPI,
|
| + __FILE__, __LINE__);
|
| +}
|
| +
|
| +WriteTransaction::~WriteTransaction() {
|
| + delete transaction_;
|
| +}
|
| +
|
| +syncable::BaseTransaction* WriteTransaction::GetWrappedTrans() const {
|
| + return transaction_;
|
| +}
|
| +
|
| +// An implementation of Visitor that we use to "visit" the
|
| +// ModelSafeWorkerInterface provided by a client of this API. The object we
|
| +// visit is responsible for calling DoWork, which will invoke Run() on it's
|
| +// cached work closure.
|
| +class ModelSafeWorkerVisitor : public ModelSafeWorkerInterface::Visitor {
|
| + public:
|
| + explicit ModelSafeWorkerVisitor(Closure* work) : work_(work) { }
|
| + virtual ~ModelSafeWorkerVisitor() { }
|
| +
|
| + // ModelSafeWorkerInterface::Visitor implementation.
|
| + virtual void DoWork() {
|
| + work_->Run();
|
| + }
|
| +
|
| + private:
|
| + // The work to be done. We run this on DoWork and it cleans itself up
|
| + // after it is run.
|
| + Closure* work_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ModelSafeWorkerVisitor);
|
| +};
|
| +
|
| +// This class is declared in the cc file to allow inheritance from sync types.
|
| +// The ModelSafeWorkerBridge is a liason between a syncapi-client defined
|
| +// ModelSafeWorkerInterface and the actual ModelSafeWorker used by the Syncer
|
| +// for the current SyncManager.
|
| +class ModelSafeWorkerBridge : public browser_sync::ModelSafeWorker {
|
| + public:
|
| + // Takes ownership of |worker|.
|
| + explicit ModelSafeWorkerBridge(ModelSafeWorkerInterface* worker)
|
| + : worker_(worker) {
|
| + }
|
| + virtual ~ModelSafeWorkerBridge() { }
|
| +
|
| + // Overriding ModelSafeWorker.
|
| + virtual void DoWorkAndWaitUntilDone(Closure* work) {
|
| + // When the syncer has work to be done, we forward it to our worker who
|
| + // will invoke DoWork on |visitor| when appropriate (from model safe
|
| + // thread).
|
| + ModelSafeWorkerVisitor visitor(work);
|
| + worker_->CallDoWorkFromModelSafeThreadAndWait(&visitor);
|
| + }
|
| +
|
| + private:
|
| + // The worker that we can forward work requests to, to ensure the work
|
| + // is performed on an appropriate model safe thread.
|
| + scoped_ptr<ModelSafeWorkerInterface> worker_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ModelSafeWorkerBridge);
|
| +};
|
| +
|
| +// A GaiaAuthenticator that uses HttpPostProviders instead of CURL.
|
| +class BridgedGaiaAuthenticator : public browser_sync::GaiaAuthenticator {
|
| + public:
|
| + BridgedGaiaAuthenticator(const string& user_agent, const string& service_id,
|
| + const string& gaia_url,
|
| + HttpPostProviderFactory* factory)
|
| + : GaiaAuthenticator(user_agent, service_id, gaia_url),
|
| + gaia_source_(user_agent), post_factory_(factory) {
|
| + }
|
| +
|
| + virtual ~BridgedGaiaAuthenticator() {
|
| + }
|
| +
|
| + virtual bool Post(const GURL& url, const string& post_body,
|
| + unsigned long* response_code, string* response_body) {
|
| + string connection_url = "https://";
|
| + connection_url += url.host();
|
| + connection_url += url.path();
|
| + HttpPostProviderInterface* http = post_factory_->Create();
|
| + http->SetUserAgent(gaia_source_.c_str());
|
| + // SSL is on 443 for Gaia Posts always.
|
| + http->SetURL(connection_url.c_str(), kSSLPort);
|
| + http->SetPostPayload("application/x-www-form-urlencoded",
|
| + post_body.length(), post_body.c_str());
|
| +
|
| + int os_error_code = 0;
|
| + int int_response_code = 0;
|
| + if (!http->MakeSynchronousPost(&os_error_code, &int_response_code)) {
|
| + LOG(INFO) << "Http POST failed, error returns: " << os_error_code;
|
| + return false;
|
| + }
|
| + *response_code = static_cast<int>(int_response_code);
|
| + response_body->assign(http->GetResponseContent(),
|
| + http->GetResponseContentLength());
|
| + post_factory_->Destroy(http);
|
| + return true;
|
| + }
|
| + private:
|
| + const std::string gaia_source_;
|
| + scoped_ptr<HttpPostProviderFactory> post_factory_;
|
| + DISALLOW_COPY_AND_ASSIGN(BridgedGaiaAuthenticator);
|
| +};
|
| +
|
| +//////////////////////////////////////////////////////////////////////////
|
| +// SyncManager's implementation: SyncManager::SyncInternal
|
| +class SyncManager::SyncInternal {
|
| + public:
|
| + typedef PThreadScopedLock<PThreadMutex> MutexLock;
|
| + explicit SyncInternal(SyncManager* sync_manager)
|
| + : observer_(NULL),
|
| + command_channel_(0),
|
| + auth_problem_(AUTH_PROBLEM_NONE),
|
| + sync_manager_(sync_manager),
|
| + notification_pending_(false),
|
| + initialized_(false) {
|
| + }
|
| +
|
| + ~SyncInternal() { }
|
| +
|
| + bool Init(const PathString& database_location,
|
| + const std::string& sync_server_and_path,
|
| + int port,
|
| + const char* gaia_service_id,
|
| + const char* gaia_source,
|
| + bool use_ssl,
|
| + HttpPostProviderFactory* post_factory,
|
| + HttpPostProviderFactory* auth_post_factory,
|
| + ModelSafeWorkerInterface* model_safe_worker,
|
| + bool attempt_last_user_authentication,
|
| + const char* user_agent);
|
| +
|
| + // Tell sync engine to submit credentials to GAIA for verification and start
|
| + // the syncing process on success. Successful GAIA authentication will kick
|
| + // off the following chain of events:
|
| + // 1. Cause sync engine to open the syncer database.
|
| + // 2. Trigger the AuthWatcher to create a Syncer for the directory and call
|
| + // SyncerThread::SyncDirectory; the SyncerThread will block until (4).
|
| + // 3. Tell the ServerConnectionManager to pass the newly received GAIA auth
|
| + // token to a sync server to obtain a sync token.
|
| + // 4. On receipt of this token, the ServerConnectionManager broadcasts
|
| + // a server-reachable event, which will unblock the SyncerThread,
|
| + // and the rest is the future.
|
| + //
|
| + // If authentication fails, an event will be broadcast all the way up to
|
| + // the SyncManager::Observer. It may, in turn, decide to try again with new
|
| + // credentials. Calling this method again is the appropriate course of action
|
| + // to "retry".
|
| + void Authenticate(const std::string& username, const std::string& password);
|
| +
|
| + // Call periodically from a database-safe thread to persist recent changes
|
| + // to the syncapi model.
|
| + void SaveChanges();
|
| +
|
| + // This listener is called upon completion of a syncable transaction, and
|
| + // builds the list of sync-engine initiated changes that will be forwarded to
|
| + // the SyncManager's Observers.
|
| + void HandleChangeEvent(const syncable::DirectoryChangeEvent& event);
|
| + void HandleTransactionCompleteChangeEvent(
|
| + const syncable::DirectoryChangeEvent& event);
|
| + void HandleCalculateChangesChangeEventFromSyncApi(
|
| + const syncable::DirectoryChangeEvent& event);
|
| + void HandleCalculateChangesChangeEventFromSyncer(
|
| + const syncable::DirectoryChangeEvent& event);
|
| +
|
| + // This listener is called by the syncer channel for all syncer events.
|
| + void HandleSyncerEvent(const SyncerEvent& event);
|
| +
|
| + // We have a direct hookup to the authwatcher to be notified for auth failures
|
| + // on startup, to serve our UI needs.
|
| + void HandleAuthWatcherEvent(const AuthWatcherEvent& event);
|
| +
|
| + // Accessors for the private members.
|
| + DirectoryManager* dir_manager() { return share_.dir_manager.get(); }
|
| + SyncAPIServerConnectionManager* connection_manager() {
|
| + return connection_manager_.get();
|
| + }
|
| + SyncerThread* syncer_thread() { return syncer_thread_.get(); }
|
| + TalkMediator* talk_mediator() { return talk_mediator_.get(); }
|
| + AuthWatcher* auth_watcher() { return auth_watcher_.get(); }
|
| + AllStatus* allstatus() { return &allstatus_; }
|
| + void set_observer(Observer* observer) { observer_ = observer; }
|
| + UserShare* GetUserShare() { return &share_; }
|
| +
|
| + // Return the currently active (validated) username as a PathString for
|
| + // use with syncable types.
|
| + const PathString& username_for_share() const {
|
| + return share_.authenticated_name;
|
| + }
|
| +
|
| + // Returns the authenticated username from our AuthWatcher in UTF8.
|
| + // See SyncManager::GetAuthenticatedUsername for details.
|
| + const char* GetAuthenticatedUsername();
|
| +
|
| + // Note about SyncManager::Status implementation: Status is a trimmed
|
| + // down AllStatus::Status, augmented with authentication failure information
|
| + // gathered from the internal AuthWatcher. The sync UI itself hooks up to
|
| + // various sources like the AuthWatcher individually, but with syncapi we try
|
| + // to keep everything status-related in one place. This means we have to
|
| + // privately manage state about authentication failures, and whenever the
|
| + // status or status summary is requested we aggregate this state with
|
| + // AllStatus::Status information.
|
| + Status ComputeAggregatedStatus();
|
| + Status::Summary ComputeAggregatedStatusSummary();
|
| +
|
| + // See SyncManager::SetupForTestMode for information.
|
| + void SetupForTestMode(const sync_char16* test_username);
|
| +
|
| + // See SyncManager::Shutdown for information.
|
| + void Shutdown();
|
| +
|
| + // Whether we're initialized to the point of being able to accept changes
|
| + // (and hence allow transaction creation). See initialized_ for details.
|
| + bool initialized() const {
|
| + MutexLock lock(&initialized_mutex_);
|
| + return initialized_;
|
| + }
|
| + private:
|
| + // Try to authenticate using persisted credentials from a previous successful
|
| + // authentication. If no such credentials exist, calls OnAuthError on
|
| + // the client to collect credentials. Otherwise, there exist local
|
| + // credentials that were once used for a successful auth, so we'll try to
|
| + // re-use these.
|
| + // Failure of that attempt will be communicated as normal using
|
| + // OnAuthError. Since this entry point will bypass normal GAIA
|
| + // authentication and try to authenticate directly with the sync service
|
| + // using a cached token, authentication failure will generally occur due to
|
| + // expired credentials, or possibly because of a password change.
|
| + void AuthenticateForLastKnownUser();
|
| +
|
| + // Helper to call OnAuthError when no authentication credentials
|
| + // are available.
|
| + void RaiseAuthNeededEvent();
|
| +
|
| + // Helper to set initialized_ to true and raise an event to clients to
|
| + // notify that initialization is complete and it is safe to send us changes.
|
| + // If already initialized, this is a no-op.
|
| + void MarkAndNotifyInitializationComplete();
|
| +
|
| + // Determine if the parents or predecessors differ between the old and new
|
| + // versions of an entry stored in |a| and |b|. Note that a node's index
|
| + // may change without its NEXT_ID changing if the node at NEXT_ID also
|
| + // moved (but the relative order is unchanged). To handle such cases,
|
| + // we rely on the caller to treat a position update on any sibling as
|
| + // updating the positions of all siblings.
|
| + static bool BookmarkPositionsDiffer(const syncable::EntryKernel& a,
|
| + const syncable::Entry& b) {
|
| + if (a.ref(syncable::NEXT_ID) != b.Get(syncable::NEXT_ID))
|
| + return true;
|
| + if (a.ref(syncable::PARENT_ID) != b.Get(syncable::PARENT_ID))
|
| + return true;
|
| + return false;
|
| + }
|
| +
|
| + // Determine if any of the fields made visible to clients of the Sync API
|
| + // differ between the versions of an entry stored in |a| and |b|.
|
| + // A return value of false means that it should be OK to ignore this change.
|
| + static bool BookmarkPropertiesDiffer(const syncable::EntryKernel& a,
|
| + const syncable::Entry& b) {
|
| + if (a.ref(syncable::NAME) != b.Get(syncable::NAME))
|
| + return true;
|
| + if (a.ref(syncable::UNSANITIZED_NAME) != b.Get(syncable::UNSANITIZED_NAME))
|
| + return true;
|
| + if (a.ref(syncable::IS_DIR) != b.Get(syncable::IS_DIR))
|
| + return true;
|
| + if (a.ref(syncable::BOOKMARK_URL) != b.Get(syncable::BOOKMARK_URL))
|
| + return true;
|
| + if (a.ref(syncable::BOOKMARK_FAVICON) != b.Get(syncable::BOOKMARK_FAVICON))
|
| + return true;
|
| + if (BookmarkPositionsDiffer(a, b))
|
| + return true;
|
| + return false;
|
| + }
|
| +
|
| + // We couple the DirectoryManager and username together in a UserShare member
|
| + // so we can return a handle to share_ to clients of the API for use when
|
| + // constructing any transaction type.
|
| + UserShare share_;
|
| +
|
| + // A cached string for callers of GetAuthenticatedUsername. We just store the
|
| + // last result of auth_watcher_->email() here and change it on future calls,
|
| + // because callers of GetAuthenticatedUsername are supposed to copy the value
|
| + // if they need it for longer than the scope of the call.
|
| + std::string cached_auth_watcher_email_;
|
| +
|
| + // A wrapper around a sqlite store used for caching authentication data,
|
| + // last user information, current sync-related URLs, and more.
|
| + scoped_ptr<UserSettings> user_settings_;
|
| +
|
| + // Observer registered via SetObserver/RemoveObserver.
|
| + // WARNING: This can be NULL!
|
| + Observer* observer_;
|
| +
|
| + // A sink for client commands from the syncer needed to create a SyncerThread.
|
| + ClientCommandChannel command_channel_;
|
| +
|
| + // The ServerConnectionManager used to abstract communication between
|
| + // the client (the Syncer) and the sync server.
|
| + scoped_ptr<SyncAPIServerConnectionManager> connection_manager_;
|
| +
|
| + // The thread that runs the Syncer. Needs to be explicitly Start()ed.
|
| + scoped_ptr<SyncerThread> syncer_thread_;
|
| +
|
| + // Notification (xmpp) handler.
|
| + scoped_ptr<TalkMediator> talk_mediator_;
|
| +
|
| + // A multi-purpose status watch object that aggregates stats from various
|
| + // sync components.
|
| + AllStatus allstatus_;
|
| +
|
| + // AuthWatcher kicks off the authentication process and follows it through
|
| + // phase 1 (GAIA) to phase 2 (sync engine). As part of this work it determines
|
| + // the initial connectivity and causes the server connection event to be
|
| + // broadcast, which signals the syncer thread to start syncing.
|
| + // It has a heavy duty constructor requiring boilerplate so we heap allocate.
|
| + scoped_ptr<AuthWatcher> auth_watcher_;
|
| +
|
| + // A store of change records produced by HandleChangeEvent during the
|
| + // CALCULATE_CHANGES step, and to be processed, and forwarded to the
|
| + // observer, by HandleChangeEvent during the TRANSACTION_COMPLETE step.
|
| + ChangeReorderBuffer change_buffer_;
|
| +
|
| + // The event listener hookup that is registered for HandleChangeEvent.
|
| + scoped_ptr<EventListenerHookup> dir_change_hookup_;
|
| +
|
| + // The event listener hookup registered for HandleSyncerEvent.
|
| + scoped_ptr<EventListenerHookup> syncer_event_;
|
| +
|
| + // The event listener hookup registered for HandleAuthWatcherEvent.
|
| + scoped_ptr<EventListenerHookup> authwatcher_hookup_;
|
| +
|
| + // Our cache of a recent authentication problem. If no authentication problem
|
| + // occurred, or if the last problem encountered has been cleared (by a
|
| + // subsequent AuthWatcherEvent), this is set to AUTH_PROBLEM_NONE.
|
| + AuthProblem auth_problem_;
|
| +
|
| + // The sync dir_manager to which we belong.
|
| + SyncManager* const sync_manager_;
|
| +
|
| + // Parameters for our thread listening to network status changes.
|
| + ThreadParams address_watch_params_;
|
| + thread_handle address_watch_thread_;
|
| +
|
| + // True if the next SyncCycle should notify peers of an update.
|
| + bool notification_pending_;
|
| +
|
| + // Set to true once Init has been called, and we know of an authenticated
|
| + // valid) username either from a fresh authentication attempt (as in
|
| + // first-use case) or from a previous attempt stored in our UserSettings
|
| + // (as in the steady-state), and the syncable::Directory has been opened,
|
| + // meaning we are ready to accept changes. Protected by initialized_mutex_
|
| + // as it can get read/set by both the SyncerThread and the AuthWatcherThread.
|
| + bool initialized_;
|
| + mutable PThreadMutex initialized_mutex_;
|
| +};
|
| +
|
| +SyncManager::SyncManager() {
|
| + data_ = new SyncInternal(this);
|
| +}
|
| +
|
| +bool SyncManager::Init(const sync_char16* database_location,
|
| + const char* sync_server_and_path,
|
| + int sync_server_port,
|
| + const char* gaia_service_id,
|
| + const char* gaia_source,
|
| + bool use_ssl,
|
| + HttpPostProviderFactory* post_factory,
|
| + HttpPostProviderFactory* auth_post_factory,
|
| + ModelSafeWorkerInterface* model_safe_worker,
|
| + bool attempt_last_user_authentication,
|
| + const char* user_agent) {
|
| + DCHECK(database_location);
|
| + DCHECK(post_factory);
|
| +
|
| + PathString db_path;
|
| + String16ToPathString(database_location, &db_path);
|
| + string server_string(sync_server_and_path);
|
| + return data_->Init(db_path,
|
| + server_string,
|
| + sync_server_port,
|
| + gaia_service_id,
|
| + gaia_source,
|
| + use_ssl,
|
| + post_factory,
|
| + auth_post_factory,
|
| + model_safe_worker,
|
| + attempt_last_user_authentication,
|
| + user_agent);
|
| +}
|
| +
|
| +void SyncManager::Authenticate(const char* username, const char* password) {
|
| + data_->Authenticate(std::string(username), std::string(password));
|
| +}
|
| +
|
| +const char* SyncManager::GetAuthenticatedUsername() {
|
| + if (!data_)
|
| + return NULL;
|
| + return data_->GetAuthenticatedUsername();
|
| +}
|
| +
|
| +const char* SyncManager::SyncInternal::GetAuthenticatedUsername() {
|
| + cached_auth_watcher_email_ = browser_sync::ToUTF8(
|
| + username_for_share()).get_string();
|
| + return cached_auth_watcher_email_.c_str();
|
| +}
|
| +
|
| +bool SyncManager::SyncInternal::Init(
|
| + const PathString& database_location,
|
| + const std::string& sync_server_and_path,
|
| + int port,
|
| + const char* gaia_service_id,
|
| + const char* gaia_source,
|
| + bool use_ssl, HttpPostProviderFactory* post_factory,
|
| + HttpPostProviderFactory* auth_post_factory,
|
| + ModelSafeWorkerInterface* model_safe_worker,
|
| + bool attempt_last_user_authentication,
|
| + const char* user_agent) {
|
| +
|
| + if (!g_log_files_initialized) {
|
| + // TODO(timsteele): Call InitLogFiles() or equivalent.
|
| + g_log_files_initialized = true;
|
| + }
|
| +
|
| + // Set up UserSettings, creating the db if necessary. We need this to
|
| + // instantiate a URLFactory to give to the Syncer.
|
| + PathString settings_db_file = AppendSlash(database_location) +
|
| + kBookmarkSyncUserSettingsDatabase;
|
| + user_settings_.reset(new UserSettings());
|
| + if (!user_settings_->Init(settings_db_file))
|
| + return false;
|
| +
|
| + share_.dir_manager.reset(new DirectoryManager(database_location));
|
| +
|
| + string client_id = user_settings_->GetClientId();
|
| + connection_manager_.reset(new SyncAPIServerConnectionManager(
|
| + sync_server_and_path, port, use_ssl, user_agent, client_id));
|
| +
|
| + // TODO(timsteele): This is temporary windows crap needed to listen for
|
| + // network status changes. We should either pump this up to the embedder to
|
| + // do (and call us in CheckServerReachable, for ex), or at least make this
|
| + // platform independent in here.
|
| + // TODO(ncarter): When this gets cleaned up, the implementation of
|
| + // CreatePThread can also be removed.
|
| +#if defined(OS_WINDOWS)
|
| + HANDLE exit_flag = CreateEvent(NULL, TRUE /*manual reset*/, FALSE, NULL);
|
| + address_watch_params_.exit_flag = exit_flag;
|
| +#endif
|
| + address_watch_params_.conn_mgr = connection_manager();
|
| + address_watch_thread_ = CreatePThread(AddressWatchThread,
|
| + &address_watch_params_);
|
| + DCHECK(NULL != address_watch_thread_);
|
| +
|
| + // Hand over the bridged POST factory to be owned by the connection
|
| + // dir_manager.
|
| + connection_manager()->SetHttpPostProviderFactory(post_factory);
|
| +
|
| + // Watch various objects for aggregated status.
|
| + allstatus()->WatchConnectionManager(connection_manager());
|
| +
|
| + std::string gaia_url = browser_sync::kGaiaUrl;
|
| + const char* service_id = gaia_service_id ?
|
| + gaia_service_id : SYNC_SERVICE_NAME;
|
| +
|
| + talk_mediator_.reset(new TalkMediatorImpl());
|
| + allstatus()->WatchTalkMediator(talk_mediator());
|
| +
|
| + BridgedGaiaAuthenticator* gaia_auth = new BridgedGaiaAuthenticator(
|
| + gaia_source, service_id, gaia_url, auth_post_factory);
|
| +
|
| + auth_watcher_.reset(new AuthWatcher(dir_manager(),
|
| + connection_manager(),
|
| + &allstatus_,
|
| + gaia_source,
|
| + service_id,
|
| + gaia_url,
|
| + user_settings_.get(),
|
| + gaia_auth,
|
| + talk_mediator()));
|
| +
|
| + talk_mediator()->WatchAuthWatcher(auth_watcher());
|
| + allstatus()->WatchAuthWatcher(auth_watcher());
|
| + authwatcher_hookup_.reset(NewEventListenerHookup(auth_watcher_->channel(),
|
| + this, &SyncInternal::HandleAuthWatcherEvent));
|
| +
|
| + // Tell the SyncerThread to use the ModelSafeWorker for bookmark model work.
|
| + // We set up both sides of the "bridge" here, with the ModelSafeWorkerBridge
|
| + // on the Syncer side, and |model_safe_worker| on the API client side.
|
| + ModelSafeWorkerBridge* worker = new ModelSafeWorkerBridge(model_safe_worker);
|
| +
|
| + syncer_thread_.reset(new SyncerThread(&command_channel_,
|
| + dir_manager(),
|
| + connection_manager(),
|
| + &allstatus_,
|
| + worker));
|
| + syncer_thread()->WatchTalkMediator(talk_mediator());
|
| + allstatus()->WatchSyncerThread(syncer_thread());
|
| +
|
| + syncer_thread()->Start(); // Start the syncer thread. This won't actually
|
| + // result in any syncing until at least the
|
| + // DirectoryManager broadcasts the OPENED event,
|
| + // and a valid server connection is detected.
|
| +
|
| + if (attempt_last_user_authentication)
|
| + AuthenticateForLastKnownUser();
|
| + return true;
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::MarkAndNotifyInitializationComplete() {
|
| + // There is only one real time we need this mutex. If we get an auth
|
| + // success, and before the initial sync ends we get an auth failure. In this
|
| + // case we'll be listening to both the AuthWatcher and Syncer, and it's a race
|
| + // between their respective threads to call MarkAndNotify. We need to make
|
| + // sure the observer is notified once and only once.
|
| + {
|
| + MutexLock lock(&initialized_mutex_);
|
| + if (initialized_)
|
| + return;
|
| + initialized_ = true;
|
| + }
|
| +
|
| + // Notify that initialization is complete.
|
| + if (observer_)
|
| + observer_->OnInitializationComplete();
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::Authenticate(const std::string& username,
|
| + const std::string& password) {
|
| + DCHECK(username_for_share().empty() ||
|
| + (username == browser_sync::ToUTF8(username_for_share()).get_string()))
|
| + << "Username change from valid username detected";
|
| + if (allstatus()->status().authenticated)
|
| + return;
|
| + if (password.empty()) {
|
| + // TODO(timsteele): Seems like this shouldn't be needed, but auth_watcher
|
| + // currently drops blank password attempts on the floor and doesn't update
|
| + // state; it only LOGs an error in this case. We want to make sure we set
|
| + // our AuthProblem state to denote an error.
|
| + RaiseAuthNeededEvent();
|
| + }
|
| + auth_watcher()->Authenticate(username, password, true);
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::AuthenticateForLastKnownUser() {
|
| + std::string username;
|
| + std::string auth_token;
|
| + if (!(auth_watcher()->settings()->GetLastUserAndServiceToken(
|
| + SYNC_SERVICE_NAME, &username, &auth_token))) {
|
| + RaiseAuthNeededEvent();
|
| + return;
|
| + }
|
| +
|
| + browser_sync::ToPathString s(username);
|
| + if (s.good()) {
|
| + share_.authenticated_name = s.get_string16();
|
| + } else {
|
| + RaiseAuthNeededEvent();
|
| + return;
|
| + }
|
| +
|
| + // We optimize by opening the directory before the "fresh" authentication
|
| + // attempt completes so that we can immediately begin processing changes.
|
| + if (!dir_manager()->Open(username_for_share())) {
|
| + DCHECK(false) << "Had last known user but could not open directory";
|
| + return;
|
| + }
|
| +
|
| + // Set the sync data type so that the server only sends us bookmarks
|
| + // changes.
|
| + {
|
| + syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
|
| + if (!lookup.good()) {
|
| + DCHECK(false) << "ScopedDirLookup failed on successfully opened dir";
|
| + return;
|
| + }
|
| + if (lookup->initial_sync_ended())
|
| + MarkAndNotifyInitializationComplete();
|
| + }
|
| +
|
| + auth_watcher()->AuthenticateWithToken(username, auth_token);
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::RaiseAuthNeededEvent() {
|
| + auth_problem_ = AUTH_PROBLEM_INVALID_GAIA_CREDENTIALS;
|
| + if (observer_)
|
| + observer_->OnAuthProblem(auth_problem_);
|
| +}
|
| +
|
| +SyncManager::~SyncManager() {
|
| + delete data_;
|
| +}
|
| +
|
| +void SyncManager::SetObserver(Observer* observer) {
|
| + data_->set_observer(observer);
|
| +}
|
| +
|
| +void SyncManager::RemoveObserver() {
|
| + data_->set_observer(NULL);
|
| +}
|
| +
|
| +void SyncManager::Shutdown() {
|
| + data_->Shutdown();
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::Shutdown() {
|
| + // First reset the AuthWatcher in case an auth attempt is in progress so that
|
| + // it terminates gracefully before we shutdown and close other components.
|
| + // Otherwise the attempt can complete after we've closed the directory, for
|
| + // example, and cause initialization to continue, which is bad.
|
| + auth_watcher_.reset();
|
| +
|
| + if (syncer_thread()) {
|
| + if (!syncer_thread()->Stop(kThreadExitTimeoutMsec))
|
| + DCHECK(false) << "Unable to stop the syncer, it won't be happy...";
|
| + }
|
| +
|
| + // Shutdown the xmpp buzz connection.
|
| + LOG(INFO) << "P2P: Mediator logout started.";
|
| + if (talk_mediator()) {
|
| + talk_mediator()->Logout();
|
| + }
|
| + LOG(INFO) << "P2P: Mediator logout completed.";
|
| +
|
| + if (dir_manager()) {
|
| + dir_manager()->FinalSaveChangesForAll();
|
| + dir_manager()->Close(username_for_share());
|
| + }
|
| +
|
| + // Reset the DirectoryManager and UserSettings so they relinquish sqlite
|
| + // handles to backing files.
|
| + share_.dir_manager.reset();
|
| + user_settings_.reset();
|
| +
|
| + // We don't want to process any more events.
|
| + dir_change_hookup_.reset();
|
| + syncer_event_.reset();
|
| + authwatcher_hookup_.reset();
|
| +
|
| +#if defined(OS_WINDOWS)
|
| + // Stop the address watch thread by signaling the exit flag.
|
| + // TODO(timsteele): Same as todo in Init().
|
| + SetEvent(address_watch_params_.exit_flag);
|
| + const DWORD wait_result = WaitForSingleObject(address_watch_thread_,
|
| + kThreadExitTimeoutMsec);
|
| + LOG_IF(ERROR, WAIT_FAILED == wait_result) << "Waiting for addr change thread "
|
| + "to exit failed. GetLastError(): " << hex << GetLastError();
|
| + LOG_IF(ERROR, WAIT_TIMEOUT == wait_result) << "Thread exit timeout expired";
|
| + CloseHandle(address_watch_params_.exit_flag);
|
| +#endif
|
| +}
|
| +
|
| +// Listen to model changes, filter out ones initiated by the sync API, and
|
| +// saves the rest (hopefully just backend Syncer changes resulting from
|
| +// ApplyUpdates) to data_->changelist.
|
| +void SyncManager::SyncInternal::HandleChangeEvent(
|
| + const syncable::DirectoryChangeEvent& event) {
|
| + if (event.todo == syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE) {
|
| + HandleTransactionCompleteChangeEvent(event);
|
| + return;
|
| + } else if (event.todo == syncable::DirectoryChangeEvent::CALCULATE_CHANGES) {
|
| + if (event.writer == syncable::SYNCAPI) {
|
| + HandleCalculateChangesChangeEventFromSyncApi(event);
|
| + return;
|
| + }
|
| + HandleCalculateChangesChangeEventFromSyncer(event);
|
| + return;
|
| + } else if (event.todo == syncable::DirectoryChangeEvent::SHUTDOWN) {
|
| + dir_change_hookup_.reset();
|
| + }
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent(
|
| + const syncable::DirectoryChangeEvent& event) {
|
| + DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE);
|
| + // This notification happens immediately after a syncable WriteTransaction
|
| + // falls out of scope.
|
| + if (change_buffer_.IsEmpty() || !observer_)
|
| + return;
|
| +
|
| + ReadTransaction trans(GetUserShare());
|
| + vector<ChangeRecord> ordered_changes;
|
| + change_buffer_.GetAllChangesInTreeOrder(&trans, &ordered_changes);
|
| + if (!ordered_changes.empty()) {
|
| + observer_->OnChangesApplied(&trans, &ordered_changes[0],
|
| + ordered_changes.size());
|
| + }
|
| + change_buffer_.Clear();
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi(
|
| + const syncable::DirectoryChangeEvent& event) {
|
| + // We have been notified about a user action changing the bookmark model.
|
| + DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES);
|
| + DCHECK_EQ(event.writer, syncable::SYNCAPI);
|
| + LOG_IF(WARNING, !change_buffer_.IsEmpty()) <<
|
| + "CALCULATE_CHANGES called with unapplied old changes.";
|
| +
|
| + bool exists_unsynced_items = false;
|
| + for (syncable::OriginalEntries::const_iterator i = event.originals->begin();
|
| + i != event.originals->end() && !exists_unsynced_items;
|
| + ++i) {
|
| + int64 id = i->ref(syncable::META_HANDLE);
|
| + syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id);
|
| + DCHECK(e.good());
|
| +
|
| + if (e.IsRoot()) {
|
| + // Ignore root object, should it ever change.
|
| + continue;
|
| + } else if (!e.Get(syncable::IS_BOOKMARK_OBJECT)) {
|
| + // Ignore non-bookmark objects.
|
| + continue;
|
| + } else if (e.Get(syncable::IS_UNSYNCED)) {
|
| + // Unsynced items will cause us to nudge the the syncer.
|
| + exists_unsynced_items = true;
|
| + }
|
| + }
|
| + if (exists_unsynced_items && syncer_thread()) {
|
| + syncer_thread()->NudgeSyncer(200, SyncerThread::kLocal); // 1/5 a second.
|
| + }
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer(
|
| + const syncable::DirectoryChangeEvent& event) {
|
| + // We only expect one notification per sync step, so change_buffer_ should
|
| + // contain no pending entries.
|
| + DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES);
|
| + DCHECK_EQ(event.writer, syncable::SYNCER);
|
| + LOG_IF(WARNING, !change_buffer_.IsEmpty()) <<
|
| + "CALCULATE_CHANGES called with unapplied old changes.";
|
| +
|
| + for (syncable::OriginalEntries::const_iterator i = event.originals->begin();
|
| + i != event.originals->end(); ++i) {
|
| + int64 id = i->ref(syncable::META_HANDLE);
|
| + syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id);
|
| + bool existed_before = !i->ref(syncable::IS_DEL);
|
| + bool exists_now = e.good() && !e.Get(syncable::IS_DEL);
|
| + DCHECK(e.good());
|
| +
|
| + // Ignore root object, should it ever change.
|
| + if (e.IsRoot())
|
| + continue;
|
| + // Ignore non-bookmark objects.
|
| + if (!e.Get(syncable::IS_BOOKMARK_OBJECT))
|
| + continue;
|
| +
|
| + if (exists_now && !existed_before)
|
| + change_buffer_.PushAddedItem(id);
|
| + else if (!exists_now && existed_before)
|
| + change_buffer_.PushDeletedItem(id);
|
| + else if (exists_now && existed_before && BookmarkPropertiesDiffer(*i, e))
|
| + change_buffer_.PushUpdatedItem(id, BookmarkPositionsDiffer(*i, e));
|
| + }
|
| +}
|
| +
|
| +SyncManager::Status::Summary
|
| +SyncManager::SyncInternal::ComputeAggregatedStatusSummary() {
|
| + switch (allstatus()->status().icon) {
|
| + case AllStatus::OFFLINE:
|
| + return Status::OFFLINE;
|
| + case AllStatus::OFFLINE_UNSYNCED:
|
| + return Status::OFFLINE_UNSYNCED;
|
| + case AllStatus::SYNCING:
|
| + return Status::SYNCING;
|
| + case AllStatus::READY:
|
| + return Status::READY;
|
| + case AllStatus::CONFLICT:
|
| + return Status::CONFLICT;
|
| + case AllStatus::OFFLINE_UNUSABLE:
|
| + return Status::OFFLINE_UNUSABLE;
|
| + default:
|
| + return Status::INVALID;
|
| + }
|
| +}
|
| +
|
| +SyncManager::Status SyncManager::SyncInternal::ComputeAggregatedStatus() {
|
| + Status return_status =
|
| + { ComputeAggregatedStatusSummary(),
|
| + allstatus()->status().authenticated,
|
| + allstatus()->status().server_up,
|
| + allstatus()->status().server_reachable,
|
| + allstatus()->status().server_broken,
|
| + allstatus()->status().notifications_enabled,
|
| + allstatus()->status().notifications_received,
|
| + allstatus()->status().notifications_sent,
|
| + allstatus()->status().unsynced_count,
|
| + allstatus()->status().conflicting_count,
|
| + allstatus()->status().syncing,
|
| + allstatus()->status().initial_sync_ended,
|
| + allstatus()->status().syncer_stuck,
|
| + allstatus()->status().updates_available,
|
| + allstatus()->status().updates_received,
|
| + allstatus()->status().disk_full,
|
| + allstatus()->status().max_consecutive_errors};
|
| + return return_status;
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::HandleSyncerEvent(const SyncerEvent& event) {
|
| + if (!initialized()) {
|
| + // We get here if A) We have successfully authenticated at least once (
|
| + // because we attach HandleSyncerEvent only once we receive notification of
|
| + // successful authentication [locally or otherwise]), but B) the initial
|
| + // sync had not completed at that time.
|
| + if (SyncerStatus(event.last_session).IsShareUsable())
|
| + MarkAndNotifyInitializationComplete();
|
| + return;
|
| + }
|
| +
|
| + if (!observer_)
|
| + return;
|
| +
|
| + // Only send an event if this is due to a cycle ending and this cycle
|
| + // concludes a canonical "sync" process; that is, based on what is known
|
| + // locally we are "all happy" and up-to-date. There may be new changes on
|
| + // the server, but we'll get them on a subsequent sync.
|
| + //
|
| + // Notifications are sent at the end of every sync cycle, regardless of
|
| + // whether we should sync again.
|
| + if (event.what_happened == SyncerEvent::SYNC_CYCLE_ENDED) {
|
| + if (!event.last_session->ShouldSyncAgain()) {
|
| + observer_->OnSyncCycleCompleted();
|
| + }
|
| +
|
| + // TODO(chron): Consider changing this back to track ShouldSyncAgain
|
| + // Only notify peers if a commit has occurred and change the bookmark model.
|
| + if (event.last_session && event.last_session->items_committed()) {
|
| + notification_pending_ = true;
|
| + }
|
| +
|
| + // SyncCycles are started by the following events: creation of the syncer,
|
| + // (re)connection to buzz, local changes, peer notifications of updates.
|
| + // Peers will be notified of changes made while there is no buzz connection
|
| + // immediately after a connection has been re-established.
|
| + // the next sync cycle.
|
| + // TODO(brg): Move this to TalkMediatorImpl as a SyncerThread event hook.
|
| + if (notification_pending_ && talk_mediator()) {
|
| + LOG(INFO) << "Sending XMPP notification...";
|
| + bool success = talk_mediator()->SendNotification();
|
| + if (success) {
|
| + notification_pending_ = false;
|
| + }
|
| + } else {
|
| + LOG(INFO) << "Didn't send XMPP notification!"
|
| + << " event.last_session: " << event.last_session
|
| + << " event.last_session->items_committed(): "
|
| + << event.last_session->items_committed()
|
| + << " talk_mediator(): " << talk_mediator();
|
| + }
|
| + }
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::HandleAuthWatcherEvent(
|
| + const AuthWatcherEvent& event) {
|
| + // We don't care about an authentication attempt starting event, and we
|
| + // don't want to reset our state to AUTH_PROBLEM_NONE because the fact that
|
| + // an _attempt_ is starting doesn't change the fact that we have an auth
|
| + // problem.
|
| + if (event.what_happened == AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START)
|
| + return;
|
| + // We clear our last auth problem cache on new auth watcher events, and only
|
| + // set it to indicate a problem state for certain AuthWatcherEvent types.
|
| + auth_problem_ = AUTH_PROBLEM_NONE;
|
| + switch (event.what_happened) {
|
| + case AuthWatcherEvent::AUTH_SUCCEEDED:
|
| + // We now know the supplied username and password were valid. If this
|
| + // wasn't the first sync, authenticated_name should already be assigned.
|
| + if (username_for_share().empty()) {
|
| + browser_sync::ToPathString s(event.user_email);
|
| + if (s.good())
|
| + share_.authenticated_name = s.get_string16();
|
| + }
|
| +
|
| + DCHECK(LowerCaseEqualsASCII(browser_sync::ToUTF8(
|
| + username_for_share()).get_string(),
|
| + StringToLowerASCII(event.user_email).c_str()))
|
| + << "username_for_share= "
|
| + << browser_sync::ToUTF8(username_for_share())
|
| + << ", event.user_email= " << event.user_email;
|
| +
|
| + if (observer_)
|
| + observer_->OnAuthProblem(AUTH_PROBLEM_NONE);
|
| +
|
| + // Hook up the DirectoryChangeEvent listener, HandleChangeEvent.
|
| + {
|
| + syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
|
| + if (!lookup.good()) {
|
| + DCHECK(false) << "ScopedDirLookup creation failed; unable to hook "
|
| + << "up directory change event listener!";
|
| + return;
|
| + }
|
| + dir_change_hookup_.reset(NewEventListenerHookup(
|
| + lookup->changes_channel(), this,
|
| + &SyncInternal::HandleChangeEvent));
|
| +
|
| + if (lookup->initial_sync_ended())
|
| + MarkAndNotifyInitializationComplete();
|
| + }
|
| + {
|
| + // Start watching the syncer channel directly here.
|
| + DCHECK(syncer_thread() != NULL);
|
| + syncer_event_.reset(NewEventListenerHookup(syncer_thread()->channel(),
|
| + this, &SyncInternal::HandleSyncerEvent));
|
| + }
|
| + return;
|
| + // Authentication failures translate to Status::AuthProblem events.
|
| + case AuthWatcherEvent::GAIA_AUTH_FAILED: // Invalid GAIA credentials.
|
| + case AuthWatcherEvent::SERVICE_AUTH_FAILED: // Expired GAIA credentials.
|
| + auth_problem_ = AUTH_PROBLEM_INVALID_GAIA_CREDENTIALS;
|
| + break;
|
| + case AuthWatcherEvent::SERVICE_USER_NOT_SIGNED_UP:
|
| + auth_problem_ = AUTH_PROBLEM_USER_NOT_SIGNED_UP;
|
| + break;
|
| + case AuthWatcherEvent::SERVICE_CONNECTION_FAILED:
|
| + auth_problem_ = AUTH_PROBLEM_CONNECTION_FAILED;
|
| + break;
|
| + default: // We don't care about the many other AuthWatcherEvent types.
|
| + return;
|
| + }
|
| +
|
| + // Fire notification that the status changed due to an authentication error.
|
| + if (observer_)
|
| + observer_->OnAuthProblem(auth_problem_);
|
| +}
|
| +
|
| +SyncManager::Status::Summary SyncManager::GetStatusSummary() const {
|
| + return data_->ComputeAggregatedStatusSummary();
|
| +}
|
| +
|
| +SyncManager::Status SyncManager::GetDetailedStatus() const {
|
| + return data_->ComputeAggregatedStatus();
|
| +}
|
| +
|
| +SyncManager::SyncInternal* SyncManager::GetImpl() const { return data_; }
|
| +
|
| +void SyncManager::SaveChanges() {
|
| + data_->SaveChanges();
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::SaveChanges() {
|
| + syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
|
| + if (!lookup.good()) {
|
| + DCHECK(false) << "ScopedDirLookup creation failed; Unable to SaveChanges";
|
| + return;
|
| + }
|
| + lookup->SaveChanges();
|
| +}
|
| +
|
| +void SyncManager::SetupForTestMode(const sync_char16* test_username) {
|
| + DCHECK(data_) << "SetupForTestMode requires initialization";
|
| + data_->SetupForTestMode(test_username);
|
| +}
|
| +
|
| +void SyncManager::SyncInternal::SetupForTestMode(
|
| + const sync_char16* test_username) {
|
| + String16ToPathString(test_username, &share_.authenticated_name);
|
| +
|
| + if (!dir_manager()->Open(username_for_share()))
|
| + DCHECK(false) << "Could not open directory when running in test mode";
|
| +
|
| + // Hook up the DirectoryChangeEvent listener, HandleChangeEvent.
|
| + {
|
| + syncable::ScopedDirLookup lookup(dir_manager(), username_for_share());
|
| + if (!lookup.good()) {
|
| + DCHECK(false) << "ScopedDirLookup creation failed; unable to hook "
|
| + << "up directory change event listener!";
|
| + return;
|
| + }
|
| + dir_change_hookup_.reset(NewEventListenerHookup(
|
| + lookup->changes_channel(), this,
|
| + &SyncInternal::HandleChangeEvent));
|
| + }
|
| + MarkAndNotifyInitializationComplete();
|
| +}
|
| +
|
| +//////////////////////////////////////////////////////////////////////////
|
| +// BaseTransaction member definitions
|
| +BaseTransaction::BaseTransaction(UserShare* share)
|
| + : lookup_(NULL) {
|
| + DCHECK(share && share->dir_manager.get());
|
| + lookup_ = new syncable::ScopedDirLookup(share->dir_manager.get(),
|
| + share->authenticated_name);
|
| + if (!(lookup_->good()))
|
| + DCHECK(false) << "ScopedDirLookup failed on valid DirManager.";
|
| +}
|
| +BaseTransaction::~BaseTransaction() {
|
| + delete lookup_;
|
| +}
|
| +
|
| +UserShare* SyncManager::GetUserShare() const {
|
| + DCHECK(data_->initialized()) << "GetUserShare requires initialization!";
|
| + return data_->GetUserShare();
|
| +}
|
| +
|
| +} // namespace sync_api
|
|
|
| Property changes on: chrome\browser\sync\engine\syncapi.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|