blob: 56dd3235976976a1ae1773bf26997d21ef494571 [file]
// Copyright 2022 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/dual_reading_list_model.h"
#include "base/auto_reset.h"
#include "base/location.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "components/reading_list/core/reading_list_entry.h"
#include "components/reading_list/core/reading_list_model_impl.h"
#include "components/sync/base/features.h"
#include "google_apis/gaia/core_account_id.h"
#include "google_apis/gaia/gaia_id.h"
#include "url/gurl.h"
namespace reading_list {
DualReadingListModel::ScopedReadingListBatchUpdateImpl::
ScopedReadingListBatchUpdateImpl(
std::unique_ptr<ScopedReadingListBatchUpdate>
local_or_syncable_model_batch,
std::unique_ptr<ScopedReadingListBatchUpdate> account_model_batch)
: local_or_syncable_model_batch_(std::move(local_or_syncable_model_batch)),
account_model_batch_(std::move(account_model_batch)) {}
DualReadingListModel::ScopedReadingListBatchUpdateImpl::
~ScopedReadingListBatchUpdateImpl() = default;
DualReadingListModel::DualReadingListModel(
std::unique_ptr<ReadingListModelImpl> local_or_syncable_model,
std::unique_ptr<ReadingListModelImpl> account_model)
: local_or_syncable_model_(std::move(local_or_syncable_model)),
account_model_(std::move(account_model)) {
DCHECK(local_or_syncable_model_);
DCHECK(account_model_);
local_or_syncable_model_->AddObserver(this);
account_model_->AddObserver(this);
}
DualReadingListModel::~DualReadingListModel() {
local_or_syncable_model_->RemoveObserver(this);
account_model_->RemoveObserver(this);
}
void DualReadingListModel::Shutdown() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
local_or_syncable_model_->Shutdown();
account_model_->Shutdown();
}
bool DualReadingListModel::loaded() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return local_or_syncable_model_->loaded() && account_model_->loaded();
}
base::WeakPtr<syncer::DataTypeControllerDelegate>
DualReadingListModel::GetSyncControllerDelegate() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return local_or_syncable_model_->GetSyncControllerDelegate();
}
base::WeakPtr<syncer::DataTypeControllerDelegate>
DualReadingListModel::GetSyncControllerDelegateForTransportMode() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/40251098): This logic should be moved to a controller and
// made more sophisticated by enabling it only if the user opted in (possibly
// pref-based).
if (syncer::IsReadingListAccountStorageEnabled()) {
return account_model_->GetSyncControllerDelegate();
}
// Otherwise, disable the datatype.
return nullptr;
}
bool DualReadingListModel::IsPerformingBatchUpdates() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return current_batch_updates_count_ > 0;
}
std::unique_ptr<ReadingListModel::ScopedReadingListBatchUpdate>
DualReadingListModel::BeginBatchUpdates() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::make_unique<ScopedReadingListBatchUpdateImpl>(
local_or_syncable_model_->BeginBatchUpdates(),
account_model_->BeginBatchUpdates());
}
base::flat_set<GURL> DualReadingListModel::GetKeys() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::STLSetUnion<base::flat_set<GURL>>(
local_or_syncable_model_->GetKeys(), account_model_->GetKeys());
}
size_t DualReadingListModel::size() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
DCHECK_EQ(unread_entry_count_ + read_entry_count_, GetKeys().size());
return unread_entry_count_ + read_entry_count_;
}
size_t DualReadingListModel::unread_size() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
return unread_entry_count_;
}
size_t DualReadingListModel::unseen_size() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
return unseen_entry_count_;
}
void DualReadingListModel::MarkAllSeen() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
std::unique_ptr<DualReadingListModel::ScopedReadingListBatchUpdate>
scoped_model_batch_updates;
if (unseen_entry_count_ != 0) {
scoped_model_batch_updates = BeginBatchUpdates();
}
for (const auto& url : GetKeys()) {
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
const bool notify_observers = !entry->HasBeenSeen();
if (notify_observers) {
NotifyObserversWithWillUpdateEntry(url);
UpdateEntryStateCountersOnEntryRemoval(*entry);
}
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->MarkEntrySeenIfExists(url);
account_model_->MarkEntrySeenIfExists(url);
}
if (notify_observers) {
UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
NotifyObserversWithDidUpdateEntry(url);
NotifyObserversWithDidApplyChanges();
}
}
DCHECK_EQ(unseen_entry_count_, 0ul);
}
bool DualReadingListModel::DeleteAllEntries(const base::Location& location) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!loaded()) {
return false;
}
// Invoking DeleteAllEntries() for the two underlying instances would be the
// most straightforward implementation, but it will complicate the observer
// notifications. The simplest implementation would be leaning on
// DualReadingListModel::RemoveEntryByURL to remove the entries.
std::unique_ptr<DualReadingListModel::ScopedReadingListBatchUpdate>
scoped_model_batch_updates = BeginBatchUpdates();
for (const auto& url : GetKeys()) {
RemoveEntryByURL(url, location);
}
DCHECK_EQ(0u, local_or_syncable_model_->size());
DCHECK_EQ(0u, account_model_->size());
return true;
}
scoped_refptr<const ReadingListEntry> DualReadingListModel::GetEntryByURL(
const GURL& gurl) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
scoped_refptr<const ReadingListEntry> local_or_syncable_entry =
local_or_syncable_model_->GetEntryByURL(gurl);
scoped_refptr<const ReadingListEntry> account_entry =
account_model_->GetEntryByURL(gurl);
if (!local_or_syncable_entry) {
return account_entry;
}
if (!account_entry) {
return local_or_syncable_entry;
}
scoped_refptr<ReadingListEntry> merged_entry =
local_or_syncable_entry->Clone();
// Merging the account entry into the local one should result in the merged
// view's distilled state being equal to the local entry's. This is because
// the local entry must be older than the account entry, as local entries can
// only be created while the user is signed out.
merged_entry->MergeWithEntry(*account_entry);
return merged_entry;
}
bool DualReadingListModel::IsUrlSupported(const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(local_or_syncable_model_->IsUrlSupported(url),
account_model_->IsUrlSupported(url));
return local_or_syncable_model_->IsUrlSupported(url);
}
GaiaId DualReadingListModel::GetAccountWhereEntryIsSavedTo(const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
GaiaId gaia_id = account_model_->GetAccountWhereEntryIsSavedTo(url);
if (!gaia_id.empty()) {
return gaia_id;
}
// `local_or_syncable_model_` may return an account for the case where it's
// sync-ing.
return local_or_syncable_model_->GetAccountWhereEntryIsSavedTo(url);
}
bool DualReadingListModel::NeedsExplicitUploadToSyncServer(
const GURL& url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!local_or_syncable_model_->IsTrackingSyncMetadata() ||
!account_model_->IsTrackingSyncMetadata());
return account_model_->IsTrackingSyncMetadata() &&
local_or_syncable_model_->GetEntryByURL(url) != nullptr &&
account_model_->GetEntryByURL(url) == nullptr;
}
void DualReadingListModel::MarkAllForUploadToSyncServerIfNeeded() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
MarkEntriesForUploadToSyncServerIfNeeded(local_or_syncable_model_->GetKeys());
}
const ReadingListEntry& DualReadingListModel::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<DualReadingListModel::ScopedReadingListBatchUpdate>
scoped_model_batch_updates;
if (GetEntryByURL(url)) {
scoped_model_batch_updates = BeginBatchUpdates();
RemoveEntryByURL(url, FROM_HERE);
}
if (account_model_->IsTrackingSyncMetadata()) {
const ReadingListEntry& entry = account_model_->AddOrReplaceEntry(
url, title, source, estimated_read_time, creation_time);
DCHECK(!GetAccountWhereEntryIsSavedTo(url).empty());
return entry;
}
const ReadingListEntry& entry = local_or_syncable_model_->AddOrReplaceEntry(
url, title, source, estimated_read_time, creation_time);
DCHECK(!local_or_syncable_model_->IsTrackingSyncMetadata() ||
!GetAccountWhereEntryIsSavedTo(url).empty());
return entry;
}
void DualReadingListModel::RemoveEntryByURL(const GURL& url,
const base::Location& location) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
// If there is no entry with the given URL, then an early return is needed to
// avoid notifying observers.
if (!entry) {
return;
}
NotifyObserversWithWillRemoveEntry(url);
UpdateEntryStateCountersOnEntryRemoval(*entry);
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->RemoveEntryByURL(url, location);
account_model_->RemoveEntryByURL(url, location);
}
NotifyObserversWithDidRemoveEntry(url);
NotifyObserversWithDidApplyChanges();
}
void DualReadingListModel::SetReadStatusIfExists(const GURL& url, bool read) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
if (!entry) {
return;
}
const bool notify_observers = entry->IsRead() != read;
if (notify_observers) {
NotifyObserversWithWillUpdateEntry(url);
UpdateEntryStateCountersOnEntryRemoval(*entry);
}
// The update propagates to both underlying ReadingListModelImpl instances
// even if the `entry` read status is equal to `read`. This is because if
// `entry` was a merged entry, then one of the two underlying entries may have
// a different read status.
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->SetReadStatusIfExists(url, read);
account_model_->SetReadStatusIfExists(url, read);
}
if (notify_observers) {
UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
NotifyObserversWithDidUpdateEntry(url);
NotifyObserversWithDidApplyChanges();
}
}
void DualReadingListModel::SetEntryTitleIfExists(const GURL& url,
const std::string& title) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
if (!entry) {
return;
}
const bool notify_observers =
entry->Title() != ReadingListModelImpl::TrimTitle(title);
if (notify_observers) {
NotifyObserversWithWillUpdateEntry(url);
}
// The update propagates to both underlying ReadingListModelImpl instances
// even if the `entry` title is equal to `title`. This is because if `entry`
// was a merged entry, then one of the two underlying entries may contain a
// different title.
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->SetEntryTitleIfExists(url, title);
account_model_->SetEntryTitleIfExists(url, title);
}
if (notify_observers) {
NotifyObserversWithDidUpdateEntry(url);
NotifyObserversWithDidApplyChanges();
}
}
void DualReadingListModel::SetEstimatedReadTimeIfExists(
const GURL& url,
base::TimeDelta estimated_read_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
if (!entry) {
return;
}
const bool notify_observers =
entry->EstimatedReadTime() != estimated_read_time;
if (notify_observers) {
NotifyObserversWithWillUpdateEntry(url);
}
// The update propagates to both underlying ReadingListModelImpl instances
// even if the `entry` estimated read time is equal to `estimated_read_time`.
// This is because if `entry` was a merged entry, then one of the two
// underlying entries may have a different estimated read time.
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->SetEstimatedReadTimeIfExists(url,
estimated_read_time);
account_model_->SetEstimatedReadTimeIfExists(url, estimated_read_time);
}
if (notify_observers) {
NotifyObserversWithDidUpdateEntry(url);
NotifyObserversWithDidApplyChanges();
}
}
void DualReadingListModel::SetEntryDistilledStateIfExists(
const GURL& url,
ReadingListEntry::DistillationState state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
if (!entry) {
return;
}
const bool notify_observers = entry->DistilledState() != state;
if (notify_observers) {
NotifyObserversWithWillUpdateEntry(url);
}
// The update propagates to both underlying ReadingListModelImpl instances
// even if the `entry` distilled state is equal to `state`. This is because if
// `entry` was a merged entry, then its distilled state is equal to the local
// entry's distilled state and the account entry may have a different
// distilled state.
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->SetEntryDistilledStateIfExists(url, state);
account_model_->SetEntryDistilledStateIfExists(url, state);
}
if (notify_observers) {
NotifyObserversWithDidUpdateEntry(url);
NotifyObserversWithDidApplyChanges();
}
}
void DualReadingListModel::SetEntryDistilledInfoIfExists(
const GURL& url,
const base::FilePath& distilled_path,
const GURL& distilled_url,
int64_t distilation_size,
base::Time distilation_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(loaded());
scoped_refptr<const ReadingListEntry> entry = GetEntryByURL(url);
if (!entry) {
return;
}
const bool notify_observers =
entry->DistilledState() != ReadingListEntry::PROCESSED ||
entry->DistilledPath() != distilled_path;
if (notify_observers) {
NotifyObserversWithWillUpdateEntry(url);
}
// The update propagates to both underlying ReadingListModelImpl instances
// even if the `entry` distilled path is equal to `distilled_path`. This is
// because if `entry` was a merged entry, then its distilled path is equal to
// the local entry's distilled path and the account entry may have a different
// distilled path.
{
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
local_or_syncable_model_->SetEntryDistilledInfoIfExists(
url, distilled_path, distilled_url, distilation_size, distilation_time);
account_model_->SetEntryDistilledInfoIfExists(
url, distilled_path, distilled_url, distilation_size, distilation_time);
}
if (notify_observers) {
NotifyObserversWithDidUpdateEntry(url);
NotifyObserversWithDidApplyChanges();
}
}
void DualReadingListModel::AddObserver(ReadingListModelObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(observer);
observers_.AddObserver(observer);
if (loaded()) {
observer->ReadingListModelLoaded(this);
}
}
void DualReadingListModel::RemoveObserver(ReadingListModelObserver* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.RemoveObserver(observer);
}
void DualReadingListModel::RecordCountMetricsOnUMAUpload() const {
local_or_syncable_model_->RecordCountMetricsOnUMAUpload();
account_model_->RecordCountMetricsOnUMAUpload();
}
void DualReadingListModel::ReadingListModelBeganBatchUpdates(
const ReadingListModel* model) {
DCHECK(!suppress_observer_notifications_);
if (!loaded()) {
return;
}
++current_batch_updates_count_;
if (current_batch_updates_count_ == 1) {
for (auto& observer : observers_) {
observer.ReadingListModelBeganBatchUpdates(this);
}
}
}
void DualReadingListModel::ReadingListModelCompletedBatchUpdates(
const ReadingListModel* model) {
DCHECK(!suppress_observer_notifications_);
if (!loaded()) {
return;
}
--current_batch_updates_count_;
if (current_batch_updates_count_ == 0) {
for (auto& observer : observers_) {
observer.ReadingListModelCompletedBatchUpdates(this);
}
}
}
void DualReadingListModel::ReadingListModelLoaded(
const ReadingListModel* model) {
DCHECK(!suppress_observer_notifications_);
if (!loaded()) {
return;
}
for (const auto& url : GetKeys()) {
UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
}
for (auto& observer : observers_) {
observer.ReadingListModelLoaded(this);
}
}
void DualReadingListModel::ReadingListWillRemoveEntry(
const ReadingListModel* model,
const GURL& url) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
if (local_or_syncable_model_->GetEntryByURL(url) &&
account_model_->GetEntryByURL(url)) {
// The fact that the entry exists in one of the models means the result is
// not an actual deletion but, at most, an update.
NotifyObserversWithWillUpdateEntry(url);
} else {
NotifyObserversWithWillRemoveEntry(url);
}
UpdateEntryStateCountersOnEntryRemoval(*GetEntryByURL(url));
}
void DualReadingListModel::ReadingListDidRemoveEntry(
const ReadingListModel* model,
const GURL& url) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
if (local_or_syncable_model_->GetEntryByURL(url) ||
account_model_->GetEntryByURL(url)) {
// The entry is still present in one of the models, so this is an
// update rather than a deletion.
UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
NotifyObserversWithDidUpdateEntry(url);
return;
}
NotifyObserversWithDidRemoveEntry(url);
}
void DualReadingListModel::ReadingListWillAddEntry(
const ReadingListModel* model,
const ReadingListEntry& entry) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
if (local_or_syncable_model_->GetEntryByURL(entry.URL()) ||
account_model_->GetEntryByURL(entry.URL())) {
// The presence of the entry in one of the models indicates that this is an
// update, not an insertion.
NotifyObserversWithWillUpdateEntry(entry.URL());
UpdateEntryStateCountersOnEntryRemoval(*GetEntryByURL(entry.URL()));
return;
}
for (auto& observer : observers_) {
observer.ReadingListWillAddEntry(this, entry);
}
}
void DualReadingListModel::ReadingListDidAddEntry(
const ReadingListModel* model,
const GURL& url,
reading_list::EntrySource source) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
if (local_or_syncable_model_->GetEntryByURL(url) &&
account_model_->GetEntryByURL(url)) {
// The entry was added to one of the models, but since it was already
// present in the other one, then this is an update instead of insertion.
NotifyObserversWithDidUpdateEntry(url);
return;
}
for (auto& observer : observers_) {
observer.ReadingListDidAddEntry(this, url, source);
}
}
void DualReadingListModel::ReadingListWillUpdateEntry(
const ReadingListModel* model,
const GURL& url) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
NotifyObserversWithWillUpdateEntry(url);
UpdateEntryStateCountersOnEntryRemoval(*GetEntryByURL(url));
}
void DualReadingListModel::ReadingListDidUpdateEntry(
const ReadingListModel* model,
const GURL& url) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
UpdateEntryStateCountersOnEntryInsertion(*GetEntryByURL(url));
NotifyObserversWithDidUpdateEntry(url);
}
void DualReadingListModel::ReadingListDidApplyChanges(ReadingListModel* model) {
if (!loaded() || suppress_observer_notifications_) {
return;
}
NotifyObserversWithDidApplyChanges();
}
DualReadingListModel::StorageStateForTesting
DualReadingListModel::GetStorageStateForURLForTesting(const GURL& url) {
const bool exists_in_local_or_syncable_model =
local_or_syncable_model_->GetEntryByURL(url) != nullptr;
const bool exists_in_account_model =
account_model_->GetEntryByURL(url) != nullptr;
if (exists_in_local_or_syncable_model && exists_in_account_model) {
return StorageStateForTesting::kExistsInBothModels;
}
if (exists_in_local_or_syncable_model) {
return StorageStateForTesting::kExistsInLocalOrSyncableModelOnly;
}
if (exists_in_account_model) {
return StorageStateForTesting::kExistsInAccountModelOnly;
}
return StorageStateForTesting::kNotFound;
}
void DualReadingListModel::NotifyObserversWithWillRemoveEntry(const GURL& url) {
for (auto& observer : observers_) {
observer.ReadingListWillRemoveEntry(this, url);
}
}
void DualReadingListModel::NotifyObserversWithDidRemoveEntry(const GURL& url) {
for (auto& observer : observers_) {
observer.ReadingListDidRemoveEntry(this, url);
}
}
void DualReadingListModel::NotifyObserversWithWillUpdateEntry(const GURL& url) {
for (auto& observer : observers_) {
observer.ReadingListWillUpdateEntry(this, url);
}
}
void DualReadingListModel::NotifyObserversWithDidUpdateEntry(const GURL& url) {
for (auto& observer : observers_) {
observer.ReadingListDidUpdateEntry(this, url);
}
}
void DualReadingListModel::NotifyObserversWithDidApplyChanges() {
for (auto& observer : observers_) {
observer.ReadingListDidApplyChanges(this);
}
}
const ReadingListModelImpl* DualReadingListModel::ToReadingListModelImpl(
const ReadingListModel* model) {
DCHECK(model == account_model_.get() ||
model == local_or_syncable_model_.get());
// It is safe to use static_cast because both `account_model_` and
// `local_or_syncable_model_` are ReadingListModelImpl, and hence it is also
// the case for model.
return static_cast<const ReadingListModelImpl*>(model);
}
void DualReadingListModel::UpdateEntryStateCountersOnEntryRemoval(
const ReadingListEntry& entry) {
if (!entry.HasBeenSeen()) {
unseen_entry_count_--;
}
if (entry.IsRead()) {
read_entry_count_--;
} else {
unread_entry_count_--;
}
}
void DualReadingListModel::UpdateEntryStateCountersOnEntryInsertion(
const ReadingListEntry& entry) {
if (!entry.HasBeenSeen()) {
unseen_entry_count_++;
}
if (entry.IsRead()) {
read_entry_count_++;
} else {
unread_entry_count_++;
}
}
base::flat_set<GURL> DualReadingListModel::GetKeysThatNeedUploadToSyncServer()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK(!local_or_syncable_model_->IsTrackingSyncMetadata() ||
!account_model_->IsTrackingSyncMetadata());
// If `local_or_syncable_model_` is used for sync, no data needs explicit
// upload to the sync server.
if (local_or_syncable_model_->IsTrackingSyncMetadata()) {
return {};
}
return local_or_syncable_model_->GetKeys();
}
void DualReadingListModel::MarkEntriesForUploadToSyncServerIfNeeded(
const base::flat_set<GURL>& urls) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!account_model_->IsTrackingSyncMetadata()) {
return;
}
base::AutoReset<bool> auto_reset_suppress_observer_notifications(
&suppress_observer_notifications_, true);
for (const GURL& url : urls) {
if (!local_or_syncable_model_->GetEntryByURL(url)) {
// Entry with url does not exist in the local model, so there is nothing
// to upload.
continue;
}
scoped_refptr<ReadingListEntry> entry = GetEntryByURL(url)->Clone();
local_or_syncable_model_->RemoveEntryByURL(url, FROM_HERE);
// If the url already exists in the account model, remove the account entry
// first before adding the "merged" entry back to the account model.
// Note: This workaround is used than just using AddOrReplaceEntry() to
// avoid ReadingListModelBeganBatchUpdates() being triggered inside
// AddOrReplaceEntry(), which causes observers to be notified even though
// this particular function does not need to send any notifications at all
// (including ReadingListModelBeganBatchUpdates).
account_model_->RemoveEntryByURL(url, FROM_HERE);
account_model_->AddEntry(std::move(entry),
reading_list::ADDED_VIA_CURRENT_APP);
// The entry state counters do not need to be updated since no value was
// "effectively" removed from the dual reading list model.
}
}
ReadingListModel* DualReadingListModel::GetLocalOrSyncableModel() {
return local_or_syncable_model_.get();
}
ReadingListModel* DualReadingListModel::GetAccountModelIfSyncing() {
return account_model_->IsTrackingSyncMetadata() ? account_model_.get()
: nullptr;
}
} // namespace reading_list