| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2011 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/internal_api/write_node.h" |
| 6 |
| 7 #include "base/json/json_writer.h" |
| 8 #include "base/utf_string_conversions.h" |
| 9 #include "base/values.h" |
| 10 #include "chrome/browser/sync/engine/nigori_util.h" |
| 11 #include "chrome/browser/sync/engine/syncapi_internal.h" |
| 12 #include "chrome/browser/sync/internal_api/base_transaction.h" |
| 13 #include "chrome/browser/sync/internal_api/write_transaction.h" |
| 14 #include "chrome/browser/sync/protocol/app_specifics.pb.h" |
| 15 #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" |
| 16 #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" |
| 17 #include "chrome/browser/sync/protocol/extension_specifics.pb.h" |
| 18 #include "chrome/browser/sync/protocol/password_specifics.pb.h" |
| 19 #include "chrome/browser/sync/protocol/session_specifics.pb.h" |
| 20 #include "chrome/browser/sync/protocol/theme_specifics.pb.h" |
| 21 #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" |
| 22 #include "chrome/browser/sync/syncable/syncable.h" |
| 23 #include "chrome/browser/sync/util/cryptographer.h" |
| 24 |
| 25 using browser_sync::Cryptographer; |
| 26 using std::string; |
| 27 using std::vector; |
| 28 using syncable::kEncryptedString; |
| 29 using syncable::SPECIFICS; |
| 30 |
| 31 namespace sync_api { |
| 32 |
| 33 static const char kDefaultNameForNewNodes[] = " "; |
| 34 |
| 35 ////////////////////////////////////////////////////////////////////////// |
| 36 // Static helper functions. |
| 37 |
| 38 // When taking a name from the syncapi, append a space if it matches the |
| 39 // pattern of a server-illegal name followed by zero or more spaces. |
| 40 static void SyncAPINameToServerName(const std::wstring& sync_api_name, |
| 41 std::string* out) { |
| 42 *out = WideToUTF8(sync_api_name); |
| 43 if (IsNameServerIllegalAfterTrimming(*out)) |
| 44 out->append(" "); |
| 45 } |
| 46 |
| 47 bool WriteNode::UpdateEntryWithEncryption( |
| 48 browser_sync::Cryptographer* cryptographer, |
| 49 const sync_pb::EntitySpecifics& new_specifics, |
| 50 syncable::MutableEntry* entry) { |
| 51 syncable::ModelType type = syncable::GetModelTypeFromSpecifics(new_specifics); |
| 52 DCHECK_GE(type, syncable::FIRST_REAL_MODEL_TYPE); |
| 53 syncable::ModelTypeSet encrypted_types = cryptographer->GetEncryptedTypes(); |
| 54 |
| 55 sync_pb::EntitySpecifics generated_specifics; |
| 56 if (type == syncable::PASSWORDS || // Has own encryption scheme. |
| 57 type == syncable::NIGORI || // Encrypted separately. |
| 58 encrypted_types.count(type) == 0 || |
| 59 new_specifics.has_encrypted()) { |
| 60 // No encryption required. |
| 61 generated_specifics.CopyFrom(new_specifics); |
| 62 } else { |
| 63 // Encrypt new_specifics into generated_specifics. |
| 64 if (VLOG_IS_ON(2)) { |
| 65 scoped_ptr<DictionaryValue> value(entry->ToValue()); |
| 66 std::string info; |
| 67 base::JSONWriter::Write(value.get(), true, &info); |
| 68 VLOG(2) << "Encrypting specifics of type " |
| 69 << syncable::ModelTypeToString(type) |
| 70 << " with content: " |
| 71 << info; |
| 72 } |
| 73 if (!cryptographer->is_initialized()) |
| 74 return false; |
| 75 syncable::AddDefaultExtensionValue(type, &generated_specifics); |
| 76 if (!cryptographer->Encrypt(new_specifics, |
| 77 generated_specifics.mutable_encrypted())) { |
| 78 NOTREACHED() << "Could not encrypt data for node of type " |
| 79 << syncable::ModelTypeToString(type); |
| 80 return false; |
| 81 } |
| 82 } |
| 83 |
| 84 const sync_pb::EntitySpecifics& old_specifics = entry->Get(SPECIFICS); |
| 85 if (AreSpecificsEqual(cryptographer, old_specifics, generated_specifics)) { |
| 86 // Even if the data is the same but the old specifics are encrypted with an |
| 87 // old key, we should go ahead and re-encrypt with the new key. |
| 88 if ((!old_specifics.has_encrypted() && |
| 89 !generated_specifics.has_encrypted()) || |
| 90 cryptographer->CanDecryptUsingDefaultKey(old_specifics.encrypted())) { |
| 91 VLOG(2) << "Specifics of type " << syncable::ModelTypeToString(type) |
| 92 << " already match, dropping change."; |
| 93 return true; |
| 94 } |
| 95 // TODO(zea): Add some way to keep track of how often we're reencrypting |
| 96 // because of a passphrase change. |
| 97 } |
| 98 |
| 99 if (generated_specifics.has_encrypted()) { |
| 100 // Overwrite the possibly sensitive non-specifics data. |
| 101 entry->Put(syncable::NON_UNIQUE_NAME, kEncryptedString); |
| 102 // For bookmarks we actually put bogus data into the unencrypted specifics, |
| 103 // else the server will try to do it for us. |
| 104 if (type == syncable::BOOKMARKS) { |
| 105 sync_pb::BookmarkSpecifics* bookmark_specifics = |
| 106 generated_specifics.MutableExtension(sync_pb::bookmark); |
| 107 if (!entry->Get(syncable::IS_DIR)) |
| 108 bookmark_specifics->set_url(kEncryptedString); |
| 109 bookmark_specifics->set_title(kEncryptedString); |
| 110 } |
| 111 } |
| 112 entry->Put(syncable::SPECIFICS, generated_specifics); |
| 113 syncable::MarkForSyncing(entry); |
| 114 return true; |
| 115 } |
| 116 |
| 117 void WriteNode::SetIsFolder(bool folder) { |
| 118 if (entry_->Get(syncable::IS_DIR) == folder) |
| 119 return; // Skip redundant changes. |
| 120 |
| 121 entry_->Put(syncable::IS_DIR, folder); |
| 122 MarkForSyncing(); |
| 123 } |
| 124 |
| 125 void WriteNode::SetTitle(const std::wstring& title) { |
| 126 std::string server_legal_name; |
| 127 SyncAPINameToServerName(title, &server_legal_name); |
| 128 |
| 129 string old_name = entry_->Get(syncable::NON_UNIQUE_NAME); |
| 130 |
| 131 if (server_legal_name == old_name) |
| 132 return; // Skip redundant changes. |
| 133 |
| 134 // Only set NON_UNIQUE_NAME to the title if we're not encrypted. |
| 135 if (GetEntitySpecifics().has_encrypted()) |
| 136 entry_->Put(syncable::NON_UNIQUE_NAME, kEncryptedString); |
| 137 else |
| 138 entry_->Put(syncable::NON_UNIQUE_NAME, server_legal_name); |
| 139 |
| 140 // For bookmarks, we also set the title field in the specifics. |
| 141 // TODO(zea): refactor bookmarks to not need this functionality. |
| 142 if (GetModelType() == syncable::BOOKMARKS) { |
| 143 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); |
| 144 new_value.set_title(server_legal_name); |
| 145 SetBookmarkSpecifics(new_value); // Does it's own encryption checking. |
| 146 } |
| 147 |
| 148 MarkForSyncing(); |
| 149 } |
| 150 |
| 151 void WriteNode::SetURL(const GURL& url) { |
| 152 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); |
| 153 new_value.set_url(url.spec()); |
| 154 SetBookmarkSpecifics(new_value); |
| 155 } |
| 156 |
| 157 void WriteNode::SetAppSpecifics( |
| 158 const sync_pb::AppSpecifics& new_value) { |
| 159 sync_pb::EntitySpecifics entity_specifics; |
| 160 entity_specifics.MutableExtension(sync_pb::app)->CopyFrom(new_value); |
| 161 SetEntitySpecifics(entity_specifics); |
| 162 } |
| 163 |
| 164 void WriteNode::SetAutofillSpecifics( |
| 165 const sync_pb::AutofillSpecifics& new_value) { |
| 166 sync_pb::EntitySpecifics entity_specifics; |
| 167 entity_specifics.MutableExtension(sync_pb::autofill)->CopyFrom(new_value); |
| 168 SetEntitySpecifics(entity_specifics); |
| 169 } |
| 170 |
| 171 void WriteNode::SetAutofillProfileSpecifics( |
| 172 const sync_pb::AutofillProfileSpecifics& new_value) { |
| 173 sync_pb::EntitySpecifics entity_specifics; |
| 174 entity_specifics.MutableExtension(sync_pb::autofill_profile)-> |
| 175 CopyFrom(new_value); |
| 176 SetEntitySpecifics(entity_specifics); |
| 177 } |
| 178 |
| 179 void WriteNode::SetBookmarkSpecifics( |
| 180 const sync_pb::BookmarkSpecifics& new_value) { |
| 181 sync_pb::EntitySpecifics entity_specifics; |
| 182 entity_specifics.MutableExtension(sync_pb::bookmark)->CopyFrom(new_value); |
| 183 SetEntitySpecifics(entity_specifics); |
| 184 } |
| 185 |
| 186 void WriteNode::SetNigoriSpecifics( |
| 187 const sync_pb::NigoriSpecifics& new_value) { |
| 188 sync_pb::EntitySpecifics entity_specifics; |
| 189 entity_specifics.MutableExtension(sync_pb::nigori)->CopyFrom(new_value); |
| 190 SetEntitySpecifics(entity_specifics); |
| 191 } |
| 192 |
| 193 void WriteNode::SetPasswordSpecifics( |
| 194 const sync_pb::PasswordSpecificsData& data) { |
| 195 DCHECK_EQ(syncable::PASSWORDS, GetModelType()); |
| 196 |
| 197 Cryptographer* cryptographer = GetTransaction()->GetCryptographer(); |
| 198 |
| 199 // Idempotency check to prevent unnecessary syncing: if the plaintexts match |
| 200 // and the old ciphertext is encrypted with the most current key, there's |
| 201 // nothing to do here. Because each encryption is seeded with a different |
| 202 // random value, checking for equivalence post-encryption doesn't suffice. |
| 203 const sync_pb::EncryptedData& old_ciphertext = |
| 204 GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::password).encrypted(); |
| 205 scoped_ptr<sync_pb::PasswordSpecificsData> old_plaintext( |
| 206 DecryptPasswordSpecifics(GetEntry()->Get(SPECIFICS), cryptographer)); |
| 207 if (old_plaintext.get() && |
| 208 old_plaintext->SerializeAsString() == data.SerializeAsString() && |
| 209 cryptographer->CanDecryptUsingDefaultKey(old_ciphertext)) { |
| 210 return; |
| 211 } |
| 212 |
| 213 sync_pb::PasswordSpecifics new_value; |
| 214 if (!cryptographer->Encrypt(data, new_value.mutable_encrypted())) { |
| 215 NOTREACHED() << "Failed to encrypt password, possibly due to sync node " |
| 216 << "corruption"; |
| 217 return; |
| 218 } |
| 219 |
| 220 sync_pb::EntitySpecifics entity_specifics; |
| 221 entity_specifics.MutableExtension(sync_pb::password)->CopyFrom(new_value); |
| 222 SetEntitySpecifics(entity_specifics); |
| 223 } |
| 224 |
| 225 void WriteNode::SetThemeSpecifics( |
| 226 const sync_pb::ThemeSpecifics& new_value) { |
| 227 sync_pb::EntitySpecifics entity_specifics; |
| 228 entity_specifics.MutableExtension(sync_pb::theme)->CopyFrom(new_value); |
| 229 SetEntitySpecifics(entity_specifics); |
| 230 } |
| 231 |
| 232 void WriteNode::SetSessionSpecifics( |
| 233 const sync_pb::SessionSpecifics& new_value) { |
| 234 sync_pb::EntitySpecifics entity_specifics; |
| 235 entity_specifics.MutableExtension(sync_pb::session)->CopyFrom(new_value); |
| 236 SetEntitySpecifics(entity_specifics); |
| 237 } |
| 238 |
| 239 void WriteNode::SetEntitySpecifics( |
| 240 const sync_pb::EntitySpecifics& new_value) { |
| 241 syncable::ModelType new_specifics_type = |
| 242 syncable::GetModelTypeFromSpecifics(new_value); |
| 243 DCHECK_NE(new_specifics_type, syncable::UNSPECIFIED); |
| 244 VLOG(1) << "Writing entity specifics of type " |
| 245 << syncable::ModelTypeToString(new_specifics_type); |
| 246 // GetModelType() can be unspecified if this is the first time this |
| 247 // node is being initialized (see PutModelType()). Otherwise, it |
| 248 // should match |new_specifics_type|. |
| 249 if (GetModelType() != syncable::UNSPECIFIED) { |
| 250 DCHECK_EQ(new_specifics_type, GetModelType()); |
| 251 } |
| 252 browser_sync::Cryptographer* cryptographer = |
| 253 GetTransaction()->GetCryptographer(); |
| 254 |
| 255 // Preserve unknown fields. |
| 256 const sync_pb::EntitySpecifics& old_specifics = entry_->Get(SPECIFICS); |
| 257 sync_pb::EntitySpecifics new_specifics; |
| 258 new_specifics.CopyFrom(new_value); |
| 259 new_specifics.mutable_unknown_fields()->MergeFrom( |
| 260 old_specifics.unknown_fields()); |
| 261 |
| 262 // Will update the entry if encryption was necessary. |
| 263 if (!UpdateEntryWithEncryption(cryptographer, new_specifics, entry_)) { |
| 264 return; |
| 265 } |
| 266 if (entry_->Get(SPECIFICS).has_encrypted()) { |
| 267 // EncryptIfNecessary already updated the entry for us and marked for |
| 268 // syncing if it was needed. Now we just make a copy of the unencrypted |
| 269 // specifics so that if this node is updated, we do not have to decrypt the |
| 270 // old data. Note that this only modifies the node's local data, not the |
| 271 // entry itself. |
| 272 SetUnencryptedSpecifics(new_value); |
| 273 } |
| 274 |
| 275 DCHECK_EQ(new_specifics_type, GetModelType()); |
| 276 } |
| 277 |
| 278 void WriteNode::ResetFromSpecifics() { |
| 279 SetEntitySpecifics(GetEntitySpecifics()); |
| 280 } |
| 281 |
| 282 void WriteNode::SetTypedUrlSpecifics( |
| 283 const sync_pb::TypedUrlSpecifics& new_value) { |
| 284 sync_pb::EntitySpecifics entity_specifics; |
| 285 entity_specifics.MutableExtension(sync_pb::typed_url)->CopyFrom(new_value); |
| 286 SetEntitySpecifics(entity_specifics); |
| 287 } |
| 288 |
| 289 void WriteNode::SetExtensionSpecifics( |
| 290 const sync_pb::ExtensionSpecifics& new_value) { |
| 291 sync_pb::EntitySpecifics entity_specifics; |
| 292 entity_specifics.MutableExtension(sync_pb::extension)->CopyFrom(new_value); |
| 293 SetEntitySpecifics(entity_specifics); |
| 294 } |
| 295 |
| 296 void WriteNode::SetExternalId(int64 id) { |
| 297 if (GetExternalId() != id) |
| 298 entry_->Put(syncable::LOCAL_EXTERNAL_ID, id); |
| 299 } |
| 300 |
| 301 WriteNode::WriteNode(WriteTransaction* transaction) |
| 302 : entry_(NULL), transaction_(transaction) { |
| 303 DCHECK(transaction); |
| 304 } |
| 305 |
| 306 WriteNode::~WriteNode() { |
| 307 delete entry_; |
| 308 } |
| 309 |
| 310 // Find an existing node matching the ID |id|, and bind this WriteNode to it. |
| 311 // Return true on success. |
| 312 bool WriteNode::InitByIdLookup(int64 id) { |
| 313 DCHECK(!entry_) << "Init called twice"; |
| 314 DCHECK_NE(id, kInvalidId); |
| 315 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 316 syncable::GET_BY_HANDLE, id); |
| 317 return (entry_->good() && !entry_->Get(syncable::IS_DEL) && |
| 318 DecryptIfNecessary()); |
| 319 } |
| 320 |
| 321 // Find a node by client tag, and bind this WriteNode to it. |
| 322 // Return true if the write node was found, and was not deleted. |
| 323 // Undeleting a deleted node is possible by ClientTag. |
| 324 bool WriteNode::InitByClientTagLookup(syncable::ModelType model_type, |
| 325 const std::string& tag) { |
| 326 DCHECK(!entry_) << "Init called twice"; |
| 327 if (tag.empty()) |
| 328 return false; |
| 329 |
| 330 const std::string hash = GenerateSyncableHash(model_type, tag); |
| 331 |
| 332 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 333 syncable::GET_BY_CLIENT_TAG, hash); |
| 334 return (entry_->good() && !entry_->Get(syncable::IS_DEL) && |
| 335 DecryptIfNecessary()); |
| 336 } |
| 337 |
| 338 bool WriteNode::InitByTagLookup(const std::string& tag) { |
| 339 DCHECK(!entry_) << "Init called twice"; |
| 340 if (tag.empty()) |
| 341 return false; |
| 342 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 343 syncable::GET_BY_SERVER_TAG, tag); |
| 344 if (!entry_->good()) |
| 345 return false; |
| 346 if (entry_->Get(syncable::IS_DEL)) |
| 347 return false; |
| 348 syncable::ModelType model_type = GetModelType(); |
| 349 DCHECK_EQ(syncable::NIGORI, model_type); |
| 350 return true; |
| 351 } |
| 352 |
| 353 void WriteNode::PutModelType(syncable::ModelType model_type) { |
| 354 // Set an empty specifics of the appropriate datatype. The presence |
| 355 // of the specific extension will identify the model type. |
| 356 DCHECK(GetModelType() == model_type || |
| 357 GetModelType() == syncable::UNSPECIFIED); // Immutable once set. |
| 358 |
| 359 sync_pb::EntitySpecifics specifics; |
| 360 syncable::AddDefaultExtensionValue(model_type, &specifics); |
| 361 SetEntitySpecifics(specifics); |
| 362 } |
| 363 |
| 364 // Create a new node with default properties, and bind this WriteNode to it. |
| 365 // Return true on success. |
| 366 bool WriteNode::InitByCreation(syncable::ModelType model_type, |
| 367 const BaseNode& parent, |
| 368 const BaseNode* predecessor) { |
| 369 DCHECK(!entry_) << "Init called twice"; |
| 370 // |predecessor| must be a child of |parent| or NULL. |
| 371 if (predecessor && predecessor->GetParentId() != parent.GetId()) { |
| 372 DCHECK(false); |
| 373 return false; |
| 374 } |
| 375 |
| 376 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); |
| 377 |
| 378 // Start out with a dummy name. We expect |
| 379 // the caller to set a meaningful name after creation. |
| 380 string dummy(kDefaultNameForNewNodes); |
| 381 |
| 382 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 383 syncable::CREATE, parent_id, dummy); |
| 384 |
| 385 if (!entry_->good()) |
| 386 return false; |
| 387 |
| 388 // Entries are untitled folders by default. |
| 389 entry_->Put(syncable::IS_DIR, true); |
| 390 |
| 391 PutModelType(model_type); |
| 392 |
| 393 // Now set the predecessor, which sets IS_UNSYNCED as necessary. |
| 394 PutPredecessor(predecessor); |
| 395 |
| 396 return true; |
| 397 } |
| 398 |
| 399 // Create a new node with default properties and a client defined unique tag, |
| 400 // and bind this WriteNode to it. |
| 401 // Return true on success. If the tag exists in the database, then |
| 402 // we will attempt to undelete the node. |
| 403 // TODO(chron): Code datatype into hash tag. |
| 404 // TODO(chron): Is model type ever lost? |
| 405 bool WriteNode::InitUniqueByCreation(syncable::ModelType model_type, |
| 406 const BaseNode& parent, |
| 407 const std::string& tag) { |
| 408 DCHECK(!entry_) << "Init called twice"; |
| 409 |
| 410 const std::string hash = GenerateSyncableHash(model_type, tag); |
| 411 |
| 412 syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID); |
| 413 |
| 414 // Start out with a dummy name. We expect |
| 415 // the caller to set a meaningful name after creation. |
| 416 string dummy(kDefaultNameForNewNodes); |
| 417 |
| 418 // Check if we have this locally and need to undelete it. |
| 419 scoped_ptr<syncable::MutableEntry> existing_entry( |
| 420 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 421 syncable::GET_BY_CLIENT_TAG, hash)); |
| 422 |
| 423 if (existing_entry->good()) { |
| 424 if (existing_entry->Get(syncable::IS_DEL)) { |
| 425 // Rules for undelete: |
| 426 // BASE_VERSION: Must keep the same. |
| 427 // ID: Essential to keep the same. |
| 428 // META_HANDLE: Must be the same, so we can't "split" the entry. |
| 429 // IS_DEL: Must be set to false, will cause reindexing. |
| 430 // This one is weird because IS_DEL is true for "update only" |
| 431 // items. It should be OK to undelete an update only. |
| 432 // MTIME/CTIME: Seems reasonable to just leave them alone. |
| 433 // IS_UNSYNCED: Must set this to true or face database insurrection. |
| 434 // We do this below this block. |
| 435 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION |
| 436 // to SERVER_VERSION. We keep it the same here. |
| 437 // IS_DIR: We'll leave it the same. |
| 438 // SPECIFICS: Reset it. |
| 439 |
| 440 existing_entry->Put(syncable::IS_DEL, false); |
| 441 |
| 442 // Client tags are immutable and must be paired with the ID. |
| 443 // If a server update comes down with an ID and client tag combo, |
| 444 // and it already exists, always overwrite it and store only one copy. |
| 445 // We have to undelete entries because we can't disassociate IDs from |
| 446 // tags and updates. |
| 447 |
| 448 existing_entry->Put(syncable::NON_UNIQUE_NAME, dummy); |
| 449 existing_entry->Put(syncable::PARENT_ID, parent_id); |
| 450 entry_ = existing_entry.release(); |
| 451 } else { |
| 452 return false; |
| 453 } |
| 454 } else { |
| 455 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(), |
| 456 syncable::CREATE, parent_id, dummy); |
| 457 if (!entry_->good()) { |
| 458 return false; |
| 459 } |
| 460 |
| 461 // Only set IS_DIR for new entries. Don't bitflip undeleted ones. |
| 462 entry_->Put(syncable::UNIQUE_CLIENT_TAG, hash); |
| 463 } |
| 464 |
| 465 // We don't support directory and tag combinations. |
| 466 entry_->Put(syncable::IS_DIR, false); |
| 467 |
| 468 // Will clear specifics data. |
| 469 PutModelType(model_type); |
| 470 |
| 471 // Now set the predecessor, which sets IS_UNSYNCED as necessary. |
| 472 PutPredecessor(NULL); |
| 473 |
| 474 return true; |
| 475 } |
| 476 |
| 477 bool WriteNode::SetPosition(const BaseNode& new_parent, |
| 478 const BaseNode* predecessor) { |
| 479 // |predecessor| must be a child of |new_parent| or NULL. |
| 480 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) { |
| 481 DCHECK(false); |
| 482 return false; |
| 483 } |
| 484 |
| 485 syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID); |
| 486 |
| 487 // Filter out redundant changes if both the parent and the predecessor match. |
| 488 if (new_parent_id == entry_->Get(syncable::PARENT_ID)) { |
| 489 const syncable::Id& old = entry_->Get(syncable::PREV_ID); |
| 490 if ((!predecessor && old.IsRoot()) || |
| 491 (predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) { |
| 492 return true; |
| 493 } |
| 494 } |
| 495 |
| 496 // Atomically change the parent. This will fail if it would |
| 497 // introduce a cycle in the hierarchy. |
| 498 if (!entry_->Put(syncable::PARENT_ID, new_parent_id)) |
| 499 return false; |
| 500 |
| 501 // Now set the predecessor, which sets IS_UNSYNCED as necessary. |
| 502 PutPredecessor(predecessor); |
| 503 |
| 504 return true; |
| 505 } |
| 506 |
| 507 const syncable::Entry* WriteNode::GetEntry() const { |
| 508 return entry_; |
| 509 } |
| 510 |
| 511 const BaseTransaction* WriteNode::GetTransaction() const { |
| 512 return transaction_; |
| 513 } |
| 514 |
| 515 void WriteNode::Remove() { |
| 516 entry_->Put(syncable::IS_DEL, true); |
| 517 MarkForSyncing(); |
| 518 } |
| 519 |
| 520 void WriteNode::PutPredecessor(const BaseNode* predecessor) { |
| 521 syncable::Id predecessor_id = predecessor ? |
| 522 predecessor->GetEntry()->Get(syncable::ID) : syncable::Id(); |
| 523 entry_->PutPredecessor(predecessor_id); |
| 524 // Mark this entry as unsynced, to wake up the syncer. |
| 525 MarkForSyncing(); |
| 526 } |
| 527 |
| 528 void WriteNode::SetFaviconBytes(const vector<unsigned char>& bytes) { |
| 529 sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics(); |
| 530 new_value.set_favicon(bytes.empty() ? NULL : &bytes[0], bytes.size()); |
| 531 SetBookmarkSpecifics(new_value); |
| 532 } |
| 533 |
| 534 void WriteNode::MarkForSyncing() { |
| 535 syncable::MarkForSyncing(entry_); |
| 536 } |
| 537 |
| 538 } // namespace sync_api |
| OLD | NEW |