Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(79)

Side by Side Diff: components/sync/core/write_node.cc

Issue 2407163004: [Sync] Move some directory-related things from core/ to syncable/. (Closed)
Patch Set: Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « components/sync/core/write_node.h ('k') | components/sync/core/write_transaction.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 "components/sync/core/write_node.h"
6
7 #include <memory>
8
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "components/sync/base/cryptographer.h"
13 #include "components/sync/core/base_transaction.h"
14 #include "components/sync/core/write_transaction.h"
15 #include "components/sync/core_impl/syncapi_internal.h"
16 #include "components/sync/protocol/bookmark_specifics.pb.h"
17 #include "components/sync/protocol/typed_url_specifics.pb.h"
18 #include "components/sync/syncable/mutable_entry.h"
19 #include "components/sync/syncable/nigori_util.h"
20 #include "components/sync/syncable/syncable_util.h"
21
22 using std::string;
23 using std::vector;
24
25 namespace syncer {
26
27 using syncable::kEncryptedString;
28 using syncable::SPECIFICS;
29
30 static const char kDefaultNameForNewNodes[] = " ";
31
32 void WriteNode::SetIsFolder(bool folder) {
33 if (entry_->GetIsDir() == folder)
34 return; // Skip redundant changes.
35
36 entry_->PutIsDir(folder);
37 MarkForSyncing();
38 }
39
40 void WriteNode::SetTitle(const std::string& title) {
41 DCHECK_NE(GetModelType(), UNSPECIFIED);
42 ModelType type = GetModelType();
43 // It's possible the nigori lost the set of encrypted types. If the current
44 // specifics are already encrypted, we want to ensure we continue encrypting.
45 bool needs_encryption = GetTransaction()->GetEncryptedTypes().Has(type) ||
46 entry_->GetSpecifics().has_encrypted();
47
48 // If this datatype is encrypted and is not a bookmark, we disregard the
49 // specified title in favor of kEncryptedString. For encrypted bookmarks the
50 // NON_UNIQUE_NAME will still be kEncryptedString, but we store the real title
51 // into the specifics. All strings compared are server legal strings.
52 std::string new_legal_title;
53 if (type != BOOKMARKS && needs_encryption) {
54 new_legal_title = kEncryptedString;
55 } else {
56 DCHECK(base::IsStringUTF8(title));
57 SyncAPINameToServerName(title, &new_legal_title);
58 base::TruncateUTF8ToByteSize(new_legal_title, 255, &new_legal_title);
59 }
60
61 std::string current_legal_title;
62 if (BOOKMARKS == type && entry_->GetSpecifics().has_encrypted()) {
63 // Encrypted bookmarks only have their title in the unencrypted specifics.
64 current_legal_title = GetBookmarkSpecifics().title();
65 } else {
66 // Non-bookmarks and legacy bookmarks (those with no title in their
67 // specifics) store their title in NON_UNIQUE_NAME. Non-legacy bookmarks
68 // store their title in specifics as well as NON_UNIQUE_NAME.
69 current_legal_title = entry_->GetNonUniqueName();
70 }
71
72 bool title_matches = (current_legal_title == new_legal_title);
73 bool encrypted_without_overwriting_name =
74 (needs_encryption && entry_->GetNonUniqueName() != kEncryptedString);
75
76 // For bookmarks, we also set the title field in the specifics.
77 // TODO(zea): refactor bookmarks to not need this functionality.
78 sync_pb::EntitySpecifics specifics = GetEntitySpecifics();
79 if (GetModelType() == BOOKMARKS &&
80 specifics.bookmark().title() != new_legal_title) {
81 specifics.mutable_bookmark()->set_title(new_legal_title);
82 SetEntitySpecifics(specifics); // Does it's own encryption checking.
83 title_matches = false;
84 }
85
86 // If the title matches and the NON_UNIQUE_NAME is properly overwritten as
87 // necessary, nothing needs to change.
88 if (title_matches && !encrypted_without_overwriting_name) {
89 DVLOG(2) << "Title matches, dropping change.";
90 return;
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_->PutNonUniqueName(kEncryptedString);
99 else
100 entry_->PutNonUniqueName(new_legal_title);
101
102 DVLOG(1) << "Overwriting title of type " << ModelTypeToString(type)
103 << " and marking for syncing.";
104 MarkForSyncing();
105 }
106
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);
112 }
113
114 void WriteNode::SetNigoriSpecifics(const sync_pb::NigoriSpecifics& new_value) {
115 sync_pb::EntitySpecifics entity_specifics;
116 entity_specifics.mutable_nigori()->CopyFrom(new_value);
117 SetEntitySpecifics(entity_specifics);
118 }
119
120 void WriteNode::SetPasswordSpecifics(
121 const sync_pb::PasswordSpecificsData& data) {
122 DCHECK_EQ(GetModelType(), PASSWORDS);
123
124 Cryptographer* cryptographer = GetTransaction()->GetCryptographer();
125
126 // We have to do the idempotency check here (vs in UpdateEntryWithEncryption)
127 // because Passwords have their encrypted data within the PasswordSpecifics,
128 // vs within the EntitySpecifics like all the other types.
129 const sync_pb::EntitySpecifics& old_specifics = GetEntitySpecifics();
130 sync_pb::EntitySpecifics entity_specifics;
131 // Copy over the old specifics if they exist.
132 if (GetModelTypeFromSpecifics(old_specifics) == PASSWORDS) {
133 entity_specifics.CopyFrom(old_specifics);
134 } else {
135 AddDefaultFieldValue(PASSWORDS, &entity_specifics);
136 }
137 sync_pb::PasswordSpecifics* password_specifics =
138 entity_specifics.mutable_password();
139 // This will only update password_specifics if the underlying unencrypted blob
140 // was different from |data| or was not encrypted with the proper passphrase.
141 if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) {
142 LOG(ERROR) << "Failed to encrypt password, possibly due to sync node "
143 << "corruption";
144 return;
145 }
146 SetEntitySpecifics(entity_specifics);
147 }
148
149 void WriteNode::SetEntitySpecifics(const sync_pb::EntitySpecifics& new_value) {
150 ModelType new_specifics_type = GetModelTypeFromSpecifics(new_value);
151 CHECK(!new_value.password().has_client_only_encrypted_data());
152 DCHECK_NE(new_specifics_type, UNSPECIFIED);
153 DVLOG(1) << "Writing entity specifics of type "
154 << ModelTypeToString(new_specifics_type);
155 DCHECK_EQ(new_specifics_type, GetModelType());
156
157 // Preserve unknown fields.
158 const sync_pb::EntitySpecifics& old_specifics = entry_->GetSpecifics();
159 sync_pb::EntitySpecifics new_specifics;
160 new_specifics.CopyFrom(new_value);
161 new_specifics.mutable_unknown_fields()->append(
162 old_specifics.unknown_fields());
163
164 // Will update the entry if encryption was necessary.
165 if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
166 new_specifics, entry_)) {
167 return;
168 }
169 if (entry_->GetSpecifics().has_encrypted()) {
170 // EncryptIfNecessary already updated the entry for us and marked for
171 // syncing if it was needed. Now we just make a copy of the unencrypted
172 // specifics so that if this node is updated, we do not have to decrypt the
173 // old data. Note that this only modifies the node's local data, not the
174 // entry itself.
175 SetUnencryptedSpecifics(new_value);
176 }
177
178 DCHECK_EQ(new_specifics_type, GetModelType());
179 }
180
181 void WriteNode::ResetFromSpecifics() {
182 SetEntitySpecifics(GetEntitySpecifics());
183 }
184
185 void WriteNode::SetTypedUrlSpecifics(
186 const sync_pb::TypedUrlSpecifics& new_value) {
187 sync_pb::EntitySpecifics entity_specifics;
188 entity_specifics.mutable_typed_url()->CopyFrom(new_value);
189 SetEntitySpecifics(entity_specifics);
190 }
191
192 void WriteNode::SetExternalId(int64_t id) {
193 if (GetExternalId() != id)
194 entry_->PutLocalExternalId(id);
195 }
196
197 WriteNode::WriteNode(WriteTransaction* transaction)
198 : entry_(NULL), transaction_(transaction) {
199 DCHECK(transaction);
200 }
201
202 WriteNode::~WriteNode() {
203 delete entry_;
204 }
205
206 // Find an existing node matching the ID |id|, and bind this WriteNode to it.
207 // Return true on success.
208 BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64_t id) {
209 DCHECK(!entry_) << "Init called twice";
210 DCHECK_NE(id, kInvalidId);
211 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
212 syncable::GET_BY_HANDLE, id);
213 if (!entry_->good())
214 return INIT_FAILED_ENTRY_NOT_GOOD;
215 if (entry_->GetIsDel())
216 return INIT_FAILED_ENTRY_IS_DEL;
217 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
218 }
219
220 // Find a node by client tag, and bind this WriteNode to it.
221 // Return true if the write node was found, and was not deleted.
222 // Undeleting a deleted node is possible by ClientTag.
223 BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup(
224 ModelType model_type,
225 const std::string& tag) {
226 DCHECK(!entry_) << "Init called twice";
227 if (tag.empty())
228 return INIT_FAILED_PRECONDITION;
229
230 const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
231
232 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
233 syncable::GET_BY_CLIENT_TAG, hash);
234 if (!entry_->good())
235 return INIT_FAILED_ENTRY_NOT_GOOD;
236 if (entry_->GetIsDel())
237 return INIT_FAILED_ENTRY_IS_DEL;
238 return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
239 }
240
241 BaseNode::InitByLookupResult WriteNode::InitTypeRoot(ModelType type) {
242 DCHECK(!entry_) << "Init called twice";
243 if (!IsRealDataType(type))
244 return INIT_FAILED_PRECONDITION;
245 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
246 syncable::GET_TYPE_ROOT, type);
247 if (!entry_->good())
248 return INIT_FAILED_ENTRY_NOT_GOOD;
249 if (entry_->GetIsDel())
250 return INIT_FAILED_ENTRY_IS_DEL;
251 ModelType model_type = GetModelType();
252 DCHECK_EQ(model_type, NIGORI);
253 return INIT_OK;
254 }
255
256 // Create a new node with default properties, and bind this WriteNode to it.
257 // Return true on success.
258 bool WriteNode::InitBookmarkByCreation(const BaseNode& parent,
259 const BaseNode* predecessor) {
260 DCHECK(!entry_) << "Init called twice";
261 // |predecessor| must be a child of |parent| or NULL.
262 if (predecessor && predecessor->GetParentId() != parent.GetId()) {
263 DCHECK(false);
264 return false;
265 }
266
267 syncable::Id parent_id = parent.GetSyncId();
268 DCHECK(!parent_id.IsNull());
269
270 // Start out with a dummy name. We expect
271 // the caller to set a meaningful name after creation.
272 string dummy(kDefaultNameForNewNodes);
273
274 entry_ =
275 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
276 syncable::CREATE, BOOKMARKS, parent_id, dummy);
277
278 if (!entry_->good())
279 return false;
280
281 // Entries are untitled folders by default.
282 entry_->PutIsDir(true);
283
284 if (!PutPredecessor(predecessor)) {
285 return false;
286 }
287
288 // Mark this entry as unsynced, to wake up the syncer.
289 MarkForSyncing();
290 return true;
291 }
292
293 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
294 ModelType model_type,
295 const BaseNode& parent,
296 const std::string& tag) {
297 return InitUniqueByCreationImpl(model_type, parent.GetSyncId(), tag);
298 }
299
300 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
301 ModelType model_type,
302 const std::string& tag) {
303 return InitUniqueByCreationImpl(model_type, syncable::Id(), tag);
304 }
305
306 // Create a new node with default properties and a client defined unique tag,
307 // and bind this WriteNode to it.
308 // Return true on success. If the tag exists in the database, then
309 // we will attempt to undelete the node.
310 WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreationImpl(
311 ModelType model_type,
312 const syncable::Id& parent_id,
313 const std::string& tag) {
314 // This DCHECK will only fail if init is called twice.
315 DCHECK(!entry_);
316 if (tag.empty()) {
317 LOG(WARNING) << "InitUniqueByCreation failed due to empty tag.";
318 return INIT_FAILED_EMPTY_TAG;
319 }
320
321 const std::string hash = syncable::GenerateSyncableHash(model_type, tag);
322
323 // Start out with a dummy name. We expect
324 // the caller to set a meaningful name after creation.
325 string dummy(kDefaultNameForNewNodes);
326
327 // Check if we have this locally and need to undelete it.
328 std::unique_ptr<syncable::MutableEntry> existing_entry(
329 new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
330 syncable::GET_BY_CLIENT_TAG, hash));
331
332 if (existing_entry->good()) {
333 bool entry_undeleted = false;
334 if (existing_entry->GetIsDel()) {
335 // Rules for undelete:
336 // BASE_VERSION: Must keep the same.
337 // ID: Essential to keep the same.
338 // META_HANDLE: Must be the same, so we can't "split" the entry.
339 // IS_DEL: Must be set to false, will cause reindexing.
340 // This one is weird because IS_DEL is true for "update only"
341 // items. It should be OK to undelete an update only.
342 // MTIME/CTIME: Seems reasonable to just leave them alone.
343 // IS_UNSYNCED: Must set this to true or face database insurrection.
344 // We do this below this block.
345 // IS_UNAPPLIED_UPDATE: Either keep it the same or also set BASE_VERSION
346 // to SERVER_VERSION. We keep it the same here.
347 // IS_DIR: We'll leave it the same.
348 // SPECIFICS: Reset it.
349
350 // Put specifics to define the entry's model type to handle the case
351 // where this is not actually an undeletion, but instead a collision
352 // with a newly downloaded, processed, and unapplied server update.
353 // This should be done first before inserting the entry into the
354 // directory's ParentChildIndex by clearing its "deleted" flag below.
355 // This is a fix for http://crbug.com/505761.
356 sync_pb::EntitySpecifics specifics;
357 AddDefaultFieldValue(model_type, &specifics);
358 existing_entry->PutSpecifics(specifics);
359
360 existing_entry->PutIsDel(false);
361
362 // Client tags are immutable and must be paired with the ID.
363 // If a server update comes down with an ID and client tag combo,
364 // and it already exists, always overwrite it and store only one copy.
365 // We have to undelete entries because we can't disassociate IDs from
366 // tags and updates.
367
368 existing_entry->PutNonUniqueName(dummy);
369 existing_entry->PutParentId(parent_id);
370 entry_undeleted = true;
371 } // Else just reuse the existing entry.
372 entry_ = existing_entry.release();
373 // If entry is undeleted, its specifics are reset to default, unencrypted
374 // value, and therefore no decryption is necessary. Moreover trying to
375 // decrypt the password entry will fail because passwords are expected to be
376 // encrypted.
377 if (!entry_undeleted && !DecryptIfNecessary())
378 return INIT_FAILED_DECRYPT_EXISTING_ENTRY;
379 } else {
380 entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
381 syncable::CREATE, model_type, parent_id,
382 dummy);
383 }
384
385 if (!entry_->good())
386 return INIT_FAILED_COULD_NOT_CREATE_ENTRY;
387
388 // Has no impact if the client tag is already set.
389 entry_->PutUniqueClientTag(hash);
390
391 // We don't support directory and tag combinations.
392 entry_->PutIsDir(false);
393
394 if (entry_->ShouldMaintainPosition()) {
395 if (!entry_->PutPredecessor(syncable::Id()))
396 return INIT_FAILED_SET_PREDECESSOR;
397 }
398
399 // Mark this entry as unsynced, to wake up the syncer.
400 MarkForSyncing();
401
402 return INIT_SUCCESS;
403 }
404
405 bool WriteNode::SetPosition(const BaseNode& new_parent,
406 const BaseNode* predecessor) {
407 // |predecessor| must be a child of |new_parent| or NULL.
408 if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
409 DCHECK(false);
410 return false;
411 }
412
413 syncable::Id new_parent_id = new_parent.GetSyncId();
414 DCHECK(!new_parent_id.IsNull());
415
416 // Filter out redundant changes if both the parent and the predecessor match.
417 if (new_parent_id == entry_->GetParentId()) {
418 const syncable::Id& old = entry_->GetPredecessorId();
419 if ((!predecessor && old.IsNull()) ||
420 (predecessor && (old == predecessor->GetSyncId()))) {
421 return true;
422 }
423 }
424
425 entry_->PutParentId(new_parent_id);
426
427 if (!PutPredecessor(predecessor)) {
428 return false;
429 }
430
431 // Mark this entry as unsynced, to wake up the syncer.
432 MarkForSyncing();
433 return true;
434 }
435
436 void WriteNode::SetAttachmentMetadata(
437 const sync_pb::AttachmentMetadata& attachment_metadata) {
438 entry_->PutAttachmentMetadata(attachment_metadata);
439 }
440
441 const syncable::Entry* WriteNode::GetEntry() const {
442 return entry_;
443 }
444
445 const BaseTransaction* WriteNode::GetTransaction() const {
446 return transaction_;
447 }
448
449 syncable::MutableEntry* WriteNode::GetMutableEntryForTest() {
450 return entry_;
451 }
452
453 void WriteNode::Tombstone() {
454 // These lines must be in this order. The call to Put(IS_DEL) might choose to
455 // unset the IS_UNSYNCED bit if the item was not known to the server at the
456 // time of deletion. It's important that the bit not be reset in that case.
457 MarkForSyncing();
458 entry_->PutIsDel(true);
459 }
460
461 void WriteNode::Drop() {
462 if (entry_->GetId().ServerKnows()) {
463 entry_->PutIsDel(true);
464 }
465 }
466
467 bool WriteNode::PutPredecessor(const BaseNode* predecessor) {
468 DCHECK(!entry_->GetParentId().IsNull());
469 syncable::Id predecessor_id =
470 predecessor ? predecessor->GetSyncId() : syncable::Id();
471 return entry_->PutPredecessor(predecessor_id);
472 }
473
474 void WriteNode::MarkForSyncing() {
475 syncable::MarkForSyncing(entry_);
476 }
477
478 } // namespace syncer
OLDNEW
« no previous file with comments | « components/sync/core/write_node.h ('k') | components/sync/core/write_transaction.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698