| // Copyright 2018 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 "components/send_tab_to_self/send_tab_to_self_bridge.h" |
| |
| #include <algorithm> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/guid.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/optional.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/send_tab_to_self/features.h" |
| #include "components/send_tab_to_self/proto/send_tab_to_self.pb.h" |
| #include "components/send_tab_to_self/target_device_info.h" |
| #include "components/sync/model/entity_change.h" |
| #include "components/sync/model/metadata_batch.h" |
| #include "components/sync/model/metadata_change_list.h" |
| #include "components/sync/model/model_type_change_processor.h" |
| #include "components/sync/model/mutable_data_batch.h" |
| #include "components/sync/protocol/model_type_state.pb.h" |
| #include "components/sync_device_info/device_info_tracker.h" |
| #include "components/sync_device_info/local_device_info_util.h" |
| |
| namespace send_tab_to_self { |
| |
| namespace { |
| |
| // Status of the result of AddEntry. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class UMAAddEntryStatus { |
| // The add entry call was successful. |
| SUCCESS = 0, |
| // The add entry call failed. |
| FAILURE = 1, |
| // The add entry call was a duplication. |
| DUPLICATE = 2, |
| // Update kMaxValue when new enums are added. |
| kMaxValue = DUPLICATE, |
| }; |
| // Status of if entries target the local device. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class UMANotifyLocalDevice { |
| // The added entry was sent to the local machine. |
| LOCAL = 0, |
| // The added entry was targeted at a different machine. |
| REMOTE = 1, |
| // Update kMaxValue when new enums are added. |
| kMaxValue = REMOTE, |
| }; |
| |
| // Status of the result of ApplySyncChanges |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class UMAApplySyncChangesStatus { |
| // The total number of entity changes received. |
| TOTAL = 0, |
| // The number of entity changes that are deletions. |
| DELETE = 1, |
| // The number of entity changes that are invalid. |
| INVALID = 2, |
| // The number of entity changes that are expired. |
| EXPIRED = 3, |
| // The number of entity changes that are sent to clients as deletions. |
| NOTIFY_DELETED = 4, |
| // The number of entity changes that are sent to clients as adds. |
| NOTIFY_ADDED = 5, |
| // The number of entity changes that are sent to clients as opened. |
| NOTIFY_OPENED = 6, |
| // Update kMaxValue when new enums are added. |
| kMaxValue = NOTIFY_OPENED, |
| }; |
| |
| using syncer::ModelTypeStore; |
| |
| const base::TimeDelta kDedupeTime = base::TimeDelta::FromSeconds(5); |
| |
| const base::TimeDelta kDeviceExpiration = base::TimeDelta::FromDays(10); |
| |
| const char kAddEntryStatus[] = "SendTabToSelf.Sync.AddEntryStatus"; |
| |
| const char kNotifyLocalDevice[] = "SendTabToSelf.Sync.NotifyLocalDevice"; |
| |
| const char kApplySyncChangesStatus[] = |
| "SendTabToSelf.Sync.ApplySyncChangesStatus"; |
| |
| // A helper function to log the status of ApplySyncChanges. |
| void LogApplySyncChangesStatus(UMAApplySyncChangesStatus status) { |
| UMA_HISTOGRAM_ENUMERATION(kApplySyncChangesStatus, status); |
| } |
| // A helper function to log the status of filtering for the current device. |
| void LogLocalDeviceNotified(UMANotifyLocalDevice status) { |
| UMA_HISTOGRAM_ENUMERATION(kNotifyLocalDevice, status); |
| } |
| |
| // Converts a time field from sync protobufs to a time object. |
| base::Time ProtoTimeToTime(int64_t proto_t) { |
| return base::Time::FromDeltaSinceWindowsEpoch( |
| base::TimeDelta::FromMicroseconds(proto_t)); |
| } |
| |
| // Allocate a EntityData and copies |specifics| into it. |
| std::unique_ptr<syncer::EntityData> CopyToEntityData( |
| const sync_pb::SendTabToSelfSpecifics& specifics) { |
| auto entity_data = std::make_unique<syncer::EntityData>(); |
| *entity_data->specifics.mutable_send_tab_to_self() = specifics; |
| entity_data->name = specifics.url(); |
| entity_data->creation_time = ProtoTimeToTime(specifics.shared_time_usec()); |
| return entity_data; |
| } |
| |
| // Parses the content of |record_list| into |*initial_data|. The output |
| // parameter is first for binding purposes. |
| base::Optional<syncer::ModelError> ParseLocalEntriesOnBackendSequence( |
| base::Time now, |
| std::map<std::string, std::unique_ptr<SendTabToSelfEntry>>* entries, |
| std::string* local_personalizable_device_name, |
| std::unique_ptr<ModelTypeStore::RecordList> record_list) { |
| DCHECK(entries); |
| DCHECK(entries->empty()); |
| DCHECK(local_personalizable_device_name); |
| DCHECK(record_list); |
| |
| *local_personalizable_device_name = |
| syncer::GetPersonalizableDeviceNameBlocking(); |
| |
| for (const syncer::ModelTypeStore::Record& r : *record_list) { |
| auto specifics = std::make_unique<SendTabToSelfLocal>(); |
| if (specifics->ParseFromString(r.value)) { |
| (*entries)[specifics->specifics().guid()] = |
| SendTabToSelfEntry::FromLocalProto(*specifics, now); |
| } else { |
| return syncer::ModelError(FROM_HERE, "Failed to deserialize specifics."); |
| } |
| } |
| |
| return base::nullopt; |
| } |
| |
| } // namespace |
| |
| SendTabToSelfBridge::SendTabToSelfBridge( |
| std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor, |
| base::Clock* clock, |
| syncer::OnceModelTypeStoreFactory create_store_callback, |
| history::HistoryService* history_service, |
| syncer::DeviceInfoTracker* device_info_tracker) |
| : ModelTypeSyncBridge(std::move(change_processor)), |
| clock_(clock), |
| history_service_(history_service), |
| device_info_tracker_(device_info_tracker), |
| mru_entry_(nullptr) { |
| DCHECK(clock_); |
| DCHECK(device_info_tracker_); |
| if (history_service) { |
| history_service->AddObserver(this); |
| } |
| |
| std::move(create_store_callback) |
| .Run(syncer::SEND_TAB_TO_SELF, |
| base::BindOnce(&SendTabToSelfBridge::OnStoreCreated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| SendTabToSelfBridge::~SendTabToSelfBridge() { |
| if (history_service_) { |
| history_service_->RemoveObserver(this); |
| } |
| } |
| |
| std::unique_ptr<syncer::MetadataChangeList> |
| SendTabToSelfBridge::CreateMetadataChangeList() { |
| return ModelTypeStore::WriteBatch::CreateMetadataChangeList(); |
| } |
| |
| base::Optional<syncer::ModelError> SendTabToSelfBridge::MergeSyncData( |
| std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, |
| syncer::EntityChangeList entity_data) { |
| DCHECK(entries_.empty()); |
| return ApplySyncChanges(std::move(metadata_change_list), |
| std::move(entity_data)); |
| } |
| |
| base::Optional<syncer::ModelError> SendTabToSelfBridge::ApplySyncChanges( |
| std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, |
| syncer::EntityChangeList entity_changes) { |
| std::vector<const SendTabToSelfEntry*> added; |
| |
| // The opened vector will accumulate both added entries that are already |
| // opened as well as existing entries that have been updated to be marked as |
| // opened. |
| std::vector<const SendTabToSelfEntry*> opened; |
| std::vector<std::string> removed; |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| for (const std::unique_ptr<syncer::EntityChange>& change : entity_changes) { |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::TOTAL); |
| const std::string& guid = change->storage_key(); |
| if (change->type() == syncer::EntityChange::ACTION_DELETE) { |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::DELETE); |
| if (entries_.find(guid) != entries_.end()) { |
| entries_.erase(change->storage_key()); |
| batch->DeleteData(guid); |
| removed.push_back(change->storage_key()); |
| |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::NOTIFY_DELETED); |
| } |
| } else { |
| |
| const sync_pb::SendTabToSelfSpecifics& specifics = |
| change->data().specifics.send_tab_to_self(); |
| |
| std::unique_ptr<SendTabToSelfEntry> remote_entry = |
| SendTabToSelfEntry::FromProto(specifics, clock_->Now()); |
| if (!remote_entry) { |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::INVALID); |
| continue; // Skip invalid entries. |
| } |
| if (remote_entry->IsExpired(clock_->Now())) { |
| // Remove expired data from server. |
| |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::EXPIRED); |
| change_processor()->Delete(guid, batch->GetMetadataChangeList()); |
| } else { |
| SendTabToSelfEntry* local_entry = |
| GetMutableEntryByGUID(remote_entry->GetGUID()); |
| SendTabToSelfLocal remote_entry_pb = remote_entry->AsLocalProto(); |
| |
| if (local_entry == nullptr) { |
| // This remote_entry is new. Add it to the model. |
| |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::NOTIFY_ADDED); |
| added.push_back(remote_entry.get()); |
| if (remote_entry->IsOpened()) { |
| LogApplySyncChangesStatus(UMAApplySyncChangesStatus::NOTIFY_OPENED); |
| opened.push_back(remote_entry.get()); |
| } |
| entries_[remote_entry->GetGUID()] = std::move(remote_entry); |
| } else { |
| // Update existing model if entries have been opened. |
| if (remote_entry->IsOpened() && !local_entry->IsOpened()) { |
| local_entry->MarkOpened(); |
| opened.push_back(local_entry); |
| } |
| } |
| |
| // Write to the store. |
| batch->WriteData(guid, remote_entry_pb.SerializeAsString()); |
| } |
| } |
| } |
| |
| batch->TakeMetadataChangesFrom(std::move(metadata_change_list)); |
| Commit(std::move(batch)); |
| |
| NotifyRemoteSendTabToSelfEntryDeleted(removed); |
| NotifyRemoteSendTabToSelfEntryAdded(added); |
| NotifyRemoteSendTabToSelfEntryOpened(opened); |
| |
| return base::nullopt; |
| } |
| |
| void SendTabToSelfBridge::GetData(StorageKeyList storage_keys, |
| DataCallback callback) { |
| auto batch = std::make_unique<syncer::MutableDataBatch>(); |
| |
| for (const std::string& guid : storage_keys) { |
| const SendTabToSelfEntry* entry = GetEntryByGUID(guid); |
| if (!entry) { |
| continue; |
| } |
| |
| batch->Put(guid, CopyToEntityData(entry->AsLocalProto().specifics())); |
| } |
| std::move(callback).Run(std::move(batch)); |
| } |
| |
| void SendTabToSelfBridge::GetAllDataForDebugging(DataCallback callback) { |
| auto batch = std::make_unique<syncer::MutableDataBatch>(); |
| for (const auto& it : entries_) { |
| batch->Put(it.first, |
| CopyToEntityData(it.second->AsLocalProto().specifics())); |
| } |
| std::move(callback).Run(std::move(batch)); |
| } |
| |
| std::string SendTabToSelfBridge::GetClientTag( |
| const syncer::EntityData& entity_data) { |
| return GetStorageKey(entity_data); |
| } |
| |
| std::string SendTabToSelfBridge::GetStorageKey( |
| const syncer::EntityData& entity_data) { |
| return entity_data.specifics.send_tab_to_self().guid(); |
| } |
| |
| void SendTabToSelfBridge::ApplyStopSyncChanges( |
| std::unique_ptr<syncer::MetadataChangeList> delete_metadata_change_list) { |
| // If |delete_metadata_change_list| is null, it indicates that sync metadata |
| // shouldn't be deleted, for example chrome is shutting down. |
| if (!delete_metadata_change_list) { |
| return; |
| } |
| |
| DCHECK(store_); |
| |
| store_->DeleteAllDataAndMetadata(base::DoNothing()); |
| |
| std::vector<std::string> all_guids = GetAllGuids(); |
| |
| entries_.clear(); |
| mru_entry_ = nullptr; |
| NotifyRemoteSendTabToSelfEntryDeleted(all_guids); |
| } |
| |
| std::vector<std::string> SendTabToSelfBridge::GetAllGuids() const { |
| std::vector<std::string> keys; |
| for (const auto& it : entries_) { |
| DCHECK_EQ(it.first, it.second->GetGUID()); |
| keys.push_back(it.first); |
| } |
| return keys; |
| } |
| |
| void SendTabToSelfBridge::DeleteAllEntries() { |
| if (!change_processor()->IsTrackingMetadata()) { |
| DCHECK_EQ(0ul, entries_.size()); |
| return; |
| } |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| std::vector<std::string> all_guids = GetAllGuids(); |
| |
| for (const auto& guid : all_guids) { |
| change_processor()->Delete(guid, batch->GetMetadataChangeList()); |
| batch->DeleteData(guid); |
| } |
| entries_.clear(); |
| mru_entry_ = nullptr; |
| |
| NotifyRemoteSendTabToSelfEntryDeleted(all_guids); |
| } |
| |
| const SendTabToSelfEntry* SendTabToSelfBridge::GetEntryByGUID( |
| const std::string& guid) const { |
| auto it = entries_.find(guid); |
| if (it == entries_.end()) { |
| return nullptr; |
| } |
| return it->second.get(); |
| } |
| |
| const SendTabToSelfEntry* SendTabToSelfBridge::AddEntry( |
| const GURL& url, |
| const std::string& title, |
| base::Time navigation_time, |
| const std::string& target_device_cache_guid) { |
| if (!change_processor()->IsTrackingMetadata()) { |
| // TODO(crbug.com/940512) handle failure case. |
| UMA_HISTOGRAM_ENUMERATION(kAddEntryStatus, UMAAddEntryStatus::FAILURE); |
| return nullptr; |
| } |
| |
| if (!url.is_valid()) { |
| UMA_HISTOGRAM_ENUMERATION(kAddEntryStatus, UMAAddEntryStatus::FAILURE); |
| return nullptr; |
| } |
| |
| // In the case where the user has attempted to send an identical URL |
| // within the last |kDedupeTime| we think it is likely that user still |
| // has the first sent tab in progress, and so we will not attempt to resend. |
| base::Time shared_time = clock_->Now(); |
| if (mru_entry_ && url == mru_entry_->GetURL() && |
| navigation_time == mru_entry_->GetOriginalNavigationTime() && |
| shared_time - mru_entry_->GetSharedTime() < kDedupeTime) { |
| UMA_HISTOGRAM_ENUMERATION(kAddEntryStatus, UMAAddEntryStatus::DUPLICATE); |
| return mru_entry_; |
| } |
| |
| std::string guid = base::GenerateGUID(); |
| |
| // Assure that we don't have a guid collision. |
| DCHECK_EQ(GetEntryByGUID(guid), nullptr); |
| |
| std::string trimmed_title = ""; |
| |
| if (base::IsStringUTF8(title)) { |
| trimmed_title = base::CollapseWhitespaceASCII(title, false); |
| } |
| |
| auto entry = std::make_unique<SendTabToSelfEntry>( |
| guid, url, trimmed_title, shared_time, navigation_time, |
| local_device_name_, target_device_cache_guid); |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| // This entry is new. Add it to the store and model. |
| auto entity_data = CopyToEntityData(entry->AsLocalProto().specifics()); |
| |
| change_processor()->Put(guid, std::move(entity_data), |
| batch->GetMetadataChangeList()); |
| |
| const SendTabToSelfEntry* result = |
| entries_.emplace(guid, std::move(entry)).first->second.get(); |
| |
| batch->WriteData(guid, result->AsLocalProto().SerializeAsString()); |
| |
| Commit(std::move(batch)); |
| mru_entry_ = result; |
| |
| UMA_HISTOGRAM_ENUMERATION(kAddEntryStatus, UMAAddEntryStatus::SUCCESS); |
| return result; |
| } |
| |
| void SendTabToSelfBridge::DeleteEntry(const std::string& guid) { |
| // Assure that an entry with that guid exists. |
| if (GetEntryByGUID(guid) == nullptr) { |
| return; |
| } |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| DeleteEntryWithBatch(guid, batch.get()); |
| |
| Commit(std::move(batch)); |
| } |
| |
| void SendTabToSelfBridge::DismissEntry(const std::string& guid) { |
| SendTabToSelfEntry* entry = GetMutableEntryByGUID(guid); |
| // Assure that an entry with that guid exists. |
| if (!entry) { |
| return; |
| } |
| |
| entry->SetNotificationDismissed(true); |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| batch->WriteData(guid, entry->AsLocalProto().SerializeAsString()); |
| Commit(std::move(batch)); |
| } |
| |
| void SendTabToSelfBridge::MarkEntryOpened(const std::string& guid) { |
| SendTabToSelfEntry* entry = GetMutableEntryByGUID(guid); |
| // Assure that an entry with that guid exists. |
| if (!entry) { |
| return; |
| } |
| |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| |
| entry->MarkOpened(); |
| |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| auto entity_data = CopyToEntityData(entry->AsLocalProto().specifics()); |
| |
| change_processor()->Put(guid, std::move(entity_data), |
| batch->GetMetadataChangeList()); |
| |
| batch->WriteData(guid, entry->AsLocalProto().SerializeAsString()); |
| Commit(std::move(batch)); |
| } |
| |
| void SendTabToSelfBridge::OnURLsDeleted( |
| history::HistoryService* history_service, |
| const history::DeletionInfo& deletion_info) { |
| // We only care about actual user (or sync) deletions. |
| |
| if (!change_processor()->IsTrackingMetadata()) { |
| return; // Sync processor not yet ready, don't sync. |
| } |
| |
| if (deletion_info.is_from_expiration()) |
| return; |
| |
| if (!deletion_info.IsAllHistory()) { |
| std::vector<GURL> urls; |
| |
| for (const history::URLRow& row : deletion_info.deleted_rows()) { |
| urls.push_back(row.url()); |
| } |
| |
| DeleteEntries(urls); |
| return; |
| } |
| |
| // All history was cleared: just delete all entries. |
| DeleteAllEntries(); |
| } |
| |
| bool SendTabToSelfBridge::IsReady() { |
| return change_processor()->IsTrackingMetadata(); |
| } |
| |
| bool SendTabToSelfBridge::HasValidTargetDevice() { |
| if (ShouldUpdateTargetDeviceInfoList()) { |
| SetTargetDeviceInfoList(); |
| } |
| return target_device_name_to_cache_info_.size() > 0; |
| } |
| |
| std::vector<TargetDeviceInfo> |
| SendTabToSelfBridge::GetTargetDeviceInfoSortedList() { |
| if (ShouldUpdateTargetDeviceInfoList()) { |
| SetTargetDeviceInfoList(); |
| } |
| return target_device_name_to_cache_info_; |
| } |
| |
| // static |
| std::unique_ptr<syncer::ModelTypeStore> |
| SendTabToSelfBridge::DestroyAndStealStoreForTest( |
| std::unique_ptr<SendTabToSelfBridge> bridge) { |
| return std::move(bridge->store_); |
| } |
| |
| bool SendTabToSelfBridge::ShouldUpdateTargetDeviceInfoListForTest() { |
| return ShouldUpdateTargetDeviceInfoList(); |
| } |
| |
| void SendTabToSelfBridge::SetLocalDeviceNameForTest( |
| const std::string& local_device_name) { |
| local_device_name_ = local_device_name; |
| } |
| |
| void SendTabToSelfBridge::NotifyRemoteSendTabToSelfEntryAdded( |
| const std::vector<const SendTabToSelfEntry*>& new_entries) { |
| if (new_entries.empty()) { |
| return; |
| } |
| |
| std::vector<const SendTabToSelfEntry*> new_local_entries; |
| |
| if (base::FeatureList::IsEnabled(kSendTabToSelfBroadcast)) { |
| new_local_entries = new_entries; |
| } else { |
| // Only pass along entries that are not dismissed or opened, and are |
| // targeted at this device, which is determined by comparing the cache guid |
| // associated with the entry to each device's local list of recently used |
| // cache_guids |
| DCHECK(!change_processor()->TrackedCacheGuid().empty()); |
| for (const SendTabToSelfEntry* entry : new_entries) { |
| if (device_info_tracker_->IsRecentLocalCacheGuid( |
| entry->GetTargetDeviceSyncCacheGuid()) && |
| !entry->GetNotificationDismissed() && !entry->IsOpened()) { |
| new_local_entries.push_back(entry); |
| LogLocalDeviceNotified(UMANotifyLocalDevice::LOCAL); |
| } else { |
| LogLocalDeviceNotified(UMANotifyLocalDevice::REMOTE); |
| } |
| } |
| } |
| |
| for (SendTabToSelfModelObserver& observer : observers_) { |
| observer.EntriesAddedRemotely(new_local_entries); |
| } |
| } |
| |
| void SendTabToSelfBridge::NotifyRemoteSendTabToSelfEntryDeleted( |
| const std::vector<std::string>& guids) { |
| if (guids.empty()) { |
| return; |
| } |
| |
| // TODO(crbug.com/956216): Only send the entries that targeted this device. |
| for (SendTabToSelfModelObserver& observer : observers_) { |
| observer.EntriesRemovedRemotely(guids); |
| } |
| } |
| |
| void SendTabToSelfBridge::NotifyRemoteSendTabToSelfEntryOpened( |
| const std::vector<const SendTabToSelfEntry*>& opened_entries) { |
| if (opened_entries.empty()) { |
| return; |
| } |
| for (SendTabToSelfModelObserver& observer : observers_) { |
| observer.EntriesOpenedRemotely(opened_entries); |
| } |
| } |
| |
| void SendTabToSelfBridge::NotifySendTabToSelfModelLoaded() { |
| for (SendTabToSelfModelObserver& observer : observers_) { |
| observer.SendTabToSelfModelLoaded(); |
| } |
| } |
| |
| void SendTabToSelfBridge::OnStoreCreated( |
| const base::Optional<syncer::ModelError>& error, |
| std::unique_ptr<syncer::ModelTypeStore> store) { |
| if (error) { |
| change_processor()->ReportError(*error); |
| return; |
| } |
| |
| auto initial_entries = std::make_unique<SendTabToSelfEntries>(); |
| SendTabToSelfEntries* initial_entries_copy = initial_entries.get(); |
| |
| auto local_device_name = std::make_unique<std::string>(); |
| std::string* local_device_name_copy = local_device_name.get(); |
| |
| store_ = std::move(store); |
| store_->ReadAllDataAndPreprocess( |
| base::BindOnce(&ParseLocalEntriesOnBackendSequence, clock_->Now(), |
| base::Unretained(initial_entries_copy), |
| base::Unretained(local_device_name_copy)), |
| base::BindOnce(&SendTabToSelfBridge::OnReadAllData, |
| weak_ptr_factory_.GetWeakPtr(), std::move(initial_entries), |
| std::move(local_device_name))); |
| } |
| |
| void SendTabToSelfBridge::OnReadAllData( |
| std::unique_ptr<SendTabToSelfEntries> initial_entries, |
| std::unique_ptr<std::string> local_device_name, |
| const base::Optional<syncer::ModelError>& error) { |
| DCHECK(initial_entries); |
| DCHECK(local_device_name); |
| |
| if (error) { |
| change_processor()->ReportError(*error); |
| return; |
| } |
| |
| entries_ = std::move(*initial_entries); |
| local_device_name_ = std::move(*local_device_name); |
| |
| store_->ReadAllMetadata(base::BindOnce( |
| &SendTabToSelfBridge::OnReadAllMetadata, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void SendTabToSelfBridge::OnReadAllMetadata( |
| const base::Optional<syncer::ModelError>& error, |
| std::unique_ptr<syncer::MetadataBatch> metadata_batch) { |
| if (error) { |
| change_processor()->ReportError(*error); |
| return; |
| } |
| change_processor()->ModelReadyToSync(std::move(metadata_batch)); |
| NotifySendTabToSelfModelLoaded(); |
| |
| DoGarbageCollection(); |
| } |
| |
| void SendTabToSelfBridge::OnCommit( |
| const base::Optional<syncer::ModelError>& error) { |
| if (error) { |
| change_processor()->ReportError(*error); |
| } |
| } |
| |
| void SendTabToSelfBridge::Commit( |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch) { |
| store_->CommitWriteBatch(std::move(batch), |
| base::BindOnce(&SendTabToSelfBridge::OnCommit, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| SendTabToSelfEntry* SendTabToSelfBridge::GetMutableEntryByGUID( |
| const std::string& guid) const { |
| auto it = entries_.find(guid); |
| if (it == entries_.end()) { |
| return nullptr; |
| } |
| return it->second.get(); |
| } |
| |
| void SendTabToSelfBridge::DoGarbageCollection() { |
| std::vector<std::string> removed; |
| |
| auto entry = entries_.begin(); |
| while (entry != entries_.end()) { |
| DCHECK_EQ(entry->first, entry->second->GetGUID()); |
| std::string guid = entry->first; |
| bool expired = entry->second->IsExpired(clock_->Now()); |
| entry++; |
| if (expired) { |
| DeleteEntry(guid); |
| removed.push_back(guid); |
| } |
| } |
| NotifyRemoteSendTabToSelfEntryDeleted(removed); |
| } |
| |
| bool SendTabToSelfBridge::ShouldUpdateTargetDeviceInfoList() const { |
| // The vector should be updated if any of these is true: |
| // * The vector is empty. |
| // * The number of total devices changed. |
| // * The oldest non-expired entry in the vector is now expired. |
| return target_device_name_to_cache_info_.empty() || |
| device_info_tracker_->GetAllDeviceInfo().size() != |
| number_of_devices_ || |
| clock_->Now() - oldest_non_expired_device_timestamp_ > |
| kDeviceExpiration; |
| } |
| |
| void SendTabToSelfBridge::SetTargetDeviceInfoList() { |
| // Verify that the current TrackedCacheGuid() is the local GUID without |
| // enforcing that tracked cache GUID is set. |
| DCHECK(device_info_tracker_->IsRecentLocalCacheGuid( |
| change_processor()->TrackedCacheGuid()) || |
| change_processor()->TrackedCacheGuid().empty()); |
| |
| std::vector<std::unique_ptr<syncer::DeviceInfo>> all_devices = |
| device_info_tracker_->GetAllDeviceInfo(); |
| number_of_devices_ = all_devices.size(); |
| |
| // Sort the DeviceInfo vector so the most recently modified devices are first. |
| std::stable_sort(all_devices.begin(), all_devices.end(), |
| [](const std::unique_ptr<syncer::DeviceInfo>& device1, |
| const std::unique_ptr<syncer::DeviceInfo>& device2) { |
| return device1->last_updated_timestamp() > |
| device2->last_updated_timestamp(); |
| }); |
| |
| target_device_name_to_cache_info_.clear(); |
| std::set<std::string> unique_device_names; |
| std::unordered_map<std::string, int> short_names_counter; |
| for (const auto& device : all_devices) { |
| // If the current device is considered expired for our purposes, stop here |
| // since the next devices in the vector are at least as expired than this |
| // one. |
| if (clock_->Now() - device->last_updated_timestamp() > kDeviceExpiration) { |
| break; |
| } |
| |
| // Don't include this device if it is the local device. |
| if (device_info_tracker_->IsRecentLocalCacheGuid(device->guid())) { |
| continue; |
| } |
| |
| DCHECK_NE(device->guid(), change_processor()->TrackedCacheGuid()); |
| |
| // Don't include devices that have disabled the send tab to self receiving |
| // feature. |
| if (!device->send_tab_to_self_receiving_enabled()) { |
| continue; |
| } |
| |
| SharingDeviceNames device_names = GetSharingDeviceNames(device.get()); |
| |
| // Only keep one device per device name. We only keep the first occurrence |
| // which is the most recent. |
| if (unique_device_names.insert(device_names.full_name).second) { |
| TargetDeviceInfo target_device_info( |
| device_names.full_name, device_names.short_name, device->guid(), |
| device->device_type(), device->last_updated_timestamp()); |
| target_device_name_to_cache_info_.push_back(target_device_info); |
| oldest_non_expired_device_timestamp_ = device->last_updated_timestamp(); |
| |
| short_names_counter[device_names.short_name]++; |
| } |
| } |
| for (auto& device_info : target_device_name_to_cache_info_) { |
| bool unique_short_name = short_names_counter[device_info.short_name] == 1; |
| device_info.device_name = |
| (unique_short_name ? device_info.short_name : device_info.full_name); |
| } |
| } |
| |
| void SendTabToSelfBridge::DeleteEntryWithBatch( |
| const std::string& guid, |
| ModelTypeStore::WriteBatch* batch) { |
| // Assure that an entry with that guid exists. |
| DCHECK(GetEntryByGUID(guid) != nullptr); |
| DCHECK(change_processor()->IsTrackingMetadata()); |
| |
| change_processor()->Delete(guid, batch->GetMetadataChangeList()); |
| |
| if (mru_entry_ && mru_entry_->GetGUID() == guid) { |
| mru_entry_ = nullptr; |
| } |
| |
| entries_.erase(guid); |
| batch->DeleteData(guid); |
| } |
| |
| void SendTabToSelfBridge::DeleteEntries(const std::vector<GURL>& urls) { |
| std::unique_ptr<ModelTypeStore::WriteBatch> batch = |
| store_->CreateWriteBatch(); |
| |
| std::vector<std::string> removed_guids; |
| |
| for (const GURL url : urls) { |
| auto entry = entries_.begin(); |
| while (entry != entries_.end()) { |
| bool to_delete = (url == entry->second->GetURL()); |
| |
| std::string guid = entry->first; |
| entry++; |
| if (to_delete) { |
| removed_guids.push_back(guid); |
| DeleteEntryWithBatch(guid, batch.get()); |
| } |
| } |
| } |
| Commit(std::move(batch)); |
| // To err on the side of completeness this notifies all clients that these |
| // entries have been removed. Regardless of if these entries were removed |
| // "remotely". |
| NotifyRemoteSendTabToSelfEntryDeleted(removed_guids); |
| } |
| |
| } // namespace send_tab_to_self |