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