| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/reading_list/core/reading_list_model_impl.h" |
| |
| #include "base/auto_reset.h" |
| #include "base/check_is_test.h" |
| #include "base/check_op.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/observer_list.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/clock.h" |
| #include "components/reading_list/core/reading_list_model_storage.h" |
| #include "components/reading_list/core/reading_list_sync_bridge.h" |
| #include "components/sync/model/client_tag_based_data_type_processor.h" |
| #include "google_apis/gaia/gaia_id.h" |
| #include "url/gurl.h" |
| |
| ReadingListModelImpl::ScopedReadingListBatchUpdateImpl:: |
| ScopedReadingListBatchUpdateImpl(ReadingListModelImpl* model) |
| : model_(model) { |
| model->AddObserver(this); |
| storage_token_ = model->StorageLayer()->EnsureBatchCreated(); |
| DCHECK(storage_token_); |
| } |
| |
| ReadingListModelImpl::ScopedReadingListBatchUpdateImpl:: |
| ~ScopedReadingListBatchUpdateImpl() { |
| storage_token_.reset(); |
| if (model_) { |
| model_->RemoveObserver(this); |
| model_->EndBatchUpdates(); |
| } |
| } |
| |
| syncer::MetadataChangeList* ReadingListModelImpl:: |
| ScopedReadingListBatchUpdateImpl::GetSyncMetadataChangeList() { |
| DCHECK(storage_token_); |
| return storage_token_->GetSyncMetadataChangeList(); |
| } |
| |
| ReadingListModelStorage::ScopedBatchUpdate* |
| ReadingListModelImpl::ScopedReadingListBatchUpdateImpl::GetStorageBatch() { |
| DCHECK(storage_token_); |
| return storage_token_.get(); |
| } |
| |
| void ReadingListModelImpl::ScopedReadingListBatchUpdateImpl:: |
| ReadingListModelLoaded(const ReadingListModel* model) {} |
| |
| void ReadingListModelImpl::ScopedReadingListBatchUpdateImpl:: |
| ReadingListModelBeingShutdown(const ReadingListModel* model) { |
| storage_token_.reset(); |
| model_->EndBatchUpdates(); |
| model_->RemoveObserver(this); |
| model_ = nullptr; |
| } |
| |
| ReadingListModelImpl::ReadingListModelImpl( |
| std::unique_ptr<ReadingListModelStorage> storage_layer, |
| syncer::StorageType sync_storage_type_for_uma, |
| syncer::WipeModelUponSyncDisabledBehavior |
| wipe_model_upon_sync_disabled_behavior, |
| base::Clock* clock) |
| : ReadingListModelImpl( |
| std::move(storage_layer), |
| sync_storage_type_for_uma, |
| wipe_model_upon_sync_disabled_behavior, |
| clock, |
| std::make_unique<syncer::ClientTagBasedDataTypeProcessor>( |
| syncer::READING_LIST, |
| /*dump_stack=*/base::DoNothing())) {} |
| |
| ReadingListModelImpl::ReadingListModelImpl( |
| std::unique_ptr<ReadingListModelStorage> storage_layer, |
| syncer::StorageType sync_storage_type_for_uma, |
| syncer::WipeModelUponSyncDisabledBehavior |
| wipe_model_upon_sync_disabled_behavior, |
| base::Clock* clock, |
| std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor) |
| : storage_layer_(std::move(storage_layer)), |
| clock_(clock), |
| sync_bridge_(sync_storage_type_for_uma, |
| wipe_model_upon_sync_disabled_behavior, |
| clock, |
| std::move(change_processor)) { |
| DCHECK(clock_); |
| DCHECK(storage_layer_); |
| |
| storage_layer_->Load(clock_, |
| base::BindOnce(&ReadingListModelImpl::StoreLoaded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| ReadingListModelImpl::~ReadingListModelImpl() { |
| for (auto& observer : observers_) { |
| observer.ReadingListModelBeingDeleted(this); |
| } |
| } |
| |
| void ReadingListModelImpl::Shutdown() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| for (auto& observer : observers_) |
| observer.ReadingListModelBeingShutdown(this); |
| loaded_ = false; |
| } |
| |
| bool ReadingListModelImpl::loaded() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return loaded_; |
| } |
| |
| bool ReadingListModelImpl::IsPerformingBatchUpdates() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return current_batch_updates_count_ > 0; |
| } |
| |
| std::unique_ptr<ReadingListModelImpl::ScopedReadingListBatchUpdate> |
| ReadingListModelImpl::BeginBatchUpdates() { |
| return BeginBatchUpdatesWithSyncMetadata(); |
| } |
| |
| size_t ReadingListModelImpl::size() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(read_entry_count_ + unread_entry_count_ == entries_.size()); |
| if (!loaded()) |
| return 0; |
| return entries_.size(); |
| } |
| |
| size_t ReadingListModelImpl::unread_size() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(read_entry_count_ + unread_entry_count_ == entries_.size()); |
| if (!loaded()) |
| return 0; |
| return unread_entry_count_; |
| } |
| |
| size_t ReadingListModelImpl::unseen_size() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!loaded()) |
| return 0; |
| return unseen_entry_count_; |
| } |
| |
| void ReadingListModelImpl::MarkAllSeen() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| if (unseen_entry_count_ == 0) { |
| return; |
| } |
| |
| std::unique_ptr<ScopedReadingListBatchUpdateImpl> batch = |
| BeginBatchUpdatesWithSyncMetadata(); |
| |
| for (auto& iterator : entries_) { |
| MarkEntrySeenImpl(iterator.second.get()); |
| } |
| DCHECK(unseen_entry_count_ == 0); |
| } |
| |
| bool ReadingListModelImpl::DeleteAllEntries(const base::Location& location) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!loaded()) { |
| return false; |
| } |
| auto scoped_model_batch_updates = BeginBatchUpdates(); |
| for (const auto& url : GetKeys()) { |
| RemoveEntryByURL(url, location); |
| } |
| |
| DCHECK(entries_.empty()); |
| return true; |
| } |
| |
| void ReadingListModelImpl::UpdateEntryStateCountersOnEntryRemoval( |
| const ReadingListEntry& entry) { |
| if (!entry.HasBeenSeen()) { |
| unseen_entry_count_--; |
| } |
| if (entry.IsRead()) { |
| read_entry_count_--; |
| } else { |
| unread_entry_count_--; |
| } |
| } |
| |
| void ReadingListModelImpl::UpdateEntryStateCountersOnEntryInsertion( |
| const ReadingListEntry& entry) { |
| if (!entry.HasBeenSeen()) { |
| unseen_entry_count_++; |
| } |
| if (entry.IsRead()) { |
| read_entry_count_++; |
| } else { |
| unread_entry_count_++; |
| } |
| } |
| |
| base::flat_set<GURL> ReadingListModelImpl::GetKeys() const { |
| std::vector<GURL> keys; |
| keys.reserve(entries_.size()); |
| for (const auto& url_and_entry : entries_) { |
| keys.push_back(url_and_entry.first); |
| } |
| return base::flat_set<GURL>(base::sorted_unique, std::move(keys)); |
| } |
| |
| scoped_refptr<const ReadingListEntry> ReadingListModelImpl::GetEntryByURL( |
| const GURL& gurl) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| return base::WrapRefCounted( |
| const_cast<ReadingListModelImpl*>(this)->GetMutableEntryFromURL(gurl)); |
| } |
| |
| ReadingListEntry* ReadingListModelImpl::GetMutableEntryFromURL( |
| const GURL& url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return nullptr; |
| } |
| return iterator->second.get(); |
| } |
| |
| ReadingListEntry* ReadingListModelImpl::SyncMergeEntry( |
| scoped_refptr<ReadingListEntry> entry) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| DCHECK(IsPerformingBatchUpdates()); |
| |
| const GURL url = entry->URL(); |
| ReadingListEntry* existing_entry = GetMutableEntryFromURL(url); |
| DCHECK(existing_entry); |
| |
| for (auto& observer : observers_) |
| observer.ReadingListWillUpdateEntry(this, url); |
| |
| UpdateEntryStateCountersOnEntryRemoval(*existing_entry); |
| existing_entry->MergeWithEntry(*entry); |
| existing_entry = GetMutableEntryFromURL(url); |
| UpdateEntryStateCountersOnEntryInsertion(*existing_entry); |
| |
| // Write to the store. |
| storage_layer_->EnsureBatchCreated()->SaveEntry(*existing_entry); |
| |
| for (auto& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, url); |
| observer.ReadingListDidApplyChanges(this); |
| } |
| |
| return existing_entry; |
| } |
| |
| void ReadingListModelImpl::SyncRemoveEntry(const GURL& url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| DCHECK(IsPerformingBatchUpdates()); |
| |
| RemoveEntryByURLImpl(url, FROM_HERE, true); |
| } |
| |
| void ReadingListModelImpl::RemoveEntryByURL(const GURL& url, |
| const base::Location& location) { |
| RemoveEntryByURLImpl(url, location, false); |
| } |
| |
| void ReadingListModelImpl::RemoveEntryByURLImpl(const GURL& url, |
| const base::Location& location, |
| bool from_sync) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url); |
| if (!entry) |
| return; |
| |
| if (!suppress_deletions_batch_updates_notifications_) { |
| for (auto& observer : observers_) { |
| observer.ReadingListWillRemoveEntry(this, url); |
| } |
| } |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->RemoveEntry(url); |
| |
| if (!from_sync) { |
| sync_bridge_.DidRemoveEntry(*entry, location, |
| batch->GetSyncMetadataChangeList()); |
| } |
| |
| UpdateEntryStateCountersOnEntryRemoval(*entry); |
| |
| entries_.erase(url); |
| |
| if (!suppress_deletions_batch_updates_notifications_) { |
| for (auto& observer : observers_) { |
| observer.ReadingListDidRemoveEntry(this, url); |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| } |
| |
| void ReadingListModelImpl::SyncDeleteAllEntriesAndSyncMetadata() { |
| DeleteAllEntries(FROM_HERE); |
| storage_layer_->DeleteAllEntriesAndSyncMetadata(); |
| } |
| |
| bool ReadingListModelImpl::IsUrlSupported(const GURL& url) { |
| return url.SchemeIsHTTPOrHTTPS(); |
| } |
| |
| GaiaId ReadingListModelImpl::GetAccountWhereEntryIsSavedTo(const GURL& url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| |
| if (entries_.find(url) == entries_.end()) { |
| return GaiaId(); |
| } |
| return sync_bridge_.change_processor()->TrackedGaiaId(); |
| } |
| |
| bool ReadingListModelImpl::NeedsExplicitUploadToSyncServer( |
| const GURL& url) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Returning true only makes sense for an implementation that maintains a |
| // separate set of local and account entries (DualReadingListModel). |
| return false; |
| } |
| |
| void ReadingListModelImpl::MarkAllForUploadToSyncServerIfNeeded() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Uploading the entries only makes sense for an implementation that maintains |
| // a separate set of local and account entries (DualReadingListModel). |
| } |
| |
| const ReadingListEntry& ReadingListModelImpl::AddOrReplaceEntry( |
| const GURL& url, |
| const std::string& title, |
| reading_list::EntrySource source, |
| std::optional<base::TimeDelta> estimated_read_time, |
| std::optional<base::Time> creation_time) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| DCHECK(IsUrlSupported(url)); |
| |
| std::unique_ptr<ReadingListModelImpl::ScopedReadingListBatchUpdate> |
| scoped_model_batch_updates; |
| if (GetEntryByURL(url)) { |
| scoped_model_batch_updates = BeginBatchUpdates(); |
| RemoveEntryByURL(url, FROM_HERE); |
| } |
| |
| std::string trimmed_title = TrimTitle(title); |
| |
| auto entry = base::MakeRefCounted<ReadingListEntry>( |
| url, trimmed_title, creation_time.value_or(clock_->Now())); |
| if (estimated_read_time.has_value()) { |
| entry->SetEstimatedReadTime(*estimated_read_time); |
| } |
| |
| AddEntry(std::move(entry), source); |
| |
| base::UmaHistogramEnumeration("ReadingList.AddOrReplaceEntry", |
| GetStorageStateForUma()); |
| |
| return *(entries_.at(url)); |
| } |
| |
| void ReadingListModelImpl::SetReadStatusIfExists(const GURL& url, bool read) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return; |
| } |
| ReadingListEntry& entry = *(iterator->second); |
| if (entry.IsRead() == read) { |
| return; |
| } |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListWillUpdateEntry(this, url); |
| } |
| UpdateEntryStateCountersOnEntryRemoval(entry); |
| entry.SetRead(read, clock_->Now()); |
| UpdateEntryStateCountersOnEntryInsertion(entry); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(entry); |
| sync_bridge_.DidAddOrUpdateEntry(entry, batch->GetSyncMetadataChangeList()); |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, url); |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| void ReadingListModelImpl::SetEntryTitleIfExists(const GURL& url, |
| const std::string& title) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return; |
| } |
| ReadingListEntry& entry = *(iterator->second); |
| std::string trimmed_title = TrimTitle(title); |
| if (entry.Title() == trimmed_title) { |
| return; |
| } |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListWillUpdateEntry(this, url); |
| } |
| entry.SetTitle(trimmed_title, clock_->Now()); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(entry); |
| sync_bridge_.DidAddOrUpdateEntry(entry, batch->GetSyncMetadataChangeList()); |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, url); |
| } |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| void ReadingListModelImpl::SetEstimatedReadTimeIfExists( |
| const GURL& url, |
| base::TimeDelta estimated_read_time) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return; |
| } |
| ReadingListEntry& entry = *(iterator->second); |
| if (entry.EstimatedReadTime() == estimated_read_time) { |
| return; |
| } |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListWillUpdateEntry(this, url); |
| } |
| entry.SetEstimatedReadTime(estimated_read_time); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(entry); |
| sync_bridge_.DidAddOrUpdateEntry(entry, batch->GetSyncMetadataChangeList()); |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, url); |
| } |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| void ReadingListModelImpl::SetEntryDistilledInfoIfExists( |
| const GURL& url, |
| const base::FilePath& distilled_path, |
| const GURL& distilled_url, |
| int64_t distillation_size, |
| base::Time distillation_date) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return; |
| } |
| ReadingListEntry& entry = *(iterator->second); |
| if (entry.DistilledState() == ReadingListEntry::PROCESSED && |
| entry.DistilledPath() == distilled_path) { |
| return; |
| } |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListWillUpdateEntry(this, url); |
| } |
| entry.SetDistilledInfo(distilled_path, distilled_url, distillation_size, |
| distillation_date); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(entry); |
| sync_bridge_.DidAddOrUpdateEntry(entry, batch->GetSyncMetadataChangeList()); |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, url); |
| } |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| void ReadingListModelImpl::SetEntryDistilledStateIfExists( |
| const GURL& url, |
| ReadingListEntry::DistillationState state) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return; |
| } |
| ReadingListEntry& entry = *(iterator->second); |
| if (entry.DistilledState() == state) { |
| return; |
| } |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListWillUpdateEntry(this, url); |
| } |
| entry.SetDistilledState(state); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(entry); |
| sync_bridge_.DidAddOrUpdateEntry(entry, batch->GetSyncMetadataChangeList()); |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, url); |
| } |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| void ReadingListModelImpl::AddObserver(ReadingListModelObserver* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(observer); |
| observers_.AddObserver(observer); |
| if (loaded()) { |
| observer->ReadingListModelLoaded(this); |
| } |
| } |
| |
| void ReadingListModelImpl::RemoveObserver(ReadingListModelObserver* observer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void ReadingListModelImpl::RecordCountMetricsOnUMAUpload() const { |
| if (!loaded()) { |
| return; |
| } |
| RecordCountMetrics(".OnUMAUpload"); |
| } |
| |
| void ReadingListModelImpl::AddEntry(scoped_refptr<ReadingListEntry> entry, |
| reading_list::EntrySource source) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(entry); |
| DCHECK(loaded()); |
| DCHECK(GetMutableEntryFromURL(entry->URL()) == nullptr); |
| |
| // TODO(crbug.com/40899983): Should decide if the DCHECK(entry) should be |
| // removed or there's a proper fix that remove the below condition. |
| if (!entry) { |
| return; |
| } |
| |
| const GURL url = entry->URL(); |
| |
| for (auto& observer : observers_) { |
| observer.ReadingListWillAddEntry(this, *entry); |
| } |
| |
| UpdateEntryStateCountersOnEntryInsertion(*entry); |
| |
| auto it = entries_.emplace(url, std::move(entry)).first; |
| const ReadingListEntry* entry_ptr = it->second.get(); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(*GetEntryByURL(url)); |
| if (source != reading_list::ADDED_VIA_SYNC) { |
| sync_bridge_.DidAddOrUpdateEntry(*entry_ptr, |
| batch->GetSyncMetadataChangeList()); |
| } |
| |
| for (auto& observer : observers_) { |
| observer.ReadingListDidAddEntry(this, url, source); |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| std::unique_ptr<ReadingListModelImpl::ScopedReadingListBatchUpdateImpl> |
| ReadingListModelImpl::BeginBatchUpdatesWithSyncMetadata() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto token = std::make_unique<ScopedReadingListBatchUpdateImpl>(this); |
| ++current_batch_updates_count_; |
| if (current_batch_updates_count_ == 1 && |
| !suppress_deletions_batch_updates_notifications_) { |
| for (auto& observer : observers_) { |
| observer.ReadingListModelBeganBatchUpdates(this); |
| } |
| } |
| return token; |
| } |
| |
| void ReadingListModelImpl::MarkEntrySeenIfExists(const GURL& url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(loaded()); |
| |
| auto iterator = entries_.find(url); |
| if (iterator == entries_.end()) { |
| return; |
| } |
| MarkEntrySeenImpl(iterator->second.get()); |
| } |
| |
| bool ReadingListModelImpl::IsTrackingSyncMetadata() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return sync_bridge_.IsTrackingMetadata(); |
| } |
| |
| // static |
| std::string ReadingListModelImpl::TrimTitle(const std::string& title) { |
| return base::CollapseWhitespaceASCII(title, false); |
| } |
| |
| // static |
| std::unique_ptr<ReadingListModelImpl> ReadingListModelImpl::BuildNewForTest( |
| std::unique_ptr<ReadingListModelStorage> storage_layer, |
| syncer::StorageType sync_storage_type, |
| syncer::WipeModelUponSyncDisabledBehavior |
| wipe_model_upon_sync_disabled_behavior, |
| base::Clock* clock, |
| std::unique_ptr<syncer::DataTypeLocalChangeProcessor> change_processor) { |
| CHECK_IS_TEST(); |
| return base::WrapUnique( |
| new ReadingListModelImpl(std::move(storage_layer), sync_storage_type, |
| wipe_model_upon_sync_disabled_behavior, clock, |
| std::move(change_processor))); |
| } |
| |
| ReadingListSyncBridge* ReadingListModelImpl::GetSyncBridgeForTest() { |
| return &sync_bridge_; |
| } |
| |
| ReadingListModelImpl::StorageStateForUma |
| ReadingListModelImpl::GetStorageStateForUma() const { |
| switch (sync_bridge_.GetStorageTypeForUma()) { |
| case syncer::StorageType::kAccount: |
| return StorageStateForUma::kAccount; |
| case syncer::StorageType::kUnspecified: |
| return sync_bridge_.IsTrackingMetadata() |
| ? StorageStateForUma::kSyncEnabled |
| : StorageStateForUma::kLocalOnly; |
| } |
| NOTREACHED(); |
| } |
| |
| std::string ReadingListModelImpl::GetStorageStateSuffixForUma() const { |
| switch (GetStorageStateForUma()) { |
| case StorageStateForUma::kAccount: |
| return ".AccountStorage"; |
| case StorageStateForUma::kLocalOnly: |
| return ".LocalStorage"; |
| case StorageStateForUma::kSyncEnabled: |
| return ".LocalStorageSyncing"; |
| } |
| NOTREACHED(); |
| } |
| |
| void ReadingListModelImpl::StoreLoaded( |
| ReadingListModelStorage::LoadResultOrError result_or_error) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!result_or_error.has_value()) { |
| sync_bridge_.ReportError( |
| {FROM_HERE, syncer::ModelError::Type::kReadingListStorageLoadFailed}); |
| return; |
| } |
| |
| entries_ = std::move(result_or_error.value().first); |
| |
| for (auto& iterator : entries_) { |
| UpdateEntryStateCountersOnEntryInsertion(*(iterator.second)); |
| } |
| |
| DCHECK_EQ(read_entry_count_ + unread_entry_count_, entries_.size()); |
| loaded_ = true; |
| |
| { |
| // In rare cases, ModelReadyToSync() leads to the deletion of all local |
| // entries. Such deletions should not be propagated to observers, because |
| // ReadingListModelLoaded hasn't been broadcasted yet. |
| base::AutoReset<bool> auto_reset_suppress_observer_notifications( |
| &suppress_deletions_batch_updates_notifications_, true); |
| sync_bridge_.ModelReadyToSync(/*model=*/this, |
| std::move(result_or_error.value().second)); |
| } |
| |
| RecordCountMetrics(".OnModelLoaded"); |
| |
| for (auto& observer : observers_) { |
| observer.ReadingListModelLoaded(this); |
| } |
| } |
| |
| void ReadingListModelImpl::EndBatchUpdates() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsPerformingBatchUpdates()); |
| DCHECK(current_batch_updates_count_ > 0); |
| --current_batch_updates_count_; |
| if (current_batch_updates_count_ == 0 && |
| !suppress_deletions_batch_updates_notifications_) { |
| for (auto& observer : observers_) { |
| observer.ReadingListModelCompletedBatchUpdates(this); |
| } |
| } |
| } |
| |
| base::WeakPtr<syncer::DataTypeControllerDelegate> |
| ReadingListModelImpl::GetSyncControllerDelegate() { |
| return sync_bridge_.change_processor()->GetControllerDelegate(); |
| } |
| |
| base::WeakPtr<syncer::DataTypeControllerDelegate> |
| ReadingListModelImpl::GetSyncControllerDelegateForTransportMode() { |
| // ReadingListModelImpl doesn't directly implement account storage. Upper |
| // layers are responsible for maintaining two instances of |
| // ReadingListModelImpl and exposing one of them as account storage. |
| return nullptr; |
| } |
| |
| ReadingListModelStorage* ReadingListModelImpl::StorageLayer() { |
| return storage_layer_.get(); |
| } |
| |
| void ReadingListModelImpl::MarkEntrySeenImpl(ReadingListEntry* entry) { |
| DCHECK(entry); |
| |
| if (entry->HasBeenSeen()) { |
| return; |
| } |
| |
| for (auto& observer : observers_) { |
| observer.ReadingListWillUpdateEntry(this, entry->URL()); |
| } |
| |
| UpdateEntryStateCountersOnEntryRemoval(*entry); |
| DCHECK(!entry->IsRead()); |
| // SetRead() is used to transition the entry from the UNSEEN state to the |
| // UNREAD state. |
| entry->SetRead(false, clock_->Now()); |
| UpdateEntryStateCountersOnEntryInsertion(*entry); |
| |
| std::unique_ptr<ReadingListModelStorage::ScopedBatchUpdate> batch = |
| storage_layer_->EnsureBatchCreated(); |
| batch->SaveEntry(*entry); |
| sync_bridge_.DidAddOrUpdateEntry(*entry, batch->GetSyncMetadataChangeList()); |
| |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidUpdateEntry(this, entry->URL()); |
| } |
| for (ReadingListModelObserver& observer : observers_) { |
| observer.ReadingListDidApplyChanges(this); |
| } |
| } |
| |
| void ReadingListModelImpl::RecordCountMetrics( |
| const std::string& event_suffix) const { |
| CHECK(loaded()); |
| std::string storage_suffix = GetStorageStateSuffixForUma(); |
| base::UmaHistogramCounts1000( |
| base::StrCat({"ReadingList.Unread.Count", event_suffix, storage_suffix}), |
| unread_entry_count_); |
| base::UmaHistogramCounts1000( |
| base::StrCat({"ReadingList.Read.Count", event_suffix, storage_suffix}), |
| read_entry_count_); |
| } |