Home | History | Annotate | Download | only in engine
      1 // Copyright 2013 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 "sync/engine/commit_util.h"
      6 
      7 #include <limits>
      8 #include <set>
      9 #include <string>
     10 #include <vector>
     11 
     12 #include "base/strings/string_util.h"
     13 #include "sync/engine/syncer_proto_util.h"
     14 #include "sync/internal_api/public/base/unique_position.h"
     15 #include "sync/protocol/bookmark_specifics.pb.h"
     16 #include "sync/protocol/sync.pb.h"
     17 #include "sync/sessions/sync_session.h"
     18 #include "sync/syncable/directory.h"
     19 #include "sync/syncable/entry.h"
     20 #include "sync/syncable/model_neutral_mutable_entry.h"
     21 #include "sync/syncable/syncable_base_transaction.h"
     22 #include "sync/syncable/syncable_base_write_transaction.h"
     23 #include "sync/syncable/syncable_changes_version.h"
     24 #include "sync/syncable/syncable_proto_util.h"
     25 #include "sync/syncable/syncable_util.h"
     26 #include "sync/util/time.h"
     27 
     28 using std::set;
     29 using std::string;
     30 using std::vector;
     31 
     32 namespace syncer {
     33 
     34 using sessions::SyncSession;
     35 using syncable::Entry;
     36 using syncable::IS_DEL;
     37 using syncable::IS_UNAPPLIED_UPDATE;
     38 using syncable::IS_UNSYNCED;
     39 using syncable::Id;
     40 using syncable::SPECIFICS;
     41 using syncable::UNIQUE_POSITION;
     42 
     43 namespace commit_util {
     44 
     45 void AddExtensionsActivityToMessage(
     46     ExtensionsActivity* activity,
     47     ExtensionsActivity::Records* extensions_activity_buffer,
     48     sync_pb::CommitMessage* message) {
     49   // This isn't perfect, since the set of extensions activity may not correlate
     50   // exactly with the items being committed.  That's OK as long as we're looking
     51   // for a rough estimate of extensions activity, not an precise mapping of
     52   // which commits were triggered by which extension.
     53   //
     54   // We will push this list of extensions activity back into the
     55   // ExtensionsActivityMonitor if this commit fails.  That's why we must keep a
     56   // copy of these records in the session.
     57   activity->GetAndClearRecords(extensions_activity_buffer);
     58 
     59   const ExtensionsActivity::Records& records = *extensions_activity_buffer;
     60   for (ExtensionsActivity::Records::const_iterator it =
     61        records.begin();
     62        it != records.end(); ++it) {
     63     sync_pb::ChromiumExtensionsActivity* activity_message =
     64         message->add_extensions_activity();
     65     activity_message->set_extension_id(it->second.extension_id);
     66     activity_message->set_bookmark_writes_since_last_commit(
     67         it->second.bookmark_write_count);
     68   }
     69 }
     70 
     71 void AddClientConfigParamsToMessage(
     72     ModelTypeSet enabled_types,
     73     sync_pb::CommitMessage* message) {
     74   sync_pb::ClientConfigParams* config_params = message->mutable_config_params();
     75   for (ModelTypeSet::Iterator it = enabled_types.First(); it.Good(); it.Inc()) {
     76     if (ProxyTypes().Has(it.Get()))
     77       continue;
     78     int field_number = GetSpecificsFieldNumberFromModelType(it.Get());
     79     config_params->mutable_enabled_type_ids()->Add(field_number);
     80   }
     81   config_params->set_tabs_datatype_enabled(
     82       enabled_types.Has(syncer::PROXY_TABS));
     83 }
     84 
     85 namespace {
     86 void SetEntrySpecifics(const Entry& meta_entry,
     87                        sync_pb::SyncEntity* sync_entry) {
     88   // Add the new style extension and the folder bit.
     89   sync_entry->mutable_specifics()->CopyFrom(meta_entry.GetSpecifics());
     90   sync_entry->set_folder(meta_entry.GetIsDir());
     91 
     92   CHECK(!sync_entry->specifics().password().has_client_only_encrypted_data());
     93   DCHECK_EQ(meta_entry.GetModelType(), GetModelType(*sync_entry));
     94 }
     95 }  // namespace
     96 
     97 void BuildCommitItem(
     98     const syncable::Entry& meta_entry,
     99     sync_pb::SyncEntity* sync_entry) {
    100   syncable::Id id = meta_entry.GetId();
    101   sync_entry->set_id_string(SyncableIdToProto(id));
    102 
    103   string name = meta_entry.GetNonUniqueName();
    104   CHECK(!name.empty());  // Make sure this isn't an update.
    105   // Note: Truncation is also performed in WriteNode::SetTitle(..). But this
    106   // call is still necessary to handle any title changes that might originate
    107   // elsewhere, or already be persisted in the directory.
    108   base::TruncateUTF8ToByteSize(name, 255, &name);
    109   sync_entry->set_name(name);
    110 
    111   // Set the non_unique_name.  If we do, the server ignores
    112   // the |name| value (using |non_unique_name| instead), and will return
    113   // in the CommitResponse a unique name if one is generated.
    114   // We send both because it may aid in logging.
    115   sync_entry->set_non_unique_name(name);
    116 
    117   if (!meta_entry.GetUniqueClientTag().empty()) {
    118     sync_entry->set_client_defined_unique_tag(
    119         meta_entry.GetUniqueClientTag());
    120   }
    121 
    122   // Deleted items with server-unknown parent ids can be a problem so we set
    123   // the parent to 0. (TODO(sync): Still true in protocol?).
    124   Id new_parent_id;
    125   if (meta_entry.GetIsDel() &&
    126       !meta_entry.GetParentId().ServerKnows()) {
    127     new_parent_id = syncable::BaseTransaction::root_id();
    128   } else {
    129     new_parent_id = meta_entry.GetParentId();
    130   }
    131   sync_entry->set_parent_id_string(SyncableIdToProto(new_parent_id));
    132 
    133   // If our parent has changed, send up the old one so the server
    134   // can correctly deal with multiple parents.
    135   // TODO(nick): With the server keeping track of the primary sync parent,
    136   // it should not be necessary to provide the old_parent_id: the version
    137   // number should suffice.
    138   if (new_parent_id != meta_entry.GetServerParentId() &&
    139       0 != meta_entry.GetBaseVersion() &&
    140       syncable::CHANGES_VERSION != meta_entry.GetBaseVersion()) {
    141     sync_entry->set_old_parent_id(
    142         SyncableIdToProto(meta_entry.GetServerParentId()));
    143   }
    144 
    145   int64 version = meta_entry.GetBaseVersion();
    146   if (syncable::CHANGES_VERSION == version || 0 == version) {
    147     // Undeletions are only supported for items that have a client tag.
    148     DCHECK(!id.ServerKnows() ||
    149            !meta_entry.GetUniqueClientTag().empty())
    150         << meta_entry;
    151 
    152     // Version 0 means to create or undelete an object.
    153     sync_entry->set_version(0);
    154   } else {
    155     DCHECK(id.ServerKnows()) << meta_entry;
    156     sync_entry->set_version(meta_entry.GetBaseVersion());
    157   }
    158   sync_entry->set_ctime(TimeToProtoTime(meta_entry.GetCtime()));
    159   sync_entry->set_mtime(TimeToProtoTime(meta_entry.GetMtime()));
    160 
    161   // Deletion is final on the server, let's move things and then delete them.
    162   if (meta_entry.GetIsDel()) {
    163     sync_entry->set_deleted(true);
    164   } else {
    165     if (meta_entry.GetSpecifics().has_bookmark()) {
    166       // Both insert_after_item_id and position_in_parent fields are set only
    167       // for legacy reasons.  See comments in sync.proto for more information.
    168       const Id& prev_id = meta_entry.GetPredecessorId();
    169       string prev_id_string =
    170           prev_id.IsRoot() ? string() : prev_id.GetServerId();
    171       sync_entry->set_insert_after_item_id(prev_id_string);
    172       sync_entry->set_position_in_parent(
    173           meta_entry.GetUniquePosition().ToInt64());
    174       meta_entry.GetUniquePosition().ToProto(
    175           sync_entry->mutable_unique_position());
    176     }
    177     SetEntrySpecifics(meta_entry, sync_entry);
    178   }
    179 }
    180 
    181 
    182 // Helpers for ProcessSingleCommitResponse.
    183 namespace {
    184 
    185 void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) {
    186   if (res.has_error_message())
    187     LOG(WARNING) << "  " << res.error_message();
    188   else
    189     LOG(WARNING) << "  No detailed error message returned from server";
    190 }
    191 
    192 const string& GetResultingPostCommitName(
    193     const sync_pb::SyncEntity& committed_entry,
    194     const sync_pb::CommitResponse_EntryResponse& entry_response) {
    195   const string& response_name =
    196       SyncerProtoUtil::NameFromCommitEntryResponse(entry_response);
    197   if (!response_name.empty())
    198     return response_name;
    199   return SyncerProtoUtil::NameFromSyncEntity(committed_entry);
    200 }
    201 
    202 bool UpdateVersionAfterCommit(
    203     const sync_pb::SyncEntity& committed_entry,
    204     const sync_pb::CommitResponse_EntryResponse& entry_response,
    205     const syncable::Id& pre_commit_id,
    206     syncable::ModelNeutralMutableEntry* local_entry) {
    207   int64 old_version = local_entry->GetBaseVersion();
    208   int64 new_version = entry_response.version();
    209   bool bad_commit_version = false;
    210   if (committed_entry.deleted() &&
    211       !local_entry->GetUniqueClientTag().empty()) {
    212     // If the item was deleted, and it's undeletable (uses the client tag),
    213     // change the version back to zero.  We must set the version to zero so
    214     // that the server knows to re-create the item if it gets committed
    215     // later for undeletion.
    216     new_version = 0;
    217   } else if (!pre_commit_id.ServerKnows()) {
    218     bad_commit_version = 0 == new_version;
    219   } else {
    220     bad_commit_version = old_version > new_version;
    221   }
    222   if (bad_commit_version) {
    223     LOG(ERROR) << "Bad version in commit return for " << *local_entry
    224                << " new_id:" << SyncableIdFromProto(entry_response.id_string())
    225                << " new_version:" << entry_response.version();
    226     return false;
    227   }
    228 
    229   // Update the base version and server version.  The base version must change
    230   // here, even if syncing_was_set is false; that's because local changes were
    231   // on top of the successfully committed version.
    232   local_entry->PutBaseVersion(new_version);
    233   DVLOG(1) << "Commit is changing base version of " << local_entry->GetId()
    234            << " to: " << new_version;
    235   local_entry->PutServerVersion(new_version);
    236   return true;
    237 }
    238 
    239 bool ChangeIdAfterCommit(
    240     const sync_pb::CommitResponse_EntryResponse& entry_response,
    241     const syncable::Id& pre_commit_id,
    242     syncable::ModelNeutralMutableEntry* local_entry) {
    243   syncable::BaseWriteTransaction* trans = local_entry->base_write_transaction();
    244   const syncable::Id& entry_response_id =
    245       SyncableIdFromProto(entry_response.id_string());
    246   if (entry_response_id != pre_commit_id) {
    247     if (pre_commit_id.ServerKnows()) {
    248       // The server can sometimes generate a new ID on commit; for example,
    249       // when committing an undeletion.
    250       DVLOG(1) << " ID changed while committing an old entry. "
    251                << pre_commit_id << " became " << entry_response_id << ".";
    252     }
    253     syncable::ModelNeutralMutableEntry same_id(
    254         trans,
    255         syncable::GET_BY_ID,
    256         entry_response_id);
    257     // We should trap this before this function.
    258     if (same_id.good()) {
    259       LOG(ERROR) << "ID clash with id " << entry_response_id
    260                  << " during commit " << same_id;
    261       return false;
    262     }
    263     ChangeEntryIDAndUpdateChildren(trans, local_entry, entry_response_id);
    264     DVLOG(1) << "Changing ID to " << entry_response_id;
    265   }
    266   return true;
    267 }
    268 
    269 void UpdateServerFieldsAfterCommit(
    270     const sync_pb::SyncEntity& committed_entry,
    271     const sync_pb::CommitResponse_EntryResponse& entry_response,
    272     syncable::ModelNeutralMutableEntry* local_entry) {
    273 
    274   // We just committed an entry successfully, and now we want to make our view
    275   // of the server state consistent with the server state. We must be careful;
    276   // |entry_response| and |committed_entry| have some identically named
    277   // fields.  We only want to consider fields from |committed_entry| when there
    278   // is not an overriding field in the |entry_response|.  We do not want to
    279   // update the server data from the local data in the entry -- it's possible
    280   // that the local data changed during the commit, and even if not, the server
    281   // has the last word on the values of several properties.
    282 
    283   local_entry->PutServerIsDel(committed_entry.deleted());
    284   if (committed_entry.deleted()) {
    285     // Don't clobber any other fields of deleted objects.
    286     return;
    287   }
    288 
    289   local_entry->PutServerIsDir(
    290       (committed_entry.folder() ||
    291        committed_entry.bookmarkdata().bookmark_folder()));
    292   local_entry->PutServerSpecifics(committed_entry.specifics());
    293   local_entry->PutServerMtime(ProtoTimeToTime(committed_entry.mtime()));
    294   local_entry->PutServerCtime(ProtoTimeToTime(committed_entry.ctime()));
    295   if (committed_entry.has_unique_position()) {
    296     local_entry->PutServerUniquePosition(
    297                      UniquePosition::FromProto(
    298                          committed_entry.unique_position()));
    299   }
    300 
    301   // TODO(nick): The server doesn't set entry_response.server_parent_id in
    302   // practice; to update SERVER_PARENT_ID appropriately here we'd need to
    303   // get the post-commit ID of the parent indicated by
    304   // committed_entry.parent_id_string(). That should be inferrable from the
    305   // information we have, but it's a bit convoluted to pull it out directly.
    306   // Getting this right is important: SERVER_PARENT_ID gets fed back into
    307   // old_parent_id during the next commit.
    308   local_entry->PutServerParentId(local_entry->GetParentId());
    309   local_entry->PutServerNonUniqueName(
    310       GetResultingPostCommitName(committed_entry, entry_response));
    311 
    312   if (local_entry->GetIsUnappliedUpdate()) {
    313     // This shouldn't happen; an unapplied update shouldn't be committed, and
    314     // if it were, the commit should have failed.  But if it does happen: we've
    315     // just overwritten the update info, so clear the flag.
    316     local_entry->PutIsUnappliedUpdate(false);
    317   }
    318 }
    319 
    320 void ProcessSuccessfulCommitResponse(
    321     const sync_pb::SyncEntity& committed_entry,
    322     const sync_pb::CommitResponse_EntryResponse& entry_response,
    323     const syncable::Id& pre_commit_id,
    324     syncable::ModelNeutralMutableEntry* local_entry,
    325     bool syncing_was_set, set<syncable::Id>* deleted_folders) {
    326   DCHECK(local_entry->GetIsUnsynced());
    327 
    328   // Update SERVER_VERSION and BASE_VERSION.
    329   if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id,
    330                                 local_entry)) {
    331     LOG(ERROR) << "Bad version in commit return for " << *local_entry
    332                << " new_id:" << SyncableIdFromProto(entry_response.id_string())
    333                << " new_version:" << entry_response.version();
    334     return;
    335   }
    336 
    337   // If the server gave us a new ID, apply it.
    338   if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) {
    339     return;
    340   }
    341 
    342   // Update our stored copy of the server state.
    343   UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry);
    344 
    345   // If the item doesn't need to be committed again (an item might need to be
    346   // committed again if it changed locally during the commit), we can remove
    347   // it from the unsynced list.
    348   if (syncing_was_set) {
    349     local_entry->PutIsUnsynced(false);
    350   }
    351 
    352   // Make a note of any deleted folders, whose children would have
    353   // been recursively deleted.
    354   // TODO(nick): Here, commit_message.deleted() would be more correct than
    355   // local_entry->GetIsDel().  For example, an item could be renamed, and then
    356   // deleted during the commit of the rename.  Unit test & fix.
    357   if (local_entry->GetIsDir() && local_entry->GetIsDel()) {
    358     deleted_folders->insert(local_entry->GetId());
    359   }
    360 }
    361 
    362 }  // namespace
    363 
    364 sync_pb::CommitResponse::ResponseType
    365 ProcessSingleCommitResponse(
    366     syncable::BaseWriteTransaction* trans,
    367     const sync_pb::CommitResponse_EntryResponse& server_entry,
    368     const sync_pb::SyncEntity& commit_request_entry,
    369     int64 metahandle,
    370     set<syncable::Id>* deleted_folders) {
    371   syncable::ModelNeutralMutableEntry local_entry(
    372       trans,
    373       syncable::GET_BY_HANDLE,
    374       metahandle);
    375   CHECK(local_entry.good());
    376   bool syncing_was_set = local_entry.GetSyncing();
    377   local_entry.PutSyncing(false);
    378 
    379   sync_pb::CommitResponse::ResponseType response = server_entry.response_type();
    380   if (!sync_pb::CommitResponse::ResponseType_IsValid(response)) {
    381     LOG(ERROR) << "Commit response has unknown response type! Possibly out "
    382                "of date client?";
    383     return sync_pb::CommitResponse::INVALID_MESSAGE;
    384   }
    385   if (sync_pb::CommitResponse::TRANSIENT_ERROR == response) {
    386     DVLOG(1) << "Transient Error Committing: " << local_entry;
    387     LogServerError(server_entry);
    388     return sync_pb::CommitResponse::TRANSIENT_ERROR;
    389   }
    390   if (sync_pb::CommitResponse::INVALID_MESSAGE == response) {
    391     LOG(ERROR) << "Error Commiting: " << local_entry;
    392     LogServerError(server_entry);
    393     return response;
    394   }
    395   if (sync_pb::CommitResponse::CONFLICT == response) {
    396     DVLOG(1) << "Conflict Committing: " << local_entry;
    397     return response;
    398   }
    399   if (sync_pb::CommitResponse::RETRY == response) {
    400     DVLOG(1) << "Retry Committing: " << local_entry;
    401     return response;
    402   }
    403   if (sync_pb::CommitResponse::OVER_QUOTA == response) {
    404     LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
    405     return response;
    406   }
    407   if (!server_entry.has_id_string()) {
    408     LOG(ERROR) << "Commit response has no id";
    409     return sync_pb::CommitResponse::INVALID_MESSAGE;
    410   }
    411 
    412   // Implied by the IsValid call above, but here for clarity.
    413   DCHECK_EQ(sync_pb::CommitResponse::SUCCESS, response) << response;
    414   // Check to see if we've been given the ID of an existing entry. If so treat
    415   // it as an error response and retry later.
    416   const syncable::Id& server_entry_id =
    417       SyncableIdFromProto(server_entry.id_string());
    418   if (local_entry.GetId() != server_entry_id) {
    419     Entry e(trans, syncable::GET_BY_ID, server_entry_id);
    420     if (e.good()) {
    421       LOG(ERROR)
    422           << "Got duplicate id when commiting id: "
    423           << local_entry.GetId()
    424           << ". Treating as an error return";
    425       return sync_pb::CommitResponse::INVALID_MESSAGE;
    426     }
    427   }
    428 
    429   if (server_entry.version() == 0) {
    430     LOG(WARNING) << "Server returned a zero version on a commit response.";
    431   }
    432 
    433   ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
    434       local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders);
    435   return response;
    436 }
    437 
    438 }  // namespace commit_util
    439 
    440 }  // namespace syncer
    441