| // Copyright 2020 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/sharing/sharing_message_bridge_impl.h" |
| |
| #include "base/guid.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "chrome/browser/sharing/features.h" |
| #include "components/sync/model/dummy_metadata_change_list.h" |
| #include "components/sync/model/metadata_batch.h" |
| #include "components/sync/model/mutable_data_batch.h" |
| #include "net/base/network_change_notifier.h" |
| |
| namespace { |
| |
| void ReplyToCallback(SharingMessageBridge::CommitFinishedCallback callback, |
| const sync_pb::SharingMessageCommitError& commit_error) { |
| DCHECK(commit_error.has_error_code()); |
| base::UmaHistogramExactLinear( |
| "Sync.SharingMessage.CommitResult", commit_error.error_code(), |
| sync_pb::SharingMessageCommitError::ErrorCode_ARRAYSIZE); |
| std::move(callback).Run(commit_error); |
| } |
| |
| void ReplyToCallback( |
| SharingMessageBridge::CommitFinishedCallback callback, |
| sync_pb::SharingMessageCommitError::ErrorCode commit_error_code) { |
| sync_pb::SharingMessageCommitError error_message; |
| error_message.set_error_code(commit_error_code); |
| ReplyToCallback(std::move(callback), error_message); |
| } |
| |
| syncer::ClientTagHash GetClientTagHashFromStorageKey( |
| const std::string& storage_key) { |
| return syncer::ClientTagHash::FromUnhashed(syncer::SHARING_MESSAGE, |
| storage_key); |
| } |
| |
| std::unique_ptr<syncer::EntityData> MoveToEntityData( |
| std::unique_ptr<sync_pb::SharingMessageSpecifics> specifics) { |
| auto entity_data = std::make_unique<syncer::EntityData>(); |
| entity_data->name = specifics->message_id(); |
| entity_data->client_tag_hash = |
| GetClientTagHashFromStorageKey(specifics->message_id()); |
| entity_data->specifics.set_allocated_sharing_message(specifics.release()); |
| return entity_data; |
| } |
| |
| std::unique_ptr<syncer::EntityData> CopyToEntityData( |
| const sync_pb::SharingMessageSpecifics& specifics) { |
| return MoveToEntityData( |
| std::make_unique<sync_pb::SharingMessageSpecifics>(specifics)); |
| } |
| |
| } // namespace |
| |
| SharingMessageBridgeImpl::SharingMessageBridgeImpl( |
| std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor) |
| : ModelTypeSyncBridge(std::move(change_processor)) { |
| // Current data type doesn't have persistent storage so it's ready to sync |
| // immediately. |
| this->change_processor()->ModelReadyToSync( |
| std::make_unique<syncer::MetadataBatch>()); |
| } |
| |
| SharingMessageBridgeImpl::~SharingMessageBridgeImpl() = default; |
| |
| void SharingMessageBridgeImpl::SendSharingMessage( |
| std::unique_ptr<sync_pb::SharingMessageSpecifics> specifics, |
| CommitFinishedCallback on_commit_callback) { |
| if (!change_processor()->IsTrackingMetadata()) { |
| ReplyToCallback(std::move(on_commit_callback), |
| sync_pb::SharingMessageCommitError::SYNC_TURNED_OFF); |
| return; |
| } |
| |
| if (net::NetworkChangeNotifier::GetConnectionType() == |
| net::NetworkChangeNotifier::CONNECTION_NONE) { |
| ReplyToCallback(std::move(on_commit_callback), |
| sync_pb::SharingMessageCommitError::SYNC_NETWORK_ERROR); |
| return; |
| } |
| |
| std::unique_ptr<syncer::MetadataChangeList> metadata_change_list = |
| CreateMetadataChangeList(); |
| // Fill in the internal message id with unique generated identifier. |
| const std::string message_id = base::GenerateGUID(); |
| specifics->set_message_id(message_id); |
| std::unique_ptr<syncer::EntityData> entity_data = |
| MoveToEntityData(std::move(specifics)); |
| const syncer::ClientTagHash client_tag_hash = |
| GetClientTagHashFromStorageKey(message_id); |
| |
| DCHECK(pending_commits_.find(client_tag_hash) == pending_commits_.end()); |
| pending_commits_.emplace( |
| client_tag_hash, |
| PendingCommit( |
| std::make_unique<TimedCallback>( |
| std::move(on_commit_callback), |
| base::BindOnce(&SharingMessageBridgeImpl::ProcessCommitTimeout, |
| base::Unretained(this), client_tag_hash)), |
| entity_data->specifics.sharing_message())); |
| |
| change_processor()->Put(message_id, std::move(entity_data), |
| metadata_change_list.get()); |
| } |
| |
| base::WeakPtr<syncer::ModelTypeControllerDelegate> |
| SharingMessageBridgeImpl::GetControllerDelegate() { |
| return change_processor()->GetControllerDelegate(); |
| } |
| |
| std::unique_ptr<syncer::MetadataChangeList> |
| SharingMessageBridgeImpl::CreateMetadataChangeList() { |
| // The data type intentionally doesn't persist the data on disk, so metadata |
| // is just ignored. |
| return std::make_unique<syncer::DummyMetadataChangeList>(); |
| } |
| |
| absl::optional<syncer::ModelError> SharingMessageBridgeImpl::MergeSyncData( |
| std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, |
| syncer::EntityChangeList entity_data) { |
| DCHECK(entity_data.empty()); |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| return {}; |
| } |
| |
| absl::optional<syncer::ModelError> SharingMessageBridgeImpl::ApplySyncChanges( |
| std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, |
| syncer::EntityChangeList entity_changes) { |
| sync_pb::SharingMessageCommitError no_error_message; |
| no_error_message.set_error_code(sync_pb::SharingMessageCommitError::NONE); |
| for (const std::unique_ptr<syncer::EntityChange>& change : entity_changes) { |
| // For commit-only data type we expect only |ACTION_DELETE| changes. |
| DCHECK_EQ(syncer::EntityChange::ACTION_DELETE, change->type()); |
| |
| const syncer::ClientTagHash client_tag_hash = |
| GetClientTagHashFromStorageKey(change->storage_key()); |
| ProcessCommitResponse(client_tag_hash, no_error_message); |
| } |
| return {}; |
| } |
| |
| void SharingMessageBridgeImpl::GetData(StorageKeyList storage_keys, |
| DataCallback callback) { |
| auto batch = std::make_unique<syncer::MutableDataBatch>(); |
| |
| for (const std::string& storage_key : storage_keys) { |
| auto iter = |
| pending_commits_.find(GetClientTagHashFromStorageKey(storage_key)); |
| if (iter == pending_commits_.end()) { |
| continue; |
| } |
| batch->Put(storage_key, CopyToEntityData(iter->second.specifics)); |
| } |
| |
| std::move(callback).Run(std::move(batch)); |
| } |
| |
| void SharingMessageBridgeImpl::GetAllDataForDebugging(DataCallback callback) { |
| auto batch = std::make_unique<syncer::MutableDataBatch>(); |
| |
| for (const auto& cth_to_commit : pending_commits_) { |
| std::unique_ptr<syncer::EntityData> entity_data = |
| CopyToEntityData(cth_to_commit.second.specifics); |
| const std::string storage_key = GetStorageKey(*entity_data); |
| batch->Put(storage_key, std::move(entity_data)); |
| } |
| |
| std::move(callback).Run(std::move(batch)); |
| } |
| |
| std::string SharingMessageBridgeImpl::GetClientTag( |
| const syncer::EntityData& entity_data) { |
| return GetStorageKey(entity_data); |
| } |
| |
| std::string SharingMessageBridgeImpl::GetStorageKey( |
| const syncer::EntityData& entity_data) { |
| DCHECK(entity_data.specifics.has_sharing_message()); |
| return entity_data.specifics.sharing_message().message_id(); |
| } |
| |
| void SharingMessageBridgeImpl::OnCommitAttemptErrors( |
| const syncer::FailedCommitResponseDataList& error_response_list) { |
| for (const syncer::FailedCommitResponseData& response : error_response_list) { |
| // We do not want to retry committing again, thus, the bridge has to untrack |
| // the failed item. |
| change_processor()->UntrackEntityForClientTagHash(response.client_tag_hash); |
| ProcessCommitResponse( |
| response.client_tag_hash, |
| response.datatype_specific_error.sharing_message_error()); |
| } |
| } |
| |
| syncer::ModelTypeSyncBridge::CommitAttemptFailedBehavior |
| SharingMessageBridgeImpl::OnCommitAttemptFailed( |
| syncer::SyncCommitError commit_error) { |
| // Full commit failed means we need to drop all entities and report an error |
| // using callback. |
| sync_pb::SharingMessageCommitError::ErrorCode sharing_message_error_code; |
| switch (commit_error) { |
| case syncer::SyncCommitError::kNetworkError: |
| sharing_message_error_code = |
| sync_pb::SharingMessageCommitError::SYNC_NETWORK_ERROR; |
| break; |
| case syncer::SyncCommitError::kAuthError: |
| // Ignore the auth error because it may be a temporary error and the |
| // message will be sent on the second attempt. |
| return CommitAttemptFailedBehavior::kShouldRetryOnNextCycle; |
| case syncer::SyncCommitError::kServerError: |
| case syncer::SyncCommitError::kBadServerResponse: |
| sharing_message_error_code = |
| sync_pb::SharingMessageCommitError::SYNC_SERVER_ERROR; |
| break; |
| } |
| |
| sync_pb::SharingMessageCommitError sync_error_message; |
| sync_error_message.set_error_code(sharing_message_error_code); |
| for (auto& cth_and_commit : pending_commits_) { |
| change_processor()->UntrackEntityForClientTagHash(cth_and_commit.first); |
| cth_and_commit.second.timed_callback->Run(sync_error_message); |
| } |
| pending_commits_.clear(); |
| return CommitAttemptFailedBehavior::kDontRetryOnNextCycle; |
| } |
| |
| void SharingMessageBridgeImpl::ApplyStopSyncChanges( |
| std::unique_ptr<syncer::MetadataChangeList> metadata_change_list) { |
| sync_pb::SharingMessageCommitError sync_disabled_error_message; |
| sync_disabled_error_message.set_error_code( |
| sync_pb::SharingMessageCommitError::SYNC_TURNED_OFF); |
| for (auto& cth_and_commit : pending_commits_) { |
| change_processor()->UntrackEntityForClientTagHash(cth_and_commit.first); |
| cth_and_commit.second.timed_callback->Run(sync_disabled_error_message); |
| } |
| pending_commits_.clear(); |
| } |
| |
| void SharingMessageBridgeImpl::ProcessCommitTimeout( |
| const syncer::ClientTagHash& client_tag_hash) { |
| change_processor()->UntrackEntityForClientTagHash(client_tag_hash); |
| sync_pb::SharingMessageCommitError error_message; |
| error_message.set_error_code( |
| sync_pb::SharingMessageCommitError::SYNC_TIMEOUT); |
| ProcessCommitResponse(client_tag_hash, error_message); |
| } |
| |
| void SharingMessageBridgeImpl::ProcessCommitResponse( |
| const syncer::ClientTagHash& client_tag_hash, |
| const sync_pb::SharingMessageCommitError& commit_error_message) { |
| const auto iter = pending_commits_.find(client_tag_hash); |
| if (iter == pending_commits_.end()) { |
| // This may happen if tasks from OnUpdateReceived and OneShotTimer were |
| // added at one time. |
| return; |
| } |
| iter->second.timed_callback->Run(commit_error_message); |
| pending_commits_.erase(iter); |
| } |
| |
| SharingMessageBridgeImpl::TimedCallback::TimedCallback( |
| CommitFinishedCallback commit_callback, |
| base::OnceClosure timeout_callback) |
| : commit_callback_(std::move(commit_callback)) { |
| const base::TimeDelta time_delta = |
| base::Seconds(kSharingMessageBridgeTimeoutSeconds.Get()); |
| timer_.Start(FROM_HERE, time_delta, std::move(timeout_callback)); |
| } |
| |
| SharingMessageBridgeImpl::TimedCallback::~TimedCallback() = default; |
| |
| void SharingMessageBridgeImpl::TimedCallback::Run( |
| const sync_pb::SharingMessageCommitError& commit_error) { |
| DCHECK(commit_callback_); |
| |
| ReplyToCallback(std::move(commit_callback_), commit_error); |
| // |timer_| may be already stopped if Run is called from |
| // ProcessCommitTimeout. |
| if (timer_.IsRunning()) { |
| timer_.Stop(); |
| } |
| } |
| |
| SharingMessageBridgeImpl::PendingCommit::PendingCommit( |
| std::unique_ptr<TimedCallback> timed_callback, |
| sync_pb::SharingMessageSpecifics specifics) |
| : timed_callback(std::move(timed_callback)), |
| specifics(std::move(specifics)) {} |
| |
| SharingMessageBridgeImpl::PendingCommit::~PendingCommit() = default; |
| |
| SharingMessageBridgeImpl::PendingCommit::PendingCommit(PendingCommit&&) = |
| default; |
| SharingMessageBridgeImpl::PendingCommit& |
| SharingMessageBridgeImpl::PendingCommit::operator=(PendingCommit&&) = default; |