// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/sync/engine/build_commit_command.h"
#include <set>
#include <string>
#include <vector>
#include "base/string_util.h"
#include "chrome/browser/sync/engine/syncer_proto_util.h"
#include "chrome/browser/sync/engine/syncer_util.h"
#include "chrome/browser/sync/protocol/bookmark_specifics.pb.h"
#include "chrome/browser/sync/sessions/sync_session.h"
#include "chrome/browser/sync/syncable/syncable.h"
#include "chrome/browser/sync/syncable/syncable_changes_version.h"
using std::set;
using std::string;
using std::vector;
using syncable::IS_DEL;
using syncable::Id;
using syncable::MutableEntry;
using syncable::SPECIFICS;
using syncable::UNSPECIFIED;
namespace browser_sync {
using sessions::SyncSession;
BuildCommitCommand::BuildCommitCommand() {}
BuildCommitCommand::~BuildCommitCommand() {}
void BuildCommitCommand::AddExtensionsActivityToMessage(
SyncSession* session, CommitMessage* message) {
// We only send ExtensionsActivity to the server if bookmarks are being
// committed.
ExtensionsActivityMonitor* monitor = session->context()->extensions_monitor();
if (!session->status_controller()->HasBookmarkCommitActivity()) {
// Return the records to the activity monitor.
monitor->PutRecords(session->extensions_activity());
session->mutable_extensions_activity()->clear();
return;
}
const ExtensionsActivityMonitor::Records& records =
session->extensions_activity();
for (ExtensionsActivityMonitor::Records::const_iterator it = records.begin();
it != records.end(); ++it) {
sync_pb::ChromiumExtensionsActivity* activity_message =
message->add_extensions_activity();
activity_message->set_extension_id(it->second.extension_id);
activity_message->set_bookmark_writes_since_last_commit(
it->second.bookmark_write_count);
}
}
namespace {
void SetEntrySpecifics(MutableEntry* meta_entry, SyncEntity* sync_entry) {
// Add the new style extension and the folder bit.
sync_entry->mutable_specifics()->CopyFrom(meta_entry->Get(SPECIFICS));
sync_entry->set_folder(meta_entry->Get(syncable::IS_DIR));
DCHECK(meta_entry->GetModelType() == sync_entry->GetModelType());
}
void SetOldStyleBookmarkData(MutableEntry* meta_entry, SyncEntity* sync_entry) {
DCHECK(meta_entry->Get(SPECIFICS).HasExtension(sync_pb::bookmark));
// Old-style inlined bookmark data.
sync_pb::SyncEntity_BookmarkData* bookmark =
sync_entry->mutable_bookmarkdata();
if (!meta_entry->Get(syncable::IS_DIR)) {
const sync_pb::BookmarkSpecifics& bookmark_specifics =
meta_entry->Get(SPECIFICS).GetExtension(sync_pb::bookmark);
bookmark->set_bookmark_url(bookmark_specifics.url());
bookmark->set_bookmark_favicon(bookmark_specifics.favicon());
bookmark->set_bookmark_folder(false);
} else {
bookmark->set_bookmark_folder(true);
}
}
} // namespace
void BuildCommitCommand::ExecuteImpl(SyncSession* session) {
ClientToServerMessage message;
message.set_share(session->context()->account_name());
message.set_message_contents(ClientToServerMessage::COMMIT);
CommitMessage* commit_message = message.mutable_commit();
commit_message->set_cache_guid(
session->write_transaction()->directory()->cache_guid());
AddExtensionsActivityToMessage(session, commit_message);
SyncerProtoUtil::AddRequestBirthday(
session->write_transaction()->directory(), &message);
const vector<Id>& commit_ids = session->status_controller()->commit_ids();
for (size_t i = 0; i < commit_ids.size(); i++) {
Id id = commit_ids[i];
SyncEntity* sync_entry =
static_cast<SyncEntity*>(commit_message->add_entries());
sync_entry->set_id(id);
MutableEntry meta_entry(session->write_transaction(),
syncable::GET_BY_ID,
id);
CHECK(meta_entry.good());
// This is the only change we make to the entry in this function.
meta_entry.Put(syncable::SYNCING, true);
DCHECK(0 != session->routing_info().count(meta_entry.GetModelType()))
<< "Committing change to datatype that's not actively enabled.";
string name = meta_entry.Get(syncable::NON_UNIQUE_NAME);
CHECK(!name.empty()); // Make sure this isn't an update.
TruncateUTF8ToByteSize(name, 255, &name);
sync_entry->set_name(name);
// Set the non_unique_name. If we do, the server ignores
// the |name| value (using |non_unique_name| instead), and will return
// in the CommitResponse a unique name if one is generated.
// We send both because it may aid in logging.
sync_entry->set_non_unique_name(name);
if (!meta_entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) {
sync_entry->set_client_defined_unique_tag(
meta_entry.Get(syncable::UNIQUE_CLIENT_TAG));
}
// Deleted items with server-unknown parent ids can be a problem so we set
// the parent to 0. (TODO(sync): Still true in protocol?).
Id new_parent_id;
if (meta_entry.Get(syncable::IS_DEL) &&
!meta_entry.Get(syncable::PARENT_ID).ServerKnows()) {
new_parent_id = session->write_transaction()->root_id();
} else {
new_parent_id = meta_entry.Get(syncable::PARENT_ID);
}
sync_entry->set_parent_id(new_parent_id);
// If our parent has changed, send up the old one so the server
// can correctly deal with multiple parents.
// TODO(nick): With the server keeping track of the primary sync parent,
// it should not be necessary to provide the old_parent_id: the version
// number should suffice.
if (new_parent_id != meta_entry.Get(syncable::SERVER_PARENT_ID) &&
0 != meta_entry.Get(syncable::BASE_VERSION) &&
syncable::CHANGES_VERSION != meta_entry.Get(syncable::BASE_VERSION)) {
sync_entry->set_old_parent_id(meta_entry.Get(syncable::SERVER_PARENT_ID));
}
int64 version = meta_entry.Get(syncable::BASE_VERSION);
if (syncable::CHANGES_VERSION == version || 0 == version) {
// Undeletions are only supported for items that have a client tag.
DCHECK(!id.ServerKnows() ||
!meta_entry.Get(syncable::UNIQUE_CLIENT_TAG).empty())
<< meta_entry;
// Version 0 means to create or undelete an object.
sync_entry->set_version(0);
} else {
DCHECK(id.ServerKnows()) << meta_entry;
sync_entry->set_version(meta_entry.Get(syncable::BASE_VERSION));
}
sync_entry->set_ctime(ClientTimeToServerTime(
meta_entry.Get(syncable::CTIME)));
sync_entry->set_mtime(ClientTimeToServerTime(
meta_entry.Get(syncable::MTIME)));
// Deletion is final on the server, let's move things and then delete them.
if (meta_entry.Get(IS_DEL)) {
sync_entry->set_deleted(true);
} else {
if (meta_entry.Get(SPECIFICS).HasExtension(sync_pb::bookmark)) {
// Common data in both new and old protocol.
const Id& prev_id = meta_entry.Get(syncable::PREV_ID);
string prev_id_string =
prev_id.IsRoot() ? string() : prev_id.GetServerId();
sync_entry->set_insert_after_item_id(prev_id_string);
// TODO(ncarter): In practice we won't want to send this data twice
// over the wire; instead, when deployed servers are able to accept
// the new-style scheme, we should abandon the old way.
SetOldStyleBookmarkData(&meta_entry, sync_entry);
}
SetEntrySpecifics(&meta_entry, sync_entry);
}
}
session->status_controller()->mutable_commit_message()->CopyFrom(message);
}
} // namespace browser_sync