OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "chrome/browser/sync/engine/syncapi.h" |
| 6 |
| 7 #if defined(OS_WINDOWS) |
| 8 #include <windows.h> |
| 9 #include <iphlpapi.h> |
| 10 #endif |
| 11 |
| 12 #include <iomanip> |
| 13 #include <list> |
| 14 #include <string> |
| 15 #include <vector> |
| 16 |
| 17 #include "base/at_exit.h" |
| 18 #include "base/basictypes.h" |
| 19 #include "base/scoped_ptr.h" |
| 20 #include "base/string_util.h" |
| 21 #include "chrome/browser/sync/engine/all_status.h" |
| 22 #include "chrome/browser/sync/engine/auth_watcher.h" |
| 23 #include "chrome/browser/sync/engine/change_reorder_buffer.h" |
| 24 #include "chrome/browser/sync/engine/client_command_channel.h" |
| 25 #include "chrome/browser/sync/engine/model_safe_worker.h" |
| 26 #include "chrome/browser/sync/engine/net/gaia_authenticator.h" |
| 27 #include "chrome/browser/sync/engine/net/server_connection_manager.h" |
| 28 #include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h" |
| 29 #include "chrome/browser/sync/engine/syncer.h" |
| 30 #include "chrome/browser/sync/engine/syncer_thread.h" |
| 31 #include "chrome/browser/sync/notifier/listener/talk_mediator.h" |
| 32 #include "chrome/browser/sync/notifier/listener/talk_mediator_impl.h" |
| 33 #include "chrome/browser/sync/protocol/service_constants.h" |
| 34 #include "chrome/browser/sync/syncable/directory_manager.h" |
| 35 #include "chrome/browser/sync/syncable/syncable.h" |
| 36 #include "chrome/browser/sync/util/character_set_converters.h" |
| 37 #include "chrome/browser/sync/util/closure.h" |
| 38 #include "chrome/browser/sync/util/crypto_helpers.h" |
| 39 #include "chrome/browser/sync/util/event_sys.h" |
| 40 #include "chrome/browser/sync/util/path_helpers.h" |
| 41 #include "chrome/browser/sync/util/pthread_helpers.h" |
| 42 #include "chrome/browser/sync/util/user_settings.h" |
| 43 #include "googleurl/src/gurl.h" |
| 44 |
| 45 using browser_sync::AllStatus; |
| 46 using browser_sync::AllStatusEvent; |
| 47 using browser_sync::AuthWatcher; |
| 48 using browser_sync::AuthWatcherEvent; |
| 49 using browser_sync::ClientCommandChannel; |
| 50 using browser_sync::Syncer; |
| 51 using browser_sync::SyncerEvent; |
| 52 using browser_sync::SyncerStatus; |
| 53 using browser_sync::SyncerThread; |
| 54 using browser_sync::UserSettings; |
| 55 using browser_sync::TalkMediator; |
| 56 using browser_sync::TalkMediatorImpl; |
| 57 using std::list; |
| 58 using std::hex; |
| 59 using std::string; |
| 60 using std::vector; |
| 61 using syncable::Directory; |
| 62 using syncable::DirectoryManager; |
| 63 |
| 64 static const int kServerReachablePollingIntervalMsec = 60000 * 60; |
| 65 static const int kThreadExitTimeoutMsec = 60000; |
| 66 static const int kSSLPort = 443; |
| 67 |
| 68 // We shouldn't call InitLogFiles more than once since that will cause a crash. |
| 69 // So we use a global state variable to avoid that. This doesn't work in case |
| 70 // of multiple threads, and if some other part also tries to call InitLogFiles |
| 71 // apart from this file. But this is okay for now since this is the only |
| 72 // place we call InitLogFiles. |
| 73 namespace { |
| 74 static bool g_log_files_initialized = false; |
| 75 static base::AtExitManager g_at_exit_manager; // Necessary for NewCallback |
| 76 } // empty namespace |
| 77 |
| 78 struct ThreadParams { |
| 79 browser_sync::ServerConnectionManager* conn_mgr; |
| 80 #if defined(OS_WINDOWS) |
| 81 HANDLE exit_flag; |
| 82 #endif |
| 83 }; |
| 84 |
| 85 // This thread calls CheckServerReachable() whenever a change occurs |
| 86 // in the table that maps IP addresses to interfaces, for example when |
| 87 // the user unplugs his network cable. |
| 88 void* AddressWatchThread(void* arg) { |
| 89 NameCurrentThreadForDebugging("SyncEngine_AddressWatcher"); |
| 90 LOG(INFO) << "starting the address watch thread"; |
| 91 const ThreadParams* const params = reinterpret_cast<const ThreadParams*>(arg); |
| 92 #if defined(OS_WINDOWS) |
| 93 OVERLAPPED overlapped = {0}; |
| 94 overlapped.hEvent = CreateEvent(NULL, FALSE, TRUE, NULL); |
| 95 HANDLE file; |
| 96 DWORD rc = WAIT_OBJECT_0; |
| 97 while (true) { |
| 98 // Only call NotifyAddrChange() after the IP address has changed or if this |
| 99 // is the first time through the loop. |
| 100 if (WAIT_OBJECT_0 == rc) { |
| 101 ResetEvent(overlapped.hEvent); |
| 102 DWORD notify_result = NotifyAddrChange(&file, &overlapped); |
| 103 if (ERROR_IO_PENDING != notify_result) { |
| 104 LOG(ERROR) << "NotifyAddrChange() returned unexpected result " |
| 105 << hex << notify_result; |
| 106 break; |
| 107 } |
| 108 } |
| 109 HANDLE events[] = { overlapped.hEvent, params->exit_flag }; |
| 110 rc = WaitForMultipleObjects(ARRAYSIZE(events), events, FALSE, |
| 111 kServerReachablePollingIntervalMsec); |
| 112 |
| 113 // If the exit flag was signaled, the thread will exit. |
| 114 if (WAIT_OBJECT_0 + 1 == rc) |
| 115 break; |
| 116 |
| 117 params->conn_mgr->CheckServerReachable(); |
| 118 } |
| 119 CloseHandle(overlapped.hEvent); |
| 120 #else |
| 121 // TODO(zork): Add this functionality to Linux. |
| 122 #endif |
| 123 LOG(INFO) << "The address watch thread has stopped"; |
| 124 return 0; |
| 125 } |
| 126 |
| 127 namespace sync_api { |
| 128 class ModelSafeWorkerBridge; |
| 129 |
| 130 static const PSTR_CHAR kBookmarkSyncUserSettingsDatabase[] = |
| 131 PSTR("BookmarkSyncSettings.sqlite3"); |
| 132 static const PSTR_CHAR kDefaultNameForNewNodes[] = PSTR(" "); |
| 133 |
| 134 // The list of names which are reserved for use by the server. |
| 135 static const char16* kForbiddenServerNames[] = |
| 136 { STRING16(""), STRING16("."), STRING16("..") }; |
| 137 |
| 138 ////////////////////////////////////////////////////////////////////////// |
| 139 // Static helper functions. |
| 140 |
| 141 // Helper function to look up the int64 metahandle of an object given the ID |
| 142 // string. |
| 143 static int64 IdToMetahandle(syncable::BaseTransaction* trans, |
| 144 const syncable::Id& id) { |
| 145 syncable::Entry entry(trans, syncable::GET_BY_ID, id); |
| 146 if (!entry.good()) |
| 147 return kInvalidId; |
| 148 return entry.Get(syncable::META_HANDLE); |
| 149 } |
| 150 |
| 151 // Checks whether |name| is a server-illegal name followed by zero or more space |
| 152 // characters. The three server-illegal names are the empty string, dot, and |
| 153 // dot-dot. Very long names (>255 bytes in UTF-8 Normalization Form C) are |
| 154 // also illegal, but are not considered here. |
| 155 static bool IsNameServerIllegalAfterTrimming(const string16& name) { |
| 156 size_t untrimmed_count = name.find_last_not_of(' ') + 1; |
| 157 for (int i = 0; i < arraysize(kForbiddenServerNames); ++i) { |
| 158 if (name.compare(0, untrimmed_count, kForbiddenServerNames[i]) == 0) |
| 159 return true; |
| 160 } |
| 161 return false; |
| 162 } |
| 163 |
| 164 static bool EndsWithSpace(const string16& string) { |
| 165 return !string.empty() && *string.rbegin() == ' '; |
| 166 } |
| 167 |
| 168 static inline void String16ToPathString(const sync_char16 *in, |
| 169 PathString *out) { |
| 170 string16 in_str(in); |
| 171 #if defined(OS_WINDOWS) |
| 172 out->assign(in_str); |
| 173 #else |
| 174 UTF16ToUTF8(in_str.c_str(), in_str.length(), out); |
| 175 #endif |
| 176 } |
| 177 |
| 178 static inline void PathStringToString16(const PathString& in, string16* out) { |
| 179 #if defined(OS_WINDOWS) |
| 180 out->assign(in); |
| 181 #else |
| 182 UTF8ToUTF16(in.c_str(), in.length(), out); |
| 183 #endif |
| 184 } |
| 185 |
| 186 // When taking a name from the syncapi, append a space if it matches the |
| 187 // pattern of a server-illegal name followed by zero or more spaces. |
| 188 static void SyncAPINameToServerName(const sync_char16 *sync_api_name, |
| 189 PathString* out) { |
| 190 String16ToPathString(sync_api_name, out); |
| 191 string16 sync_api_name_str(sync_api_name); |
| 192 if (IsNameServerIllegalAfterTrimming(sync_api_name_str)) |
| 193 out->append(PSTR(" ")); |
| 194 } |
| 195 |
| 196 // In the reverse direction, if a server name matches the pattern of a |
| 197 // server-illegal name followed by one or more spaces, remove the trailing |
| 198 // space. |
| 199 static void ServerNameToSyncAPIName(const PathString& server_name, |
| 200 string16*out) { |
| 201 string16 server_name_str; |
| 202 PathStringToString16(server_name, &server_name_str); |
| 203 if (IsNameServerIllegalAfterTrimming(server_name_str) && |
| 204 EndsWithSpace(server_name_str)) |
| 205 out->assign(server_name_str, 0, server_name_str.size() - 1); |
| 206 else |
| 207 out->assign(server_name_str); |
| 208 } |
| 209 |
| 210 // A UserShare encapsulates the syncable pieces that represent an authenticated |
| 211 // user and their data (share). |
| 212 // This encompasses all pieces required to build transaction objects on the |
| 213 // syncable share. |
| 214 struct UserShare { |
| 215 // The DirectoryManager itself, which is the parent of Transactions and can |
| 216 // be shared across multiple threads (unlike Directory). |
| 217 scoped_ptr<DirectoryManager> dir_manager; |
| 218 |
| 219 // The username of the sync user. This is empty until we have performed at |
| 220 // least one successful GAIA authentication with this username, which means |
| 221 // on first-run it is empty until an AUTH_SUCCEEDED event and on future runs |
| 222 // it is set as soon as the client instructs us to authenticate for the last |
| 223 // known valid user (AuthenticateForLastKnownUser()). |
| 224 // Stored as a PathString to avoid string conversions each time a transaction |
| 225 // is created. |
| 226 PathString authenticated_name; |
| 227 }; |
| 228 |
| 229 //////////////////////////////////// |
| 230 // BaseNode member definitions. |
| 231 |
| 232 // BaseNode::BaseNodeInternal provides storage for member Get() functions that |
| 233 // need to return pointers (e.g. strings). |
| 234 struct BaseNode::BaseNodeInternal { |
| 235 string16 url; |
| 236 string16 title; |
| 237 Directory::ChildHandles child_handles; |
| 238 syncable::Blob favicon; |
| 239 }; |
| 240 |
| 241 BaseNode::BaseNode() : data_(new BaseNode::BaseNodeInternal) {} |
| 242 |
| 243 BaseNode::~BaseNode() { |
| 244 delete data_; |
| 245 } |
| 246 |
| 247 int64 BaseNode::GetParentId() const { |
| 248 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), |
| 249 GetEntry()->Get(syncable::PARENT_ID)); |
| 250 } |
| 251 |
| 252 int64 BaseNode::GetId() const { |
| 253 return GetEntry()->Get(syncable::META_HANDLE); |
| 254 } |
| 255 |
| 256 bool BaseNode::GetIsFolder() const { |
| 257 return GetEntry()->Get(syncable::IS_DIR); |
| 258 } |
| 259 |
| 260 const sync_char16* BaseNode::GetTitle() const { |
| 261 // Store the string in data_ so that the returned pointer is valid. |
| 262 ServerNameToSyncAPIName(GetEntry()->GetName().non_unique_value(), |
| 263 &data_->title); |
| 264 return data_->title.c_str(); |
| 265 } |
| 266 |
| 267 const sync_char16* BaseNode::GetURL() const { |
| 268 // Store the string in data_ so that the returned pointer is valid. |
| 269 PathStringToString16(GetEntry()->Get(syncable::BOOKMARK_URL), &data_->url); |
| 270 return data_->url.c_str(); |
| 271 } |
| 272 |
| 273 const int64* BaseNode::GetChildIds(size_t* child_count) const { |
| 274 DCHECK(child_count); |
| 275 Directory* dir = GetTransaction()->GetLookup(); |
| 276 dir->GetChildHandles(GetTransaction()->GetWrappedTrans(), |
| 277 GetEntry()->Get(syncable::ID), &data_->child_handles); |
| 278 |
| 279 *child_count = data_->child_handles.size(); |
| 280 return (data_->child_handles.empty()) ? NULL : &data_->child_handles[0]; |
| 281 } |
| 282 |
| 283 int64 BaseNode::GetPredecessorId() const { |
| 284 syncable::Id id_string = GetEntry()->Get(syncable::PREV_ID); |
| 285 if (id_string.IsRoot()) |
| 286 return kInvalidId; |
| 287 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); |
| 288 } |
| 289 |
| 290 int64 BaseNode::GetSuccessorId() const { |
| 291 syncable::Id id_string = GetEntry()->Get(syncable::NEXT_ID); |
| 292 if (id_string.IsRoot()) |
| 293 return kInvalidId; |
| 294 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); |
| 295 } |
| 296 |
| 297 int64 BaseNode::GetFirstChildId() const { |
| 298 syncable::Directory* dir = GetTransaction()->GetLookup(); |
| 299 syncable::BaseTransaction* trans = GetTransaction()->GetWrappedTrans(); |
| 300 syncable::Id id_string = |
| 301 dir->GetFirstChildId(trans, GetEntry()->Get(syncable::ID)); |
| 302 if (id_string.IsRoot()) |
| 303 return kInvalidId; |
| 304 return IdToMetahandle(GetTransaction()->GetWrappedTrans(), id_string); |
| 305 } |
| 306 |
| 307 const unsigned char* BaseNode::GetFaviconBytes(size_t* size_in_bytes) { |
| 308 data_->favicon = GetEntry()->Get(syncable::BOOKMARK_FAVICON); |
| 309 *size_in_bytes = data_->favicon.size(); |
| 310 if (*size_in_bytes) |
| 311 return &(data_->favicon[0]); |
| 312 else |
| 313 return NULL; |
| 314 } |
| 315 |
| 316 int64 BaseNode::GetExternalId() const { |
| 317 return GetEntry()->Get(syncable::LOCAL_EXTERNAL_ID); |
| 318 } |
| 319 |
| 320 //////////////////////////////////// |
| 321 // WriteNode member definitions |
| 322 void WriteNode::SetIsFolder(bool folder) { |
| 323 if (entry_->Get(syncable::IS_DIR) == folder) |
| 324 return; // Skip redundant changes. |
| 325 |
| 326 entry_->Put(syncable::IS_DIR, folder); |
| 327 MarkForSyncing(); |
| 328 } |
| 329 |
| 330 void WriteNode::SetTitle(const sync_char16* title) { |
| 331 PathString server_legal_name; |
| 332 SyncAPINameToServerName(title, &server_legal_name); |
| 333 syncable::SyncName sync_name(server_legal_name); |
| 334 syncable::DBName db_name(sync_name.value()); |
| 335 db_name.MakeOSLegal(); |
| 336 db_name.MakeNoncollidingForEntry(transaction_->GetWrappedTrans(), |
| 337 entry_->Get(syncable::PARENT_ID), entry_); |
| 338 |
| 339 syncable::Name new_name = syncable::Name::FromDBNameAndSyncName(db_name, |
| 340 sync_name); |
| 341 if (new_name == entry_->GetName()) |
| 342 return; // Skip redundant changes. |
| 343 |
| 344 entry_->PutName(new_name); |
| 345 MarkForSyncing(); |
| 346 } |
| 347 |
| 348 void WriteNode::SetURL(const sync_char16* url) { |
| 349 PathString url_string; |
| 350 String16ToPathString(url, &url_string); |
| 351 if (url_string == entry_->Get(syncable::BOOKMARK_URL)) |
| 352 return; // Skip redundant changes. |
| 353 |
| 354 entry_->Put(syncable::BOOKMARK_URL, url_string); |
| 355 MarkForSyncing(); |
| 356 } |
| 357 |
| 358 void WriteNode::SetExternalId(int64 id) { |
| 359 if (GetExternalId() != id) |
| 360 entry_->Put(syncable::LOCAL_EXTERNAL_ID, id); |
| 361 } |
| 362 |
| 363 WriteNode::WriteNode(WriteTransaction* transaction) |
| 364 : entry_(NULL), transaction_(transaction) { |
| 365 DCHECK(transaction); |
| 366 } |
| 367 |
| 368 WriteNode::~WriteNode() { |
| 369 delete entry_; |
| 370 } |
| 371 |
| 372 // Find an existing node matching the ID |id|, and bind this WriteNode |
| 373 // to it. Return true on success. |
| 374 bool WriteNode::InitByIdLookup(int64 id) { |
| 375 DCHECK(!entry_) << "Init called twice"; |
| 376 DCHECK_NE(id, kInvalidId); |
| 377 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 378 syncable::GET_BY_HANDLE, id); |
| 379 return (entry_->good() && !entry_->Get(syncable::IS_DEL)); |
| 380 } |
| 381 |
| 382 // Create a new node with default properties, and bind this WriteNode to it. |
| 383 // Return true on success. |
| 384 bool WriteNode::InitByCreation(const BaseNode& parent, |
| 385 const BaseNode* predecessor) { |
| 386 DCHECK(!entry_) << "Init called twice"; |
| 387 // |predecessor| must be a child of |parent| or NULL. |
| 388 if (predecessor && predecessor->GetParentId() != parent.GetId()) { |
| 389 DCHECK(false); |
| 390 return false; |
| 391 } |
| 392 |
| 393 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); |
| 394 |
| 395 // Start out with a dummy name, but make it unique. We expect |
| 396 // the caller to set a meaningful name after creation. |
| 397 syncable::DBName dummy(kDefaultNameForNewNodes); |
| 398 dummy.MakeOSLegal(); |
| 399 dummy.MakeNoncollidingForEntry(transaction_->GetWrappedTrans(), parent_id, |
| 400 NULL); |
| 401 |
| 402 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 403 syncable::CREATE, parent_id, dummy); |
| 404 |
| 405 if (!entry_->good()) |
| 406 return false; |
| 407 |
| 408 // Entries are untitled folders by default. |
| 409 entry_->Put(syncable::IS_DIR, true); |
| 410 // TODO(ncarter): Naming this bit IS_BOOKMARK_OBJECT is a bit unfortunate, |
| 411 // since the rest of SyncAPI is essentially bookmark-agnostic. |
| 412 entry_->Put(syncable::IS_BOOKMARK_OBJECT, true); |
| 413 |
| 414 // Now set the predecessor, which sets IS_UNSYNCED as necessary. |
| 415 PutPredecessor(predecessor); |
| 416 |
| 417 return true; |
| 418 } |
| 419 |
| 420 bool WriteNode::SetPosition(const BaseNode& new_parent, |
| 421 const BaseNode* predecessor) { |
| 422 // |predecessor| must be a child of |new_parent| or NULL. |
| 423 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) { |
| 424 DCHECK(false); |
| 425 return false; |
| 426 } |
| 427 |
| 428 syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID); |
| 429 |
| 430 // Filter out redundant changes if both the parent and the predecessor match. |
| 431 if (new_parent_id == entry_->Get(syncable::PARENT_ID)) { |
| 432 const syncable::Id& old = entry_->Get(syncable::PREV_ID); |
| 433 if ((!predecessor && old.IsRoot()) || |
| 434 (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) { |
| 435 return true; |
| 436 } |
| 437 } |
| 438 |
| 439 // Discard the old database name, derive a new database name from the sync |
| 440 // name, and make it legal and unique. |
| 441 syncable::Name name = syncable::Name::FromSyncName(GetEntry()->GetName()); |
| 442 name.db_value().MakeOSLegal(); |
| 443 name.db_value().MakeNoncollidingForEntry(GetTransaction()->GetWrappedTrans(), |
| 444 new_parent_id, entry_); |
| 445 |
| 446 // Atomically change the parent and name. This will fail if it would |
| 447 // introduce a cycle in the hierarchy. |
| 448 if (!entry_->PutParentIdAndName(new_parent_id, name)) |
| 449 return false; |
| 450 |
| 451 // Now set the predecessor, which sets IS_UNSYNCED as necessary. |
| 452 PutPredecessor(predecessor); |
| 453 |
| 454 return true; |
| 455 } |
| 456 |
| 457 const syncable::Entry* WriteNode::GetEntry() const { |
| 458 return entry_; |
| 459 } |
| 460 |
| 461 const BaseTransaction* WriteNode::GetTransaction() const { |
| 462 return transaction_; |
| 463 } |
| 464 |
| 465 void WriteNode::Remove() { |
| 466 entry_->Put(syncable::IS_DEL, true); |
| 467 MarkForSyncing(); |
| 468 } |
| 469 |
| 470 void WriteNode::PutPredecessor(const BaseNode* predecessor) { |
| 471 syncable::Id predecessor_id = predecessor ? |
| 472 predecessor->GetEntry()->Get(syncable::ID) : syncable::Id(); |
| 473 entry_->PutPredecessor(predecessor_id); |
| 474 // Mark this entry as unsynced, to wake up the syncer. |
| 475 MarkForSyncing(); |
| 476 } |
| 477 |
| 478 void WriteNode::SetFaviconBytes(const unsigned char* bytes, |
| 479 size_t size_in_bytes) { |
| 480 syncable::Blob new_favicon(bytes, bytes + size_in_bytes); |
| 481 if (new_favicon == entry_->Get(syncable::BOOKMARK_FAVICON)) |
| 482 return; // Skip redundant changes. |
| 483 |
| 484 entry_->Put(syncable::BOOKMARK_FAVICON, new_favicon); |
| 485 MarkForSyncing(); |
| 486 } |
| 487 |
| 488 void WriteNode::MarkForSyncing() { |
| 489 syncable::MarkForSyncing(entry_); |
| 490 } |
| 491 |
| 492 ////////////////////////////////////////////////////////////////////////// |
| 493 // ReadNode member definitions |
| 494 ReadNode::ReadNode(const BaseTransaction* transaction) |
| 495 : entry_(NULL), transaction_(transaction) { |
| 496 DCHECK(transaction); |
| 497 } |
| 498 |
| 499 ReadNode::~ReadNode() { |
| 500 delete entry_; |
| 501 } |
| 502 |
| 503 void ReadNode::InitByRootLookup() { |
| 504 DCHECK(!entry_) << "Init called twice"; |
| 505 syncable::BaseTransaction* trans = transaction_->GetWrappedTrans(); |
| 506 entry_ = new syncable::Entry(trans, syncable::GET_BY_ID, trans->root_id()); |
| 507 if (!entry_->good()) |
| 508 DCHECK(false) << "Could not lookup root node for reading."; |
| 509 } |
| 510 |
| 511 bool ReadNode::InitByIdLookup(int64 id) { |
| 512 DCHECK(!entry_) << "Init called twice"; |
| 513 DCHECK_NE(id, kInvalidId); |
| 514 syncable::BaseTransaction* trans = transaction_->GetWrappedTrans(); |
| 515 entry_ = new syncable::Entry(trans, syncable::GET_BY_HANDLE, id); |
| 516 if (!entry_->good()) |
| 517 return false; |
| 518 if (entry_->Get(syncable::IS_DEL)) |
| 519 return false; |
| 520 LOG_IF(WARNING, !entry_->Get(syncable::IS_BOOKMARK_OBJECT)) |
| 521 << "SyncAPI InitByIdLookup referencing non-bookmark object."; |
| 522 return true; |
| 523 } |
| 524 |
| 525 const syncable::Entry* ReadNode::GetEntry() const { |
| 526 return entry_; |
| 527 } |
| 528 |
| 529 const BaseTransaction* ReadNode::GetTransaction() const { |
| 530 return transaction_; |
| 531 } |
| 532 |
| 533 bool ReadNode::InitByTagLookup(const sync_char16* tag) { |
| 534 DCHECK(!entry_) << "Init called twice"; |
| 535 PathString tag_string; |
| 536 String16ToPathString(tag, &tag_string); |
| 537 if (tag_string.empty()) |
| 538 return false; |
| 539 syncable::BaseTransaction* trans = transaction_->GetWrappedTrans(); |
| 540 entry_ = new syncable::Entry(trans, syncable::GET_BY_TAG, tag_string); |
| 541 if (!entry_->good()) |
| 542 return false; |
| 543 if (entry_->Get(syncable::IS_DEL)) |
| 544 return false; |
| 545 LOG_IF(WARNING, !entry_->Get(syncable::IS_BOOKMARK_OBJECT)) |
| 546 << "SyncAPI InitByTagLookup referencing non-bookmark object."; |
| 547 return true; |
| 548 } |
| 549 |
| 550 |
| 551 ////////////////////////////////////////////////////////////////////////// |
| 552 // ReadTransaction member definitions |
| 553 ReadTransaction::ReadTransaction(UserShare* share) |
| 554 : BaseTransaction(share), |
| 555 transaction_(NULL) { |
| 556 transaction_ = new syncable::ReadTransaction(GetLookup(), __FILE__, __LINE__); |
| 557 } |
| 558 |
| 559 ReadTransaction::~ReadTransaction() { |
| 560 delete transaction_; |
| 561 } |
| 562 |
| 563 syncable::BaseTransaction* ReadTransaction::GetWrappedTrans() const { |
| 564 return transaction_; |
| 565 } |
| 566 |
| 567 ////////////////////////////////////////////////////////////////////////// |
| 568 // WriteTransaction member definitions |
| 569 WriteTransaction::WriteTransaction(UserShare* share) |
| 570 : BaseTransaction(share), |
| 571 transaction_(NULL) { |
| 572 transaction_ = new syncable::WriteTransaction(GetLookup(), syncable::SYNCAPI, |
| 573 __FILE__, __LINE__); |
| 574 } |
| 575 |
| 576 WriteTransaction::~WriteTransaction() { |
| 577 delete transaction_; |
| 578 } |
| 579 |
| 580 syncable::BaseTransaction* WriteTransaction::GetWrappedTrans() const { |
| 581 return transaction_; |
| 582 } |
| 583 |
| 584 // An implementation of Visitor that we use to "visit" the |
| 585 // ModelSafeWorkerInterface provided by a client of this API. The object we |
| 586 // visit is responsible for calling DoWork, which will invoke Run() on it's |
| 587 // cached work closure. |
| 588 class ModelSafeWorkerVisitor : public ModelSafeWorkerInterface::Visitor { |
| 589 public: |
| 590 explicit ModelSafeWorkerVisitor(Closure* work) : work_(work) { } |
| 591 virtual ~ModelSafeWorkerVisitor() { } |
| 592 |
| 593 // ModelSafeWorkerInterface::Visitor implementation. |
| 594 virtual void DoWork() { |
| 595 work_->Run(); |
| 596 } |
| 597 |
| 598 private: |
| 599 // The work to be done. We run this on DoWork and it cleans itself up |
| 600 // after it is run. |
| 601 Closure* work_; |
| 602 |
| 603 DISALLOW_COPY_AND_ASSIGN(ModelSafeWorkerVisitor); |
| 604 }; |
| 605 |
| 606 // This class is declared in the cc file to allow inheritance from sync types. |
| 607 // The ModelSafeWorkerBridge is a liason between a syncapi-client defined |
| 608 // ModelSafeWorkerInterface and the actual ModelSafeWorker used by the Syncer |
| 609 // for the current SyncManager. |
| 610 class ModelSafeWorkerBridge : public browser_sync::ModelSafeWorker { |
| 611 public: |
| 612 // Takes ownership of |worker|. |
| 613 explicit ModelSafeWorkerBridge(ModelSafeWorkerInterface* worker) |
| 614 : worker_(worker) { |
| 615 } |
| 616 virtual ~ModelSafeWorkerBridge() { } |
| 617 |
| 618 // Overriding ModelSafeWorker. |
| 619 virtual void DoWorkAndWaitUntilDone(Closure* work) { |
| 620 // When the syncer has work to be done, we forward it to our worker who |
| 621 // will invoke DoWork on |visitor| when appropriate (from model safe |
| 622 // thread). |
| 623 ModelSafeWorkerVisitor visitor(work); |
| 624 worker_->CallDoWorkFromModelSafeThreadAndWait(&visitor); |
| 625 } |
| 626 |
| 627 private: |
| 628 // The worker that we can forward work requests to, to ensure the work |
| 629 // is performed on an appropriate model safe thread. |
| 630 scoped_ptr<ModelSafeWorkerInterface> worker_; |
| 631 |
| 632 DISALLOW_COPY_AND_ASSIGN(ModelSafeWorkerBridge); |
| 633 }; |
| 634 |
| 635 // A GaiaAuthenticator that uses HttpPostProviders instead of CURL. |
| 636 class BridgedGaiaAuthenticator : public browser_sync::GaiaAuthenticator { |
| 637 public: |
| 638 BridgedGaiaAuthenticator(const string& user_agent, const string& service_id, |
| 639 const string& gaia_url, |
| 640 HttpPostProviderFactory* factory) |
| 641 : GaiaAuthenticator(user_agent, service_id, gaia_url), |
| 642 gaia_source_(user_agent), post_factory_(factory) { |
| 643 } |
| 644 |
| 645 virtual ~BridgedGaiaAuthenticator() { |
| 646 } |
| 647 |
| 648 virtual bool Post(const GURL& url, const string& post_body, |
| 649 unsigned long* response_code, string* response_body) { |
| 650 string connection_url = "https://"; |
| 651 connection_url += url.host(); |
| 652 connection_url += url.path(); |
| 653 HttpPostProviderInterface* http = post_factory_->Create(); |
| 654 http->SetUserAgent(gaia_source_.c_str()); |
| 655 // SSL is on 443 for Gaia Posts always. |
| 656 http->SetURL(connection_url.c_str(), kSSLPort); |
| 657 http->SetPostPayload("application/x-www-form-urlencoded", |
| 658 post_body.length(), post_body.c_str()); |
| 659 |
| 660 int os_error_code = 0; |
| 661 int int_response_code = 0; |
| 662 if (!http->MakeSynchronousPost(&os_error_code, &int_response_code)) { |
| 663 LOG(INFO) << "Http POST failed, error returns: " << os_error_code; |
| 664 return false; |
| 665 } |
| 666 *response_code = static_cast<int>(int_response_code); |
| 667 response_body->assign(http->GetResponseContent(), |
| 668 http->GetResponseContentLength()); |
| 669 post_factory_->Destroy(http); |
| 670 return true; |
| 671 } |
| 672 private: |
| 673 const std::string gaia_source_; |
| 674 scoped_ptr<HttpPostProviderFactory> post_factory_; |
| 675 DISALLOW_COPY_AND_ASSIGN(BridgedGaiaAuthenticator); |
| 676 }; |
| 677 |
| 678 ////////////////////////////////////////////////////////////////////////// |
| 679 // SyncManager's implementation: SyncManager::SyncInternal |
| 680 class SyncManager::SyncInternal { |
| 681 public: |
| 682 typedef PThreadScopedLock<PThreadMutex> MutexLock; |
| 683 explicit SyncInternal(SyncManager* sync_manager) |
| 684 : observer_(NULL), |
| 685 command_channel_(0), |
| 686 auth_problem_(AUTH_PROBLEM_NONE), |
| 687 sync_manager_(sync_manager), |
| 688 notification_pending_(false), |
| 689 initialized_(false) { |
| 690 } |
| 691 |
| 692 ~SyncInternal() { } |
| 693 |
| 694 bool Init(const PathString& database_location, |
| 695 const std::string& sync_server_and_path, |
| 696 int port, |
| 697 const char* gaia_service_id, |
| 698 const char* gaia_source, |
| 699 bool use_ssl, |
| 700 HttpPostProviderFactory* post_factory, |
| 701 HttpPostProviderFactory* auth_post_factory, |
| 702 ModelSafeWorkerInterface* model_safe_worker, |
| 703 bool attempt_last_user_authentication, |
| 704 const char* user_agent); |
| 705 |
| 706 // Tell sync engine to submit credentials to GAIA for verification and start |
| 707 // the syncing process on success. Successful GAIA authentication will kick |
| 708 // off the following chain of events: |
| 709 // 1. Cause sync engine to open the syncer database. |
| 710 // 2. Trigger the AuthWatcher to create a Syncer for the directory and call |
| 711 // SyncerThread::SyncDirectory; the SyncerThread will block until (4). |
| 712 // 3. Tell the ServerConnectionManager to pass the newly received GAIA auth |
| 713 // token to a sync server to obtain a sync token. |
| 714 // 4. On receipt of this token, the ServerConnectionManager broadcasts |
| 715 // a server-reachable event, which will unblock the SyncerThread, |
| 716 // and the rest is the future. |
| 717 // |
| 718 // If authentication fails, an event will be broadcast all the way up to |
| 719 // the SyncManager::Observer. It may, in turn, decide to try again with new |
| 720 // credentials. Calling this method again is the appropriate course of action |
| 721 // to "retry". |
| 722 void Authenticate(const std::string& username, const std::string& password); |
| 723 |
| 724 // Call periodically from a database-safe thread to persist recent changes |
| 725 // to the syncapi model. |
| 726 void SaveChanges(); |
| 727 |
| 728 // This listener is called upon completion of a syncable transaction, and |
| 729 // builds the list of sync-engine initiated changes that will be forwarded to |
| 730 // the SyncManager's Observers. |
| 731 void HandleChangeEvent(const syncable::DirectoryChangeEvent& event); |
| 732 void HandleTransactionCompleteChangeEvent( |
| 733 const syncable::DirectoryChangeEvent& event); |
| 734 void HandleCalculateChangesChangeEventFromSyncApi( |
| 735 const syncable::DirectoryChangeEvent& event); |
| 736 void HandleCalculateChangesChangeEventFromSyncer( |
| 737 const syncable::DirectoryChangeEvent& event); |
| 738 |
| 739 // This listener is called by the syncer channel for all syncer events. |
| 740 void HandleSyncerEvent(const SyncerEvent& event); |
| 741 |
| 742 // We have a direct hookup to the authwatcher to be notified for auth failures |
| 743 // on startup, to serve our UI needs. |
| 744 void HandleAuthWatcherEvent(const AuthWatcherEvent& event); |
| 745 |
| 746 // Accessors for the private members. |
| 747 DirectoryManager* dir_manager() { return share_.dir_manager.get(); } |
| 748 SyncAPIServerConnectionManager* connection_manager() { |
| 749 return connection_manager_.get(); |
| 750 } |
| 751 SyncerThread* syncer_thread() { return syncer_thread_.get(); } |
| 752 TalkMediator* talk_mediator() { return talk_mediator_.get(); } |
| 753 AuthWatcher* auth_watcher() { return auth_watcher_.get(); } |
| 754 AllStatus* allstatus() { return &allstatus_; } |
| 755 void set_observer(Observer* observer) { observer_ = observer; } |
| 756 UserShare* GetUserShare() { return &share_; } |
| 757 |
| 758 // Return the currently active (validated) username as a PathString for |
| 759 // use with syncable types. |
| 760 const PathString& username_for_share() const { |
| 761 return share_.authenticated_name; |
| 762 } |
| 763 |
| 764 // Returns the authenticated username from our AuthWatcher in UTF8. |
| 765 // See SyncManager::GetAuthenticatedUsername for details. |
| 766 const char* GetAuthenticatedUsername(); |
| 767 |
| 768 // Note about SyncManager::Status implementation: Status is a trimmed |
| 769 // down AllStatus::Status, augmented with authentication failure information |
| 770 // gathered from the internal AuthWatcher. The sync UI itself hooks up to |
| 771 // various sources like the AuthWatcher individually, but with syncapi we try |
| 772 // to keep everything status-related in one place. This means we have to |
| 773 // privately manage state about authentication failures, and whenever the |
| 774 // status or status summary is requested we aggregate this state with |
| 775 // AllStatus::Status information. |
| 776 Status ComputeAggregatedStatus(); |
| 777 Status::Summary ComputeAggregatedStatusSummary(); |
| 778 |
| 779 // See SyncManager::SetupForTestMode for information. |
| 780 void SetupForTestMode(const sync_char16* test_username); |
| 781 |
| 782 // See SyncManager::Shutdown for information. |
| 783 void Shutdown(); |
| 784 |
| 785 // Whether we're initialized to the point of being able to accept changes |
| 786 // (and hence allow transaction creation). See initialized_ for details. |
| 787 bool initialized() const { |
| 788 MutexLock lock(&initialized_mutex_); |
| 789 return initialized_; |
| 790 } |
| 791 private: |
| 792 // Try to authenticate using persisted credentials from a previous successful |
| 793 // authentication. If no such credentials exist, calls OnAuthError on |
| 794 // the client to collect credentials. Otherwise, there exist local |
| 795 // credentials that were once used for a successful auth, so we'll try to |
| 796 // re-use these. |
| 797 // Failure of that attempt will be communicated as normal using |
| 798 // OnAuthError. Since this entry point will bypass normal GAIA |
| 799 // authentication and try to authenticate directly with the sync service |
| 800 // using a cached token, authentication failure will generally occur due to |
| 801 // expired credentials, or possibly because of a password change. |
| 802 void AuthenticateForLastKnownUser(); |
| 803 |
| 804 // Helper to call OnAuthError when no authentication credentials |
| 805 // are available. |
| 806 void RaiseAuthNeededEvent(); |
| 807 |
| 808 // Helper to set initialized_ to true and raise an event to clients to |
| 809 // notify that initialization is complete and it is safe to send us changes. |
| 810 // If already initialized, this is a no-op. |
| 811 void MarkAndNotifyInitializationComplete(); |
| 812 |
| 813 // Determine if the parents or predecessors differ between the old and new |
| 814 // versions of an entry stored in |a| and |b|. Note that a node's index |
| 815 // may change without its NEXT_ID changing if the node at NEXT_ID also |
| 816 // moved (but the relative order is unchanged). To handle such cases, |
| 817 // we rely on the caller to treat a position update on any sibling as |
| 818 // updating the positions of all siblings. |
| 819 static bool BookmarkPositionsDiffer(const syncable::EntryKernel& a, |
| 820 const syncable::Entry& b) { |
| 821 if (a.ref(syncable::NEXT_ID) != b.Get(syncable::NEXT_ID)) |
| 822 return true; |
| 823 if (a.ref(syncable::PARENT_ID) != b.Get(syncable::PARENT_ID)) |
| 824 return true; |
| 825 return false; |
| 826 } |
| 827 |
| 828 // Determine if any of the fields made visible to clients of the Sync API |
| 829 // differ between the versions of an entry stored in |a| and |b|. |
| 830 // A return value of false means that it should be OK to ignore this change. |
| 831 static bool BookmarkPropertiesDiffer(const syncable::EntryKernel& a, |
| 832 const syncable::Entry& b) { |
| 833 if (a.ref(syncable::NAME) != b.Get(syncable::NAME)) |
| 834 return true; |
| 835 if (a.ref(syncable::UNSANITIZED_NAME) != b.Get(syncable::UNSANITIZED_NAME)) |
| 836 return true; |
| 837 if (a.ref(syncable::IS_DIR) != b.Get(syncable::IS_DIR)) |
| 838 return true; |
| 839 if (a.ref(syncable::BOOKMARK_URL) != b.Get(syncable::BOOKMARK_URL)) |
| 840 return true; |
| 841 if (a.ref(syncable::BOOKMARK_FAVICON) != b.Get(syncable::BOOKMARK_FAVICON)) |
| 842 return true; |
| 843 if (BookmarkPositionsDiffer(a, b)) |
| 844 return true; |
| 845 return false; |
| 846 } |
| 847 |
| 848 // We couple the DirectoryManager and username together in a UserShare member |
| 849 // so we can return a handle to share_ to clients of the API for use when |
| 850 // constructing any transaction type. |
| 851 UserShare share_; |
| 852 |
| 853 // A cached string for callers of GetAuthenticatedUsername. We just store the |
| 854 // last result of auth_watcher_->email() here and change it on future calls, |
| 855 // because callers of GetAuthenticatedUsername are supposed to copy the value |
| 856 // if they need it for longer than the scope of the call. |
| 857 std::string cached_auth_watcher_email_; |
| 858 |
| 859 // A wrapper around a sqlite store used for caching authentication data, |
| 860 // last user information, current sync-related URLs, and more. |
| 861 scoped_ptr<UserSettings> user_settings_; |
| 862 |
| 863 // Observer registered via SetObserver/RemoveObserver. |
| 864 // WARNING: This can be NULL! |
| 865 Observer* observer_; |
| 866 |
| 867 // A sink for client commands from the syncer needed to create a SyncerThread. |
| 868 ClientCommandChannel command_channel_; |
| 869 |
| 870 // The ServerConnectionManager used to abstract communication between |
| 871 // the client (the Syncer) and the sync server. |
| 872 scoped_ptr<SyncAPIServerConnectionManager> connection_manager_; |
| 873 |
| 874 // The thread that runs the Syncer. Needs to be explicitly Start()ed. |
| 875 scoped_ptr<SyncerThread> syncer_thread_; |
| 876 |
| 877 // Notification (xmpp) handler. |
| 878 scoped_ptr<TalkMediator> talk_mediator_; |
| 879 |
| 880 // A multi-purpose status watch object that aggregates stats from various |
| 881 // sync components. |
| 882 AllStatus allstatus_; |
| 883 |
| 884 // AuthWatcher kicks off the authentication process and follows it through |
| 885 // phase 1 (GAIA) to phase 2 (sync engine). As part of this work it determines |
| 886 // the initial connectivity and causes the server connection event to be |
| 887 // broadcast, which signals the syncer thread to start syncing. |
| 888 // It has a heavy duty constructor requiring boilerplate so we heap allocate. |
| 889 scoped_ptr<AuthWatcher> auth_watcher_; |
| 890 |
| 891 // A store of change records produced by HandleChangeEvent during the |
| 892 // CALCULATE_CHANGES step, and to be processed, and forwarded to the |
| 893 // observer, by HandleChangeEvent during the TRANSACTION_COMPLETE step. |
| 894 ChangeReorderBuffer change_buffer_; |
| 895 |
| 896 // The event listener hookup that is registered for HandleChangeEvent. |
| 897 scoped_ptr<EventListenerHookup> dir_change_hookup_; |
| 898 |
| 899 // The event listener hookup registered for HandleSyncerEvent. |
| 900 scoped_ptr<EventListenerHookup> syncer_event_; |
| 901 |
| 902 // The event listener hookup registered for HandleAuthWatcherEvent. |
| 903 scoped_ptr<EventListenerHookup> authwatcher_hookup_; |
| 904 |
| 905 // Our cache of a recent authentication problem. If no authentication problem |
| 906 // occurred, or if the last problem encountered has been cleared (by a |
| 907 // subsequent AuthWatcherEvent), this is set to AUTH_PROBLEM_NONE. |
| 908 AuthProblem auth_problem_; |
| 909 |
| 910 // The sync dir_manager to which we belong. |
| 911 SyncManager* const sync_manager_; |
| 912 |
| 913 // Parameters for our thread listening to network status changes. |
| 914 ThreadParams address_watch_params_; |
| 915 thread_handle address_watch_thread_; |
| 916 |
| 917 // True if the next SyncCycle should notify peers of an update. |
| 918 bool notification_pending_; |
| 919 |
| 920 // Set to true once Init has been called, and we know of an authenticated |
| 921 // valid) username either from a fresh authentication attempt (as in |
| 922 // first-use case) or from a previous attempt stored in our UserSettings |
| 923 // (as in the steady-state), and the syncable::Directory has been opened, |
| 924 // meaning we are ready to accept changes. Protected by initialized_mutex_ |
| 925 // as it can get read/set by both the SyncerThread and the AuthWatcherThread. |
| 926 bool initialized_; |
| 927 mutable PThreadMutex initialized_mutex_; |
| 928 }; |
| 929 |
| 930 SyncManager::SyncManager() { |
| 931 data_ = new SyncInternal(this); |
| 932 } |
| 933 |
| 934 bool SyncManager::Init(const sync_char16* database_location, |
| 935 const char* sync_server_and_path, |
| 936 int sync_server_port, |
| 937 const char* gaia_service_id, |
| 938 const char* gaia_source, |
| 939 bool use_ssl, |
| 940 HttpPostProviderFactory* post_factory, |
| 941 HttpPostProviderFactory* auth_post_factory, |
| 942 ModelSafeWorkerInterface* model_safe_worker, |
| 943 bool attempt_last_user_authentication, |
| 944 const char* user_agent) { |
| 945 DCHECK(database_location); |
| 946 DCHECK(post_factory); |
| 947 |
| 948 PathString db_path; |
| 949 String16ToPathString(database_location, &db_path); |
| 950 string server_string(sync_server_and_path); |
| 951 return data_->Init(db_path, |
| 952 server_string, |
| 953 sync_server_port, |
| 954 gaia_service_id, |
| 955 gaia_source, |
| 956 use_ssl, |
| 957 post_factory, |
| 958 auth_post_factory, |
| 959 model_safe_worker, |
| 960 attempt_last_user_authentication, |
| 961 user_agent); |
| 962 } |
| 963 |
| 964 void SyncManager::Authenticate(const char* username, const char* password) { |
| 965 data_->Authenticate(std::string(username), std::string(password)); |
| 966 } |
| 967 |
| 968 const char* SyncManager::GetAuthenticatedUsername() { |
| 969 if (!data_) |
| 970 return NULL; |
| 971 return data_->GetAuthenticatedUsername(); |
| 972 } |
| 973 |
| 974 const char* SyncManager::SyncInternal::GetAuthenticatedUsername() { |
| 975 cached_auth_watcher_email_ = browser_sync::ToUTF8( |
| 976 username_for_share()).get_string(); |
| 977 return cached_auth_watcher_email_.c_str(); |
| 978 } |
| 979 |
| 980 bool SyncManager::SyncInternal::Init( |
| 981 const PathString& database_location, |
| 982 const std::string& sync_server_and_path, |
| 983 int port, |
| 984 const char* gaia_service_id, |
| 985 const char* gaia_source, |
| 986 bool use_ssl, HttpPostProviderFactory* post_factory, |
| 987 HttpPostProviderFactory* auth_post_factory, |
| 988 ModelSafeWorkerInterface* model_safe_worker, |
| 989 bool attempt_last_user_authentication, |
| 990 const char* user_agent) { |
| 991 |
| 992 if (!g_log_files_initialized) { |
| 993 // TODO(timsteele): Call InitLogFiles() or equivalent. |
| 994 g_log_files_initialized = true; |
| 995 } |
| 996 |
| 997 // Set up UserSettings, creating the db if necessary. We need this to |
| 998 // instantiate a URLFactory to give to the Syncer. |
| 999 PathString settings_db_file = AppendSlash(database_location) + |
| 1000 kBookmarkSyncUserSettingsDatabase; |
| 1001 user_settings_.reset(new UserSettings()); |
| 1002 if (!user_settings_->Init(settings_db_file)) |
| 1003 return false; |
| 1004 |
| 1005 share_.dir_manager.reset(new DirectoryManager(database_location)); |
| 1006 |
| 1007 string client_id = user_settings_->GetClientId(); |
| 1008 connection_manager_.reset(new SyncAPIServerConnectionManager( |
| 1009 sync_server_and_path, port, use_ssl, user_agent, client_id)); |
| 1010 |
| 1011 // TODO(timsteele): This is temporary windows crap needed to listen for |
| 1012 // network status changes. We should either pump this up to the embedder to |
| 1013 // do (and call us in CheckServerReachable, for ex), or at least make this |
| 1014 // platform independent in here. |
| 1015 // TODO(ncarter): When this gets cleaned up, the implementation of |
| 1016 // CreatePThread can also be removed. |
| 1017 #if defined(OS_WINDOWS) |
| 1018 HANDLE exit_flag = CreateEvent(NULL, TRUE /*manual reset*/, FALSE, NULL); |
| 1019 address_watch_params_.exit_flag = exit_flag; |
| 1020 #endif |
| 1021 address_watch_params_.conn_mgr = connection_manager(); |
| 1022 address_watch_thread_ = CreatePThread(AddressWatchThread, |
| 1023 &address_watch_params_); |
| 1024 DCHECK(NULL != address_watch_thread_); |
| 1025 |
| 1026 // Hand over the bridged POST factory to be owned by the connection |
| 1027 // dir_manager. |
| 1028 connection_manager()->SetHttpPostProviderFactory(post_factory); |
| 1029 |
| 1030 // Watch various objects for aggregated status. |
| 1031 allstatus()->WatchConnectionManager(connection_manager()); |
| 1032 |
| 1033 std::string gaia_url = browser_sync::kGaiaUrl; |
| 1034 const char* service_id = gaia_service_id ? |
| 1035 gaia_service_id : SYNC_SERVICE_NAME; |
| 1036 |
| 1037 talk_mediator_.reset(new TalkMediatorImpl()); |
| 1038 allstatus()->WatchTalkMediator(talk_mediator()); |
| 1039 |
| 1040 BridgedGaiaAuthenticator* gaia_auth = new BridgedGaiaAuthenticator( |
| 1041 gaia_source, service_id, gaia_url, auth_post_factory); |
| 1042 |
| 1043 auth_watcher_.reset(new AuthWatcher(dir_manager(), |
| 1044 connection_manager(), |
| 1045 &allstatus_, |
| 1046 gaia_source, |
| 1047 service_id, |
| 1048 gaia_url, |
| 1049 user_settings_.get(), |
| 1050 gaia_auth, |
| 1051 talk_mediator())); |
| 1052 |
| 1053 talk_mediator()->WatchAuthWatcher(auth_watcher()); |
| 1054 allstatus()->WatchAuthWatcher(auth_watcher()); |
| 1055 authwatcher_hookup_.reset(NewEventListenerHookup(auth_watcher_->channel(), |
| 1056 this, &SyncInternal::HandleAuthWatcherEvent)); |
| 1057 |
| 1058 // Tell the SyncerThread to use the ModelSafeWorker for bookmark model work. |
| 1059 // We set up both sides of the "bridge" here, with the ModelSafeWorkerBridge |
| 1060 // on the Syncer side, and |model_safe_worker| on the API client side. |
| 1061 ModelSafeWorkerBridge* worker = new ModelSafeWorkerBridge(model_safe_worker); |
| 1062 |
| 1063 syncer_thread_.reset(new SyncerThread(&command_channel_, |
| 1064 dir_manager(), |
| 1065 connection_manager(), |
| 1066 &allstatus_, |
| 1067 worker)); |
| 1068 syncer_thread()->WatchTalkMediator(talk_mediator()); |
| 1069 allstatus()->WatchSyncerThread(syncer_thread()); |
| 1070 |
| 1071 syncer_thread()->Start(); // Start the syncer thread. This won't actually |
| 1072 // result in any syncing until at least the |
| 1073 // DirectoryManager broadcasts the OPENED event, |
| 1074 // and a valid server connection is detected. |
| 1075 |
| 1076 if (attempt_last_user_authentication) |
| 1077 AuthenticateForLastKnownUser(); |
| 1078 return true; |
| 1079 } |
| 1080 |
| 1081 void SyncManager::SyncInternal::MarkAndNotifyInitializationComplete() { |
| 1082 // There is only one real time we need this mutex. If we get an auth |
| 1083 // success, and before the initial sync ends we get an auth failure. In this |
| 1084 // case we'll be listening to both the AuthWatcher and Syncer, and it's a race |
| 1085 // between their respective threads to call MarkAndNotify. We need to make |
| 1086 // sure the observer is notified once and only once. |
| 1087 { |
| 1088 MutexLock lock(&initialized_mutex_); |
| 1089 if (initialized_) |
| 1090 return; |
| 1091 initialized_ = true; |
| 1092 } |
| 1093 |
| 1094 // Notify that initialization is complete. |
| 1095 if (observer_) |
| 1096 observer_->OnInitializationComplete(); |
| 1097 } |
| 1098 |
| 1099 void SyncManager::SyncInternal::Authenticate(const std::string& username, |
| 1100 const std::string& password) { |
| 1101 DCHECK(username_for_share().empty() || |
| 1102 (username == browser_sync::ToUTF8(username_for_share()).get_string())) |
| 1103 << "Username change from valid username detected"; |
| 1104 if (allstatus()->status().authenticated) |
| 1105 return; |
| 1106 if (password.empty()) { |
| 1107 // TODO(timsteele): Seems like this shouldn't be needed, but auth_watcher |
| 1108 // currently drops blank password attempts on the floor and doesn't update |
| 1109 // state; it only LOGs an error in this case. We want to make sure we set |
| 1110 // our AuthProblem state to denote an error. |
| 1111 RaiseAuthNeededEvent(); |
| 1112 } |
| 1113 auth_watcher()->Authenticate(username, password, true); |
| 1114 } |
| 1115 |
| 1116 void SyncManager::SyncInternal::AuthenticateForLastKnownUser() { |
| 1117 std::string username; |
| 1118 std::string auth_token; |
| 1119 if (!(auth_watcher()->settings()->GetLastUserAndServiceToken( |
| 1120 SYNC_SERVICE_NAME, &username, &auth_token))) { |
| 1121 RaiseAuthNeededEvent(); |
| 1122 return; |
| 1123 } |
| 1124 |
| 1125 browser_sync::ToPathString s(username); |
| 1126 if (s.good()) { |
| 1127 share_.authenticated_name = s.get_string16(); |
| 1128 } else { |
| 1129 RaiseAuthNeededEvent(); |
| 1130 return; |
| 1131 } |
| 1132 |
| 1133 // We optimize by opening the directory before the "fresh" authentication |
| 1134 // attempt completes so that we can immediately begin processing changes. |
| 1135 if (!dir_manager()->Open(username_for_share())) { |
| 1136 DCHECK(false) << "Had last known user but could not open directory"; |
| 1137 return; |
| 1138 } |
| 1139 |
| 1140 // Set the sync data type so that the server only sends us bookmarks |
| 1141 // changes. |
| 1142 { |
| 1143 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1144 if (!lookup.good()) { |
| 1145 DCHECK(false) << "ScopedDirLookup failed on successfully opened dir"; |
| 1146 return; |
| 1147 } |
| 1148 if (lookup->initial_sync_ended()) |
| 1149 MarkAndNotifyInitializationComplete(); |
| 1150 } |
| 1151 |
| 1152 auth_watcher()->AuthenticateWithToken(username, auth_token); |
| 1153 } |
| 1154 |
| 1155 void SyncManager::SyncInternal::RaiseAuthNeededEvent() { |
| 1156 auth_problem_ = AUTH_PROBLEM_INVALID_GAIA_CREDENTIALS; |
| 1157 if (observer_) |
| 1158 observer_->OnAuthProblem(auth_problem_); |
| 1159 } |
| 1160 |
| 1161 SyncManager::~SyncManager() { |
| 1162 delete data_; |
| 1163 } |
| 1164 |
| 1165 void SyncManager::SetObserver(Observer* observer) { |
| 1166 data_->set_observer(observer); |
| 1167 } |
| 1168 |
| 1169 void SyncManager::RemoveObserver() { |
| 1170 data_->set_observer(NULL); |
| 1171 } |
| 1172 |
| 1173 void SyncManager::Shutdown() { |
| 1174 data_->Shutdown(); |
| 1175 } |
| 1176 |
| 1177 void SyncManager::SyncInternal::Shutdown() { |
| 1178 // First reset the AuthWatcher in case an auth attempt is in progress so that |
| 1179 // it terminates gracefully before we shutdown and close other components. |
| 1180 // Otherwise the attempt can complete after we've closed the directory, for |
| 1181 // example, and cause initialization to continue, which is bad. |
| 1182 auth_watcher_.reset(); |
| 1183 |
| 1184 if (syncer_thread()) { |
| 1185 if (!syncer_thread()->Stop(kThreadExitTimeoutMsec)) |
| 1186 DCHECK(false) << "Unable to stop the syncer, it won't be happy..."; |
| 1187 } |
| 1188 |
| 1189 // Shutdown the xmpp buzz connection. |
| 1190 LOG(INFO) << "P2P: Mediator logout started."; |
| 1191 if (talk_mediator()) { |
| 1192 talk_mediator()->Logout(); |
| 1193 } |
| 1194 LOG(INFO) << "P2P: Mediator logout completed."; |
| 1195 |
| 1196 if (dir_manager()) { |
| 1197 dir_manager()->FinalSaveChangesForAll(); |
| 1198 dir_manager()->Close(username_for_share()); |
| 1199 } |
| 1200 |
| 1201 // Reset the DirectoryManager and UserSettings so they relinquish sqlite |
| 1202 // handles to backing files. |
| 1203 share_.dir_manager.reset(); |
| 1204 user_settings_.reset(); |
| 1205 |
| 1206 // We don't want to process any more events. |
| 1207 dir_change_hookup_.reset(); |
| 1208 syncer_event_.reset(); |
| 1209 authwatcher_hookup_.reset(); |
| 1210 |
| 1211 #if defined(OS_WINDOWS) |
| 1212 // Stop the address watch thread by signaling the exit flag. |
| 1213 // TODO(timsteele): Same as todo in Init(). |
| 1214 SetEvent(address_watch_params_.exit_flag); |
| 1215 const DWORD wait_result = WaitForSingleObject(address_watch_thread_, |
| 1216 kThreadExitTimeoutMsec); |
| 1217 LOG_IF(ERROR, WAIT_FAILED == wait_result) << "Waiting for addr change thread " |
| 1218 "to exit failed. GetLastError(): " << hex << GetLastError(); |
| 1219 LOG_IF(ERROR, WAIT_TIMEOUT == wait_result) << "Thread exit timeout expired"; |
| 1220 CloseHandle(address_watch_params_.exit_flag); |
| 1221 #endif |
| 1222 } |
| 1223 |
| 1224 // Listen to model changes, filter out ones initiated by the sync API, and |
| 1225 // saves the rest (hopefully just backend Syncer changes resulting from |
| 1226 // ApplyUpdates) to data_->changelist. |
| 1227 void SyncManager::SyncInternal::HandleChangeEvent( |
| 1228 const syncable::DirectoryChangeEvent& event) { |
| 1229 if (event.todo == syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE) { |
| 1230 HandleTransactionCompleteChangeEvent(event); |
| 1231 return; |
| 1232 } else if (event.todo == syncable::DirectoryChangeEvent::CALCULATE_CHANGES) { |
| 1233 if (event.writer == syncable::SYNCAPI) { |
| 1234 HandleCalculateChangesChangeEventFromSyncApi(event); |
| 1235 return; |
| 1236 } |
| 1237 HandleCalculateChangesChangeEventFromSyncer(event); |
| 1238 return; |
| 1239 } else if (event.todo == syncable::DirectoryChangeEvent::SHUTDOWN) { |
| 1240 dir_change_hookup_.reset(); |
| 1241 } |
| 1242 } |
| 1243 |
| 1244 void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent( |
| 1245 const syncable::DirectoryChangeEvent& event) { |
| 1246 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE); |
| 1247 // This notification happens immediately after a syncable WriteTransaction |
| 1248 // falls out of scope. |
| 1249 if (change_buffer_.IsEmpty() || !observer_) |
| 1250 return; |
| 1251 |
| 1252 ReadTransaction trans(GetUserShare()); |
| 1253 vector<ChangeRecord> ordered_changes; |
| 1254 change_buffer_.GetAllChangesInTreeOrder(&trans, &ordered_changes); |
| 1255 if (!ordered_changes.empty()) { |
| 1256 observer_->OnChangesApplied(&trans, &ordered_changes[0], |
| 1257 ordered_changes.size()); |
| 1258 } |
| 1259 change_buffer_.Clear(); |
| 1260 } |
| 1261 |
| 1262 void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( |
| 1263 const syncable::DirectoryChangeEvent& event) { |
| 1264 // We have been notified about a user action changing the bookmark model. |
| 1265 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES); |
| 1266 DCHECK_EQ(event.writer, syncable::SYNCAPI); |
| 1267 LOG_IF(WARNING, !change_buffer_.IsEmpty()) << |
| 1268 "CALCULATE_CHANGES called with unapplied old changes."; |
| 1269 |
| 1270 bool exists_unsynced_items = false; |
| 1271 for (syncable::OriginalEntries::const_iterator i = event.originals->begin(); |
| 1272 i != event.originals->end() && !exists_unsynced_items; |
| 1273 ++i) { |
| 1274 int64 id = i->ref(syncable::META_HANDLE); |
| 1275 syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id); |
| 1276 DCHECK(e.good()); |
| 1277 |
| 1278 if (e.IsRoot()) { |
| 1279 // Ignore root object, should it ever change. |
| 1280 continue; |
| 1281 } else if (!e.Get(syncable::IS_BOOKMARK_OBJECT)) { |
| 1282 // Ignore non-bookmark objects. |
| 1283 continue; |
| 1284 } else if (e.Get(syncable::IS_UNSYNCED)) { |
| 1285 // Unsynced items will cause us to nudge the the syncer. |
| 1286 exists_unsynced_items = true; |
| 1287 } |
| 1288 } |
| 1289 if (exists_unsynced_items && syncer_thread()) { |
| 1290 syncer_thread()->NudgeSyncer(200, SyncerThread::kLocal); // 1/5 a second. |
| 1291 } |
| 1292 } |
| 1293 |
| 1294 void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( |
| 1295 const syncable::DirectoryChangeEvent& event) { |
| 1296 // We only expect one notification per sync step, so change_buffer_ should |
| 1297 // contain no pending entries. |
| 1298 DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES); |
| 1299 DCHECK_EQ(event.writer, syncable::SYNCER); |
| 1300 LOG_IF(WARNING, !change_buffer_.IsEmpty()) << |
| 1301 "CALCULATE_CHANGES called with unapplied old changes."; |
| 1302 |
| 1303 for (syncable::OriginalEntries::const_iterator i = event.originals->begin(); |
| 1304 i != event.originals->end(); ++i) { |
| 1305 int64 id = i->ref(syncable::META_HANDLE); |
| 1306 syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id); |
| 1307 bool existed_before = !i->ref(syncable::IS_DEL); |
| 1308 bool exists_now = e.good() && !e.Get(syncable::IS_DEL); |
| 1309 DCHECK(e.good()); |
| 1310 |
| 1311 // Ignore root object, should it ever change. |
| 1312 if (e.IsRoot()) |
| 1313 continue; |
| 1314 // Ignore non-bookmark objects. |
| 1315 if (!e.Get(syncable::IS_BOOKMARK_OBJECT)) |
| 1316 continue; |
| 1317 |
| 1318 if (exists_now && !existed_before) |
| 1319 change_buffer_.PushAddedItem(id); |
| 1320 else if (!exists_now && existed_before) |
| 1321 change_buffer_.PushDeletedItem(id); |
| 1322 else if (exists_now && existed_before && BookmarkPropertiesDiffer(*i, e)) |
| 1323 change_buffer_.PushUpdatedItem(id, BookmarkPositionsDiffer(*i, e)); |
| 1324 } |
| 1325 } |
| 1326 |
| 1327 SyncManager::Status::Summary |
| 1328 SyncManager::SyncInternal::ComputeAggregatedStatusSummary() { |
| 1329 switch (allstatus()->status().icon) { |
| 1330 case AllStatus::OFFLINE: |
| 1331 return Status::OFFLINE; |
| 1332 case AllStatus::OFFLINE_UNSYNCED: |
| 1333 return Status::OFFLINE_UNSYNCED; |
| 1334 case AllStatus::SYNCING: |
| 1335 return Status::SYNCING; |
| 1336 case AllStatus::READY: |
| 1337 return Status::READY; |
| 1338 case AllStatus::CONFLICT: |
| 1339 return Status::CONFLICT; |
| 1340 case AllStatus::OFFLINE_UNUSABLE: |
| 1341 return Status::OFFLINE_UNUSABLE; |
| 1342 default: |
| 1343 return Status::INVALID; |
| 1344 } |
| 1345 } |
| 1346 |
| 1347 SyncManager::Status SyncManager::SyncInternal::ComputeAggregatedStatus() { |
| 1348 Status return_status = |
| 1349 { ComputeAggregatedStatusSummary(), |
| 1350 allstatus()->status().authenticated, |
| 1351 allstatus()->status().server_up, |
| 1352 allstatus()->status().server_reachable, |
| 1353 allstatus()->status().server_broken, |
| 1354 allstatus()->status().notifications_enabled, |
| 1355 allstatus()->status().notifications_received, |
| 1356 allstatus()->status().notifications_sent, |
| 1357 allstatus()->status().unsynced_count, |
| 1358 allstatus()->status().conflicting_count, |
| 1359 allstatus()->status().syncing, |
| 1360 allstatus()->status().initial_sync_ended, |
| 1361 allstatus()->status().syncer_stuck, |
| 1362 allstatus()->status().updates_available, |
| 1363 allstatus()->status().updates_received, |
| 1364 allstatus()->status().disk_full, |
| 1365 allstatus()->status().max_consecutive_errors}; |
| 1366 return return_status; |
| 1367 } |
| 1368 |
| 1369 void SyncManager::SyncInternal::HandleSyncerEvent(const SyncerEvent& event) { |
| 1370 if (!initialized()) { |
| 1371 // We get here if A) We have successfully authenticated at least once ( |
| 1372 // because we attach HandleSyncerEvent only once we receive notification of |
| 1373 // successful authentication [locally or otherwise]), but B) the initial |
| 1374 // sync had not completed at that time. |
| 1375 if (SyncerStatus(event.last_session).IsShareUsable()) |
| 1376 MarkAndNotifyInitializationComplete(); |
| 1377 return; |
| 1378 } |
| 1379 |
| 1380 if (!observer_) |
| 1381 return; |
| 1382 |
| 1383 // Only send an event if this is due to a cycle ending and this cycle |
| 1384 // concludes a canonical "sync" process; that is, based on what is known |
| 1385 // locally we are "all happy" and up-to-date. There may be new changes on |
| 1386 // the server, but we'll get them on a subsequent sync. |
| 1387 // |
| 1388 // Notifications are sent at the end of every sync cycle, regardless of |
| 1389 // whether we should sync again. |
| 1390 if (event.what_happened == SyncerEvent::SYNC_CYCLE_ENDED) { |
| 1391 if (!event.last_session->ShouldSyncAgain()) { |
| 1392 observer_->OnSyncCycleCompleted(); |
| 1393 } |
| 1394 |
| 1395 // TODO(chron): Consider changing this back to track ShouldSyncAgain |
| 1396 // Only notify peers if a commit has occurred and change the bookmark model. |
| 1397 if (event.last_session && event.last_session->items_committed()) { |
| 1398 notification_pending_ = true; |
| 1399 } |
| 1400 |
| 1401 // SyncCycles are started by the following events: creation of the syncer, |
| 1402 // (re)connection to buzz, local changes, peer notifications of updates. |
| 1403 // Peers will be notified of changes made while there is no buzz connection |
| 1404 // immediately after a connection has been re-established. |
| 1405 // the next sync cycle. |
| 1406 // TODO(brg): Move this to TalkMediatorImpl as a SyncerThread event hook. |
| 1407 if (notification_pending_ && talk_mediator()) { |
| 1408 LOG(INFO) << "Sending XMPP notification..."; |
| 1409 bool success = talk_mediator()->SendNotification(); |
| 1410 if (success) { |
| 1411 notification_pending_ = false; |
| 1412 } |
| 1413 } else { |
| 1414 LOG(INFO) << "Didn't send XMPP notification!" |
| 1415 << " event.last_session: " << event.last_session |
| 1416 << " event.last_session->items_committed(): " |
| 1417 << event.last_session->items_committed() |
| 1418 << " talk_mediator(): " << talk_mediator(); |
| 1419 } |
| 1420 } |
| 1421 } |
| 1422 |
| 1423 void SyncManager::SyncInternal::HandleAuthWatcherEvent( |
| 1424 const AuthWatcherEvent& event) { |
| 1425 // We don't care about an authentication attempt starting event, and we |
| 1426 // don't want to reset our state to AUTH_PROBLEM_NONE because the fact that |
| 1427 // an _attempt_ is starting doesn't change the fact that we have an auth |
| 1428 // problem. |
| 1429 if (event.what_happened == AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START) |
| 1430 return; |
| 1431 // We clear our last auth problem cache on new auth watcher events, and only |
| 1432 // set it to indicate a problem state for certain AuthWatcherEvent types. |
| 1433 auth_problem_ = AUTH_PROBLEM_NONE; |
| 1434 switch (event.what_happened) { |
| 1435 case AuthWatcherEvent::AUTH_SUCCEEDED: |
| 1436 // We now know the supplied username and password were valid. If this |
| 1437 // wasn't the first sync, authenticated_name should already be assigned. |
| 1438 if (username_for_share().empty()) { |
| 1439 browser_sync::ToPathString s(event.user_email); |
| 1440 if (s.good()) |
| 1441 share_.authenticated_name = s.get_string16(); |
| 1442 } |
| 1443 |
| 1444 DCHECK(LowerCaseEqualsASCII(browser_sync::ToUTF8( |
| 1445 username_for_share()).get_string(), |
| 1446 StringToLowerASCII(event.user_email).c_str())) |
| 1447 << "username_for_share= " |
| 1448 << browser_sync::ToUTF8(username_for_share()) |
| 1449 << ", event.user_email= " << event.user_email; |
| 1450 |
| 1451 if (observer_) |
| 1452 observer_->OnAuthProblem(AUTH_PROBLEM_NONE); |
| 1453 |
| 1454 // Hook up the DirectoryChangeEvent listener, HandleChangeEvent. |
| 1455 { |
| 1456 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1457 if (!lookup.good()) { |
| 1458 DCHECK(false) << "ScopedDirLookup creation failed; unable to hook " |
| 1459 << "up directory change event listener!"; |
| 1460 return; |
| 1461 } |
| 1462 dir_change_hookup_.reset(NewEventListenerHookup( |
| 1463 lookup->changes_channel(), this, |
| 1464 &SyncInternal::HandleChangeEvent)); |
| 1465 |
| 1466 if (lookup->initial_sync_ended()) |
| 1467 MarkAndNotifyInitializationComplete(); |
| 1468 } |
| 1469 { |
| 1470 // Start watching the syncer channel directly here. |
| 1471 DCHECK(syncer_thread() != NULL); |
| 1472 syncer_event_.reset(NewEventListenerHookup(syncer_thread()->channel(), |
| 1473 this, &SyncInternal::HandleSyncerEvent)); |
| 1474 } |
| 1475 return; |
| 1476 // Authentication failures translate to Status::AuthProblem events. |
| 1477 case AuthWatcherEvent::GAIA_AUTH_FAILED: // Invalid GAIA credentials. |
| 1478 case AuthWatcherEvent::SERVICE_AUTH_FAILED: // Expired GAIA credentials. |
| 1479 auth_problem_ = AUTH_PROBLEM_INVALID_GAIA_CREDENTIALS; |
| 1480 break; |
| 1481 case AuthWatcherEvent::SERVICE_USER_NOT_SIGNED_UP: |
| 1482 auth_problem_ = AUTH_PROBLEM_USER_NOT_SIGNED_UP; |
| 1483 break; |
| 1484 case AuthWatcherEvent::SERVICE_CONNECTION_FAILED: |
| 1485 auth_problem_ = AUTH_PROBLEM_CONNECTION_FAILED; |
| 1486 break; |
| 1487 default: // We don't care about the many other AuthWatcherEvent types. |
| 1488 return; |
| 1489 } |
| 1490 |
| 1491 // Fire notification that the status changed due to an authentication error. |
| 1492 if (observer_) |
| 1493 observer_->OnAuthProblem(auth_problem_); |
| 1494 } |
| 1495 |
| 1496 SyncManager::Status::Summary SyncManager::GetStatusSummary() const { |
| 1497 return data_->ComputeAggregatedStatusSummary(); |
| 1498 } |
| 1499 |
| 1500 SyncManager::Status SyncManager::GetDetailedStatus() const { |
| 1501 return data_->ComputeAggregatedStatus(); |
| 1502 } |
| 1503 |
| 1504 SyncManager::SyncInternal* SyncManager::GetImpl() const { return data_; } |
| 1505 |
| 1506 void SyncManager::SaveChanges() { |
| 1507 data_->SaveChanges(); |
| 1508 } |
| 1509 |
| 1510 void SyncManager::SyncInternal::SaveChanges() { |
| 1511 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1512 if (!lookup.good()) { |
| 1513 DCHECK(false) << "ScopedDirLookup creation failed; Unable to SaveChanges"; |
| 1514 return; |
| 1515 } |
| 1516 lookup->SaveChanges(); |
| 1517 } |
| 1518 |
| 1519 void SyncManager::SetupForTestMode(const sync_char16* test_username) { |
| 1520 DCHECK(data_) << "SetupForTestMode requires initialization"; |
| 1521 data_->SetupForTestMode(test_username); |
| 1522 } |
| 1523 |
| 1524 void SyncManager::SyncInternal::SetupForTestMode( |
| 1525 const sync_char16* test_username) { |
| 1526 String16ToPathString(test_username, &share_.authenticated_name); |
| 1527 |
| 1528 if (!dir_manager()->Open(username_for_share())) |
| 1529 DCHECK(false) << "Could not open directory when running in test mode"; |
| 1530 |
| 1531 // Hook up the DirectoryChangeEvent listener, HandleChangeEvent. |
| 1532 { |
| 1533 syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); |
| 1534 if (!lookup.good()) { |
| 1535 DCHECK(false) << "ScopedDirLookup creation failed; unable to hook " |
| 1536 << "up directory change event listener!"; |
| 1537 return; |
| 1538 } |
| 1539 dir_change_hookup_.reset(NewEventListenerHookup( |
| 1540 lookup->changes_channel(), this, |
| 1541 &SyncInternal::HandleChangeEvent)); |
| 1542 } |
| 1543 MarkAndNotifyInitializationComplete(); |
| 1544 } |
| 1545 |
| 1546 ////////////////////////////////////////////////////////////////////////// |
| 1547 // BaseTransaction member definitions |
| 1548 BaseTransaction::BaseTransaction(UserShare* share) |
| 1549 : lookup_(NULL) { |
| 1550 DCHECK(share && share->dir_manager.get()); |
| 1551 lookup_ = new syncable::ScopedDirLookup(share->dir_manager.get(), |
| 1552 share->authenticated_name); |
| 1553 if (!(lookup_->good())) |
| 1554 DCHECK(false) << "ScopedDirLookup failed on valid DirManager."; |
| 1555 } |
| 1556 BaseTransaction::~BaseTransaction() { |
| 1557 delete lookup_; |
| 1558 } |
| 1559 |
| 1560 UserShare* SyncManager::GetUserShare() const { |
| 1561 DCHECK(data_->initialized()) << "GetUserShare requires initialization!"; |
| 1562 return data_->GetUserShare(); |
| 1563 } |
| 1564 |
| 1565 } // namespace sync_api |
OLD | NEW |