#include "sync/internal_api/public/write_node.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "sync/internal_api/public/base_transaction.h"
#include "sync/internal_api/public/write_transaction.h"
#include "sync/internal_api/syncapi_internal.h"
#include "sync/protocol/app_specifics.pb.h"
#include "sync/protocol/autofill_specifics.pb.h"
#include "sync/protocol/bookmark_specifics.pb.h"
#include "sync/protocol/extension_specifics.pb.h"
#include "sync/protocol/password_specifics.pb.h"
#include "sync/protocol/session_specifics.pb.h"
#include "sync/protocol/theme_specifics.pb.h"
#include "sync/protocol/typed_url_specifics.pb.h"
#include "sync/syncable/mutable_entry.h"
#include "sync/syncable/nigori_util.h"
#include "sync/util/cryptographer.h"
using std::string;
using std::vector;
namespace syncer {
using syncable::kEncryptedString;
using syncable::SPECIFICS;
static const char kDefaultNameForNewNodes[] = " ";
void WriteNode::SetIsFolder(bool folder) {
if (entry_->Get(syncable::IS_DIR) == folder)
return;
entry_->Put(syncable::IS_DIR, folder);
MarkForSyncing();
}
void WriteNode::SetTitle(const std::wstring& title) {
DCHECK_NE(GetModelType(), UNSPECIFIED);
ModelType type = GetModelType();
bool needs_encryption = GetTransaction()->GetEncryptedTypes().Has(type) ||
entry_->Get(SPECIFICS).has_encrypted();
std::string new_legal_title;
if (type != BOOKMARKS && needs_encryption) {
new_legal_title = kEncryptedString;
} else {
SyncAPINameToServerName(WideToUTF8(title), &new_legal_title);
}
std::string current_legal_title;
if (BOOKMARKS == type &&
entry_->Get(syncable::SPECIFICS).has_encrypted()) {
current_legal_title = GetBookmarkSpecifics().title();
} else {
current_legal_title = entry_->Get(syncable::NON_UNIQUE_NAME);
}
bool title_matches = (current_legal_title == new_legal_title);
bool encrypted_without_overwriting_name = (needs_encryption &&
entry_->Get(syncable::NON_UNIQUE_NAME) != kEncryptedString);
if (title_matches && !encrypted_without_overwriting_name) {
DVLOG(2) << "Title matches, dropping change.";
return;
}
if (GetModelType() == BOOKMARKS) {
sync_pb::EntitySpecifics specifics = GetEntitySpecifics();
specifics.mutable_bookmark()->set_title(new_legal_title);
SetEntitySpecifics(specifics);
}
if (needs_encryption)
entry_->Put(syncable::NON_UNIQUE_NAME, kEncryptedString);
else
entry_->Put(syncable::NON_UNIQUE_NAME, new_legal_title);
DVLOG(1) << "Overwriting title of type "
<< ModelTypeToString(type)
<< " and marking for syncing.";
MarkForSyncing();
}
void WriteNode::SetURL(const GURL& url) {
sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
new_value.set_url(url.spec());
SetBookmarkSpecifics(new_value);
}
void WriteNode::SetAppSpecifics(
const sync_pb::AppSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_app()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetAutofillSpecifics(
const sync_pb::AutofillSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_autofill()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetAutofillProfileSpecifics(
const sync_pb::AutofillProfileSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_autofill_profile()->
CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetBookmarkSpecifics(
const sync_pb::BookmarkSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_bookmark()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetNigoriSpecifics(
const sync_pb::NigoriSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_nigori()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetPasswordSpecifics(
const sync_pb::PasswordSpecificsData& data) {
DCHECK_EQ(GetModelType(), PASSWORDS);
Cryptographer* cryptographer = GetTransaction()->GetCryptographer();
const sync_pb::EntitySpecifics& old_specifics = GetEntry()->Get(SPECIFICS);
sync_pb::EntitySpecifics entity_specifics;
if (GetModelTypeFromSpecifics(old_specifics) == PASSWORDS) {
entity_specifics.CopyFrom(old_specifics);
} else {
AddDefaultFieldValue(PASSWORDS, &entity_specifics);
}
sync_pb::PasswordSpecifics* password_specifics =
entity_specifics.mutable_password();
if (!cryptographer->Encrypt(data, password_specifics->mutable_encrypted())) {
NOTREACHED() << "Failed to encrypt password, possibly due to sync node "
<< "corruption";
return;
}
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetThemeSpecifics(
const sync_pb::ThemeSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_theme()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetSessionSpecifics(
const sync_pb::SessionSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_session()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetEntitySpecifics(
const sync_pb::EntitySpecifics& new_value) {
ModelType new_specifics_type =
GetModelTypeFromSpecifics(new_value);
DCHECK_NE(new_specifics_type, UNSPECIFIED);
DVLOG(1) << "Writing entity specifics of type "
<< ModelTypeToString(new_specifics_type);
if (GetModelType() != UNSPECIFIED) {
DCHECK_EQ(new_specifics_type, GetModelType());
}
const sync_pb::EntitySpecifics& old_specifics = entry_->Get(SPECIFICS);
sync_pb::EntitySpecifics new_specifics;
new_specifics.CopyFrom(new_value);
new_specifics.mutable_unknown_fields()->MergeFrom(
old_specifics.unknown_fields());
if (!UpdateEntryWithEncryption(GetTransaction()->GetWrappedTrans(),
new_specifics,
entry_)) {
return;
}
if (entry_->Get(SPECIFICS).has_encrypted()) {
SetUnencryptedSpecifics(new_value);
}
DCHECK_EQ(new_specifics_type, GetModelType());
}
void WriteNode::ResetFromSpecifics() {
SetEntitySpecifics(GetEntitySpecifics());
}
void WriteNode::SetTypedUrlSpecifics(
const sync_pb::TypedUrlSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_typed_url()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetExtensionSpecifics(
const sync_pb::ExtensionSpecifics& new_value) {
sync_pb::EntitySpecifics entity_specifics;
entity_specifics.mutable_extension()->CopyFrom(new_value);
SetEntitySpecifics(entity_specifics);
}
void WriteNode::SetExternalId(int64 id) {
if (GetExternalId() != id)
entry_->Put(syncable::LOCAL_EXTERNAL_ID, id);
}
WriteNode::WriteNode(WriteTransaction* transaction)
: entry_(NULL), transaction_(transaction) {
DCHECK(transaction);
}
WriteNode::~WriteNode() {
delete entry_;
}
BaseNode::InitByLookupResult WriteNode::InitByIdLookup(int64 id) {
DCHECK(!entry_) << "Init called twice";
DCHECK_NE(id, kInvalidId);
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::GET_BY_HANDLE, id);
if (!entry_->good())
return INIT_FAILED_ENTRY_NOT_GOOD;
if (entry_->Get(syncable::IS_DEL))
return INIT_FAILED_ENTRY_IS_DEL;
return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
}
BaseNode::InitByLookupResult WriteNode::InitByClientTagLookup(
ModelType model_type,
const std::string& tag) {
DCHECK(!entry_) << "Init called twice";
if (tag.empty())
return INIT_FAILED_PRECONDITION;
const std::string hash = GenerateSyncableHash(model_type, tag);
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::GET_BY_CLIENT_TAG, hash);
if (!entry_->good())
return INIT_FAILED_ENTRY_NOT_GOOD;
if (entry_->Get(syncable::IS_DEL))
return INIT_FAILED_ENTRY_IS_DEL;
return DecryptIfNecessary() ? INIT_OK : INIT_FAILED_DECRYPT_IF_NECESSARY;
}
BaseNode::InitByLookupResult WriteNode::InitByTagLookup(
const std::string& tag) {
DCHECK(!entry_) << "Init called twice";
if (tag.empty())
return INIT_FAILED_PRECONDITION;
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::GET_BY_SERVER_TAG, tag);
if (!entry_->good())
return INIT_FAILED_ENTRY_NOT_GOOD;
if (entry_->Get(syncable::IS_DEL))
return INIT_FAILED_ENTRY_IS_DEL;
ModelType model_type = GetModelType();
DCHECK_EQ(model_type, NIGORI);
return INIT_OK;
}
void WriteNode::PutModelType(ModelType model_type) {
DCHECK(GetModelType() == model_type ||
GetModelType() == UNSPECIFIED);
sync_pb::EntitySpecifics specifics;
AddDefaultFieldValue(model_type, &specifics);
SetEntitySpecifics(specifics);
}
bool WriteNode::InitByCreation(ModelType model_type,
const BaseNode& parent,
const BaseNode* predecessor) {
DCHECK(!entry_) << "Init called twice";
if (predecessor && predecessor->GetParentId() != parent.GetId()) {
DCHECK(false);
return false;
}
syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);
string dummy(kDefaultNameForNewNodes);
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::CREATE, parent_id, dummy);
if (!entry_->good())
return false;
entry_->Put(syncable::IS_DIR, true);
PutModelType(model_type);
return PutPredecessor(predecessor);
}
WriteNode::InitUniqueByCreationResult WriteNode::InitUniqueByCreation(
ModelType model_type,
const BaseNode& parent,
const std::string& tag) {
DCHECK(!entry_);
if (tag.empty()) {
LOG(WARNING) << "InitUniqueByCreation failed due to empty tag.";
return INIT_FAILED_EMPTY_TAG;
}
const std::string hash = GenerateSyncableHash(model_type, tag);
syncable::Id parent_id = parent.GetEntry()->Get(syncable::ID);
string dummy(kDefaultNameForNewNodes);
scoped_ptr<syncable::MutableEntry> existing_entry(
new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::GET_BY_CLIENT_TAG, hash));
if (existing_entry->good()) {
if (existing_entry->Get(syncable::IS_DEL)) {
existing_entry->Put(syncable::IS_DEL, false);
existing_entry->Put(syncable::NON_UNIQUE_NAME, dummy);
existing_entry->Put(syncable::PARENT_ID, parent_id);
entry_ = existing_entry.release();
} else {
return INIT_FAILED_ENTRY_ALREADY_EXISTS;
}
} else {
entry_ = new syncable::MutableEntry(transaction_->GetWrappedWriteTrans(),
syncable::CREATE, parent_id, dummy);
if (!entry_->good())
return INIT_FAILED_COULD_NOT_CREATE_ENTRY;
entry_->Put(syncable::UNIQUE_CLIENT_TAG, hash);
}
entry_->Put(syncable::IS_DIR, false);
PutModelType(model_type);
bool success = PutPredecessor(NULL);
if (!success)
return INIT_FAILED_SET_PREDECESSOR;
return INIT_SUCCESS;
}
bool WriteNode::SetPosition(const BaseNode& new_parent,
const BaseNode* predecessor) {
if (predecessor && predecessor->GetParentId() != new_parent.GetId()) {
DCHECK(false);
return false;
}
syncable::Id new_parent_id = new_parent.GetEntry()->Get(syncable::ID);
if (new_parent_id == entry_->Get(syncable::PARENT_ID)) {
const syncable::Id& old = entry_->Get(syncable::PREV_ID);
if ((!predecessor && old.IsRoot()) ||
(predecessor && (old == predecessor->GetEntry()->Get(syncable::ID)))) {
return true;
}
}
if (!entry_->Put(syncable::PARENT_ID, new_parent_id))
return false;
return PutPredecessor(predecessor);
}
const syncable::Entry* WriteNode::GetEntry() const {
return entry_;
}
const BaseTransaction* WriteNode::GetTransaction() const {
return transaction_;
}
syncable::MutableEntry* WriteNode::GetMutableEntryForTest() {
return entry_;
}
void WriteNode::Remove() {
MarkForSyncing();
entry_->Put(syncable::IS_DEL, true);
}
bool WriteNode::PutPredecessor(const BaseNode* predecessor) {
syncable::Id predecessor_id = predecessor ?
predecessor->GetEntry()->Get(syncable::ID) : syncable::Id();
if (!entry_->PutPredecessor(predecessor_id))
return false;
MarkForSyncing();
return true;
}
void WriteNode::SetFaviconBytes(const vector<unsigned char>& bytes) {
sync_pb::BookmarkSpecifics new_value = GetBookmarkSpecifics();
new_value.set_favicon(bytes.empty() ? NULL : &bytes[0], bytes.size());
SetBookmarkSpecifics(new_value);
}
void WriteNode::MarkForSyncing() {
syncable::MarkForSyncing(entry_);
}
}