Infobar material design refresh: bg color
[chromium-blink-merge.git] / sync / internal_api / write_node.cc
blobdd358c41f9fad66a07befbaa4cc0acae467dc349
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.
5 #include "sync/internal_api/public/write_node.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "base/values.h"
10 #include "sync/internal_api/public/base_transaction.h"
11 #include "sync/internal_api/public/write_transaction.h"
12 #include "sync/internal_api/syncapi_internal.h"
13 #include "sync/protocol/bookmark_specifics.pb.h"
14 #include "sync/protocol/typed_url_specifics.pb.h"
15 #include "sync/syncable/mutable_entry.h"
16 #include "sync/syncable/nigori_util.h"
17 #include "sync/syncable/syncable_util.h"
18 #include "sync/util/cryptographer.h"
20 using std::string;
21 using std::vector;
23 namespace syncer {
25 using syncable::kEncryptedString;
26 using syncable::SPECIFICS;
28 static const char kDefaultNameForNewNodes[] = " ";
30 void WriteNode::SetIsFolder(bool folder) {
31 if (entry_->GetIsDir() == folder)
32 return; // Skip redundant changes.
34 entry_->PutIsDir(folder);
35 MarkForSyncing();
38 void WriteNode::SetTitle(const std::string& title) {
39 DCHECK_NE(GetModelType(), UNSPECIFIED);
40 ModelType type = GetModelType();
41 // It's possible the nigori lost the set of encrypted types. If the current
42 // specifics are already encrypted, we want to ensure we continue encrypting.
43 bool needs_encryption = GetTransaction()->GetEncryptedTypes().Has(type) ||
44 entry_->GetSpecifics().has_encrypted();
46 // If this datatype is encrypted and is not a bookmark, we disregard the
47 // specified title in favor of kEncryptedString. For encrypted bookmarks the
48 // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title
49 // into the specifics. All strings compared are server legal strings.
50 std::string new_legal_title;
51 if (type != BOOKMARKS && needs_encryption) {
52 new_legal_title = kEncryptedString;
53 } else {
54 DCHECK(base::IsStringUTF8(title));
55 SyncAPINameToServerName(title, &new_legal_title);
56 base::TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title);
59 std::string current_legal_title;
60 if (BOOKMARKS == type &&
61 entry_->GetSpecifics().has_encrypted()) {
62 // Encrypted bookmarks only have their title in the unencrypted specifics.
63 current_legal_title = GetBookmarkSpecifics().title();
64 } else {
65 // Non-bookmarks and legacy bookmarks (those with no title in their
66 // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks
67 // store their title in specifics as well as NON_UNIQUE_NAME.
68 current_legal_title = entry_->GetNonUniqueName();
71 bool title_matches = (current_legal_title == new_legal_title);
72 bool encrypted_without_overwriting_name = (needs_encryption &&
73 entry_->GetNonUniqueName() != kEncryptedString);
75 // For bookmarks, we also set the title field in the specifics.
76 // TODO(zea): refactor bookmarks to not need this functionality.
77 sync_pb::EntitySpecifics specifics = GetEntitySpecifics();
78 if (GetModelType() == BOOKMARKS &&
79 specifics.bookmark().title() != new_legal_title) {
80 specifics.mutable_bookmark()->set_title(new_legal_title);
81 SetEntitySpecifics(specifics); // Does it's own encryption checking.
82 title_matches = false;
85 // If the title matches and the NON_UNIQUE_NAME is properly overwritten as
86 // necessary, nothing needs to change.
87 if (title_matches && !encrypted_without_overwriting_name) {
88 DVLOG(2) << "Title matches, dropping change.";
89 return;
92 // For bookmarks, this has to happen after we set the title in the specifics,
93 // because the presence of a title in the NON_UNIQUE_NAME is what controls
94 // the logic deciding whether this is an empty node or a legacy bookmark.
95 // See BaseNode::GetUnencryptedSpecific(..).
96 if (needs_encryption)
97 entry_->PutNonUniqueName(kEncryptedString);
98 else
99 entry_->PutNonUniqueName(new_legal_title);
101 DVLOG(1) << "Overwriting title of type "
102 << ModelTypeToString(type)
103 << " and marking for syncing.";
104 MarkForSyncing();
107 void WriteNode::SetBookmarkSpecifics(
108 const sync_pb::BookmarkSpecifics& new_value) {
109 sync_pb::EntitySpecifics entity_specifics;
110 entity_specifics.mutable_bookmark()->CopyFrom(new_value);
111 SetEntitySpecifics(entity_specifics);
114 void WriteNode::SetNigoriSpecifics(
115 const sync_pb::NigoriSpecifics& new_value) {
116 sync_pb::EntitySpecifics entity_specifics;
117 entity_specifics.mutable_nigori()->CopyFrom(new_value);
118 SetEntitySpecifics(entity_specifics);
121 void WriteNode::SetPasswordSpecifics(
122 const sync_pb::PasswordSpecificsData& data) {
123 DCHECK_EQ(GetModelType(), PASSWORDS);
125 Cryptographer* cryptographer = GetTransaction()->GetCryptographer();
127 // We have to do the idempotency check here (vs in UpdateEntryWithEncryption)
128 // because Passwords have their encrypted data within the PasswordSpecifics,
129 // vs within the EntitySpecifics like all the other types.
130 const sync_pb::EntitySpecifics& old_specifics = GetEntry()->GetSpecifics();
131 sync_pb::EntitySpecifics entity_specifics;
132 // Copy over the old specifics if they exist.
133 if (GetModelTypeFromSpecifics(old_specifics) == PASSWORDS) {
134 entity_specifics.CopyFrom(old_specifics);
135 } else {
136 AddDefaultFieldValue(PASSWORDS, &entity_specifics);
138 sync_pb::PasswordSpecifics* password_specifics =
139 entity_specifics.mutable_password();
140 // This will only update password_specifics if the underlying unencrypted blob
141 // was different from |data| or was not encrypted with the proper passphrase.
142 if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) {
143 LOG(ERROR) << "Failed to encrypt password, possibly due to sync node "
144 << "corruption";
145 return;
147 SetEntitySpecifics(entity_specifics);
150 void WriteNode::SetEntitySpecifics(
151 const sync_pb::EntitySpecifics& new_value) {
152 ModelType new_specifics_type =
153 GetModelTypeFromSpecifics(new_value);
154 CHECK(!new_value.password().has_client_only_encrypted_data());
155 DCHECK_NE(new_specifics_type, UNSPECIFIED);
156 DVLOG(1) << "Writing entity specifics of type "
157 << ModelTypeToString(new_specifics_type);
158 DCHECK_EQ(new_specifics_type, GetModelType());
160 // Preserve unknown fields.
161 const sync_pb::EntitySpecifics& old_specifics = entry_->GetSpecifics();
162 sync_pb::EntitySpecifics new_specifics;
163 new_specifics.CopyFrom(new_value);
164 new_specifics.mutable_unknown_fields()->MergeFrom(
165 old_specifics.unknown_fields());
167 // Will update the entry if encryption was necessary.
168 if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
169 new_specifics,
170 entry_)) {
171 return;
173 if (entry_->GetSpecifics().has_encrypted()) {
174 // EncryptIfNecessary already updated the entry for us and marked for
175 // syncing if it was needed. Now we just make a copy of the unencrypted
176 // specifics so that if this node is updated, we do not have to decrypt the
177 // old data. Note that this only modifies the node's local data, not the
178 // entry itself.
179 SetUnencryptedSpecifics(new_value);
182 DCHECK_EQ(new_specifics_type, GetModelType());
185 void WriteNode::ResetFromSpecifics() {
186 SetEntitySpecifics(GetEntitySpecifics());
189 void WriteNode::SetTypedUrlSpecifics(
190 const sync_pb::TypedUrlSpecifics& new_value) {
191 sync_pb::EntitySpecifics entity_specifics;
192 entity_specifics.mutable_typed_url()->CopyFrom(new_value);
193 SetEntitySpecifics(entity_specifics);
196 void WriteNode::SetExternalId(int64 id) {
197 if (GetExternalId() != id)
198 entry_->PutLocalExternalId(id);
201 WriteNode::WriteNode(WriteTransaction* transaction)
202 : entry_(NULL), transaction_(transaction) {
203 DCHECK(transaction);
206 WriteNode::~WriteNode() {
207 delete entry_;
210 // Find an existing node matching the ID |id|, and bind this WriteNode to it.
211 // Return true on success.
212 BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64 id) {
213 DCHECK(!entry_) << "Init called twice";
214 DCHECK_NE(id, kInvalidId);
215 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
216 syncable::GET_BY_HANDLE, id);
217 if (!entry_->good())
218 return INIT_FAILED_ENTRY_NOT_GOOD;
219 if (entry_->GetIsDel())
220 return INIT_FAILED_ENTRY_IS_DEL;
221 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
224 // Find a node by client tag, and bind this WriteNode to it.
225 // Return true if the write node was found, and was not deleted.
226 // Undeleting a deleted node is possible by ClientTag.
227 BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup(
228 ModelType model_type,
229 const std::string& tag) {
230 DCHECK(!entry_) << "Init called twice";
231 if (tag.empty())
232 return INIT_FAILED_PRECONDITION;
234 const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
236 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
237 syncable::GET_BY_CLIENT_TAG, hash);
238 if (!entry_->good())
239 return INIT_FAILED_ENTRY_NOT_GOOD;
240 if (entry_->GetIsDel())
241 return INIT_FAILED_ENTRY_IS_DEL;
242 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
245 BaseNode::InitByLookupResult WriteNode::InitTypeRoot(ModelType type) {
246 DCHECK(!entry_) << "Init called twice";
247 if (!IsRealDataType(type))
248 return INIT_FAILED_PRECONDITION;
249 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
250 syncable::GET_TYPE_ROOT, type);
251 if (!entry_->good())
252 return INIT_FAILED_ENTRY_NOT_GOOD;
253 if (entry_->GetIsDel())
254 return INIT_FAILED_ENTRY_IS_DEL;
255 ModelType model_type = GetModelType();
256 DCHECK_EQ(model_type, NIGORI);
257 return INIT_OK;
260 // Create a new node with default properties, and bind this WriteNode to it.
261 // Return true on success.
262 bool WriteNode::InitBookmarkByCreation(const BaseNode& parent,
263 const BaseNode* predecessor) {
264 DCHECK(!entry_) << "Init called twice";
265 // |predecessor| must be a child of |parent| or NULL.
266 if (predecessor && predecessor->GetParentId() != parent.GetId()) {
267 DCHECK(false);
268 return false;
271 syncable::Id parent_id = parent.GetEntry()->GetId();
272 DCHECK(!parent_id.IsNull());
274 // Start out with a dummy name. We expect
275 // the caller to set a meaningful name after creation.
276 string dummy(kDefaultNameForNewNodes);
278 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
279 syncable::CREATE, BOOKMARKS,
280 parent_id, dummy);
282 if (!entry_->good())
283 return false;
285 // Entries are untitled folders by default.
286 entry_->PutIsDir(true);
288 if (!PutPredecessor(predecessor)) {
289 return false;
292 // Mark this entry as unsynced, to wake up the syncer.
293 MarkForSyncing();
294 return true;
297 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
298 ModelType model_type,
299 const BaseNode& parent,
300 const std::string& tag) {
301 return InitUniqueByCreationImpl(model_type, parent.GetEntry()->GetId(), tag);
304 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
305 ModelType model_type,
306 const std::string& tag) {
307 return InitUniqueByCreationImpl(model_type, syncable::Id(), tag);
310 // Create a new node with default properties and a client defined unique tag,
311 // and bind this WriteNode to it.
312 // Return true on success. If the tag exists in the database, then
313 // we will attempt to undelete the node.
314 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreationImpl(
315 ModelType model_type,
316 const syncable::Id& parent_id,
317 const std::string& tag) {
318 // This DCHECK will only fail if init is called twice.
319 DCHECK(!entry_);
320 if (tag.empty()) {
321 LOG(WARNING) << "InitUniqueByCreation failed due to empty tag.";
322 return INIT_FAILED_EMPTY_TAG;
325 const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
327 // Start out with a dummy name. We expect
328 // the caller to set a meaningful name after creation.
329 string dummy(kDefaultNameForNewNodes);
331 // Check if we have this locally and need to undelete it.
332 scoped_ptr<syncable::MutableEntry> existing_entry(
333 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
334 syncable::GET_BY_CLIENT_TAG, hash));
336 if (existing_entry->good()) {
337 if (existing_entry->GetIsDel()) {
338 // Rules for undelete:
339 // BASE_VERSION: Must keep the same.
340 // ID: Essential to keep the same.
341 // META_HANDLE: Must be the same, so we can't "split" the entry.
342 // IS_DEL: Must be set to false, will cause reindexing.
343 // This one is weird because IS_DEL is true for "update only"
344 // items. It should be OK to undelete an update only.
345 // MTIME/CTIME: Seems reasonable to just leave them alone.
346 // IS_UNSYNCED: Must set this to true or face database insurrection.
347 // We do this below this block.
348 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
349 // to SERVER_VERSION. We keep it the same here.
350 // IS_DIR: We'll leave it the same.
351 // SPECIFICS: Reset it.
353 // Put specifics to define the entry's model type to handle the case
354 // where this is not actually an undeletion, but instead a collision
355 // with a newly downloaded, processed, and unapplied server update.
356 // This should be done first before inserting the entry into the
357 // directory's ParentChildIndex by clearing its "deleted" flag below.
358 // This is a fix for http://crbug.com/505761.
359 sync_pb::EntitySpecifics specifics;
360 AddDefaultFieldValue(model_type, &specifics);
361 existing_entry->PutSpecifics(specifics);
363 existing_entry->PutIsDel(false);
365 // Client tags are immutable and must be paired with the ID.
366 // If a server update comes down with an ID and client tag combo,
367 // and it already exists, always overwrite it and store only one copy.
368 // We have to undelete entries because we can't disassociate IDs from
369 // tags and updates.
371 existing_entry->PutNonUniqueName(dummy);
372 existing_entry->PutParentId(parent_id);
373 } // Else just reuse the existing entry.
374 entry_ = existing_entry.release();
375 } else {
376 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
377 syncable::CREATE,
378 model_type, parent_id, dummy);
381 if (!entry_->good())
382 return INIT_FAILED_COULD_NOT_CREATE_ENTRY;
384 // Has no impact if the client tag is already set.
385 entry_->PutUniqueClientTag(hash);
387 // We don't support directory and tag combinations.
388 entry_->PutIsDir(false);
390 if (entry_->ShouldMaintainPosition()) {
391 if (!entry_->PutPredecessor(syncable::Id()))
392 return INIT_FAILED_SET_PREDECESSOR;
395 // Mark this entry as unsynced, to wake up the syncer.
396 MarkForSyncing();
398 return INIT_SUCCESS;
401 bool WriteNode::SetPosition(const BaseNode& new_parent,
402 const BaseNode* predecessor) {
403 // |predecessor| must be a child of |new_parent| or NULL.
404 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
405 DCHECK(false);
406 return false;
409 syncable::Id new_parent_id = new_parent.GetEntry()->GetId();
410 DCHECK(!new_parent_id.IsNull());
412 // Filter out redundant changes if both the parent and the predecessor match.
413 if (new_parent_id == entry_->GetParentId()) {
414 const syncable::Id& old = entry_->GetPredecessorId();
415 if ((!predecessor && old.IsNull()) ||
416 (predecessor && (old == predecessor->GetEntry()->GetId()))) {
417 return true;
421 entry_->PutParentId(new_parent_id);
423 if (!PutPredecessor(predecessor)) {
424 return false;
427 // Mark this entry as unsynced, to wake up the syncer.
428 MarkForSyncing();
429 return true;
432 void WriteNode::SetAttachmentMetadata(
433 const sync_pb::AttachmentMetadata& attachment_metadata) {
434 entry_->PutAttachmentMetadata(attachment_metadata);
437 const syncable::Entry* WriteNode::GetEntry() const {
438 return entry_;
441 const BaseTransaction* WriteNode::GetTransaction() const {
442 return transaction_;
445 syncable::MutableEntry* WriteNode::GetMutableEntryForTest() {
446 return entry_;
449 void WriteNode::Tombstone() {
450 // These lines must be in this order. The call to Put(IS_DEL) might choose to
451 // unset the IS_UNSYNCED bit if the item was not known to the server at the
452 // time of deletion. It's important that the bit not be reset in that case.
453 MarkForSyncing();
454 entry_->PutIsDel(true);
457 void WriteNode::Drop() {
458 if (entry_->GetId().ServerKnows()) {
459 entry_->PutIsDel(true);
463 bool WriteNode::PutPredecessor(const BaseNode* predecessor) {
464 DCHECK(!entry_->GetParentId().IsNull());
465 syncable::Id predecessor_id = predecessor ?
466 predecessor->GetEntry()->GetId() : syncable::Id();
467 return entry_->PutPredecessor(predecessor_id);
470 void WriteNode::MarkForSyncing() {
471 syncable::MarkForSyncing(entry_);
474 } // namespace syncer