OLD | NEW |
| (Empty) |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "components/reading_list/ios/reading_list_model_impl.h" | |
6 | |
7 #include "base/bind.h" | |
8 #include "base/logging.h" | |
9 #include "base/memory/ptr_util.h" | |
10 #include "base/strings/string_util.h" | |
11 #include "base/time/clock.h" | |
12 #include "components/prefs/pref_service.h" | |
13 #include "components/reading_list/ios/reading_list_model_storage.h" | |
14 #include "components/reading_list/ios/reading_list_pref_names.h" | |
15 #include "url/gurl.h" | |
16 | |
17 ReadingListModelImpl::ReadingListModelImpl( | |
18 std::unique_ptr<ReadingListModelStorage> storage, | |
19 PrefService* pref_service, | |
20 std::unique_ptr<base::Clock> clock) | |
21 : entries_(base::MakeUnique<ReadingListEntries>()), | |
22 unread_entry_count_(0), | |
23 read_entry_count_(0), | |
24 unseen_entry_count_(0), | |
25 clock_(std::move(clock)), | |
26 pref_service_(pref_service), | |
27 has_unseen_(false), | |
28 loaded_(false), | |
29 weak_ptr_factory_(this) { | |
30 DCHECK(CalledOnValidThread()); | |
31 DCHECK(clock_); | |
32 if (storage) { | |
33 storage_layer_ = std::move(storage); | |
34 storage_layer_->SetReadingListModel(this, this, clock_.get()); | |
35 } else { | |
36 loaded_ = true; | |
37 } | |
38 has_unseen_ = GetPersistentHasUnseen(); | |
39 } | |
40 | |
41 ReadingListModelImpl::~ReadingListModelImpl() {} | |
42 | |
43 void ReadingListModelImpl::StoreLoaded( | |
44 std::unique_ptr<ReadingListEntries> entries) { | |
45 DCHECK(CalledOnValidThread()); | |
46 DCHECK(entries); | |
47 entries_ = std::move(entries); | |
48 for (auto& iterator : *entries_) { | |
49 UpdateEntryStateCountersOnEntryInsertion(iterator.second); | |
50 } | |
51 DCHECK(read_entry_count_ + unread_entry_count_ == entries_->size()); | |
52 loaded_ = true; | |
53 for (auto& observer : observers_) | |
54 observer.ReadingListModelLoaded(this); | |
55 } | |
56 | |
57 void ReadingListModelImpl::Shutdown() { | |
58 DCHECK(CalledOnValidThread()); | |
59 for (auto& observer : observers_) | |
60 observer.ReadingListModelBeingDeleted(this); | |
61 loaded_ = false; | |
62 } | |
63 | |
64 bool ReadingListModelImpl::loaded() const { | |
65 DCHECK(CalledOnValidThread()); | |
66 return loaded_; | |
67 } | |
68 | |
69 size_t ReadingListModelImpl::size() const { | |
70 DCHECK(CalledOnValidThread()); | |
71 DCHECK(read_entry_count_ + unread_entry_count_ == entries_->size()); | |
72 if (!loaded()) | |
73 return 0; | |
74 return entries_->size(); | |
75 } | |
76 | |
77 size_t ReadingListModelImpl::unread_size() const { | |
78 DCHECK(CalledOnValidThread()); | |
79 DCHECK(read_entry_count_ + unread_entry_count_ == entries_->size()); | |
80 if (!loaded()) | |
81 return 0; | |
82 return unread_entry_count_; | |
83 } | |
84 | |
85 size_t ReadingListModelImpl::unseen_size() const { | |
86 DCHECK(CalledOnValidThread()); | |
87 if (!loaded()) | |
88 return 0; | |
89 return unseen_entry_count_; | |
90 } | |
91 | |
92 void ReadingListModelImpl::SetUnseenFlag() { | |
93 if (!has_unseen_) { | |
94 has_unseen_ = true; | |
95 if (!IsPerformingBatchUpdates()) { | |
96 SetPersistentHasUnseen(true); | |
97 } | |
98 } | |
99 } | |
100 | |
101 bool ReadingListModelImpl::GetLocalUnseenFlag() const { | |
102 DCHECK(CalledOnValidThread()); | |
103 if (!loaded()) | |
104 return false; | |
105 // If there are currently no unseen entries, return false even if has_unseen_ | |
106 // is true. | |
107 // This is possible if the last unseen entry has be removed via sync. | |
108 return has_unseen_ && unseen_entry_count_; | |
109 } | |
110 | |
111 void ReadingListModelImpl::ResetLocalUnseenFlag() { | |
112 DCHECK(CalledOnValidThread()); | |
113 if (!loaded()) { | |
114 return; | |
115 } | |
116 has_unseen_ = false; | |
117 if (!IsPerformingBatchUpdates()) | |
118 SetPersistentHasUnseen(false); | |
119 } | |
120 | |
121 void ReadingListModelImpl::MarkAllSeen() { | |
122 DCHECK(CalledOnValidThread()); | |
123 DCHECK(loaded()); | |
124 if (unseen_entry_count_ == 0) { | |
125 return; | |
126 } | |
127 std::unique_ptr<ReadingListModel::ScopedReadingListBatchUpdate> | |
128 model_batch_updates = BeginBatchUpdates(); | |
129 for (auto& iterator : *entries_) { | |
130 ReadingListEntry& entry = iterator.second; | |
131 if (entry.HasBeenSeen()) { | |
132 continue; | |
133 } | |
134 for (auto& observer : observers_) { | |
135 observer.ReadingListWillUpdateEntry(this, iterator.first); | |
136 } | |
137 UpdateEntryStateCountersOnEntryRemoval(entry); | |
138 entry.SetRead(false, clock_->Now()); | |
139 UpdateEntryStateCountersOnEntryInsertion(entry); | |
140 if (storage_layer_) { | |
141 storage_layer_->SaveEntry(entry); | |
142 } | |
143 for (auto& observer : observers_) { | |
144 observer.ReadingListDidApplyChanges(this); | |
145 } | |
146 } | |
147 DCHECK(unseen_entry_count_ == 0); | |
148 } | |
149 | |
150 bool ReadingListModelImpl::DeleteAllEntries() { | |
151 DCHECK(CalledOnValidThread()); | |
152 if (!loaded()) { | |
153 return false; | |
154 } | |
155 auto scoped_model_batch_updates = BeginBatchUpdates(); | |
156 for (const auto& url : Keys()) { | |
157 RemoveEntryByURL(url); | |
158 } | |
159 return entries_->empty(); | |
160 } | |
161 | |
162 void ReadingListModelImpl::UpdateEntryStateCountersOnEntryRemoval( | |
163 const ReadingListEntry& entry) { | |
164 if (!entry.HasBeenSeen()) { | |
165 unseen_entry_count_--; | |
166 } | |
167 if (entry.IsRead()) { | |
168 read_entry_count_--; | |
169 } else { | |
170 unread_entry_count_--; | |
171 } | |
172 } | |
173 | |
174 void ReadingListModelImpl::UpdateEntryStateCountersOnEntryInsertion( | |
175 const ReadingListEntry& entry) { | |
176 if (!entry.HasBeenSeen()) { | |
177 unseen_entry_count_++; | |
178 } | |
179 if (entry.IsRead()) { | |
180 read_entry_count_++; | |
181 } else { | |
182 unread_entry_count_++; | |
183 } | |
184 } | |
185 | |
186 const std::vector<GURL> ReadingListModelImpl::Keys() const { | |
187 std::vector<GURL> keys; | |
188 for (const auto& iterator : *entries_) { | |
189 keys.push_back(iterator.first); | |
190 } | |
191 return keys; | |
192 } | |
193 | |
194 const ReadingListEntry* ReadingListModelImpl::GetEntryByURL( | |
195 const GURL& gurl) const { | |
196 DCHECK(CalledOnValidThread()); | |
197 DCHECK(loaded()); | |
198 return GetMutableEntryFromURL(gurl); | |
199 } | |
200 | |
201 const ReadingListEntry* ReadingListModelImpl::GetFirstUnreadEntry( | |
202 bool distilled) const { | |
203 DCHECK(CalledOnValidThread()); | |
204 DCHECK(loaded()); | |
205 if (unread_entry_count_ == 0) { | |
206 return nullptr; | |
207 } | |
208 int64_t update_time_all = 0; | |
209 const ReadingListEntry* first_entry_all = nullptr; | |
210 int64_t update_time_distilled = 0; | |
211 const ReadingListEntry* first_entry_distilled = nullptr; | |
212 for (auto& iterator : *entries_) { | |
213 ReadingListEntry& entry = iterator.second; | |
214 if (entry.IsRead()) { | |
215 continue; | |
216 } | |
217 if (entry.UpdateTime() > update_time_all) { | |
218 update_time_all = entry.UpdateTime(); | |
219 first_entry_all = &entry; | |
220 } | |
221 if (entry.DistilledState() == ReadingListEntry::PROCESSED && | |
222 entry.UpdateTime() > update_time_distilled) { | |
223 update_time_distilled = entry.UpdateTime(); | |
224 first_entry_distilled = &entry; | |
225 } | |
226 } | |
227 DCHECK(first_entry_all); | |
228 DCHECK_GT(update_time_all, 0); | |
229 if (distilled && first_entry_distilled) { | |
230 return first_entry_distilled; | |
231 } | |
232 return first_entry_all; | |
233 } | |
234 | |
235 ReadingListEntry* ReadingListModelImpl::GetMutableEntryFromURL( | |
236 const GURL& url) const { | |
237 DCHECK(CalledOnValidThread()); | |
238 DCHECK(loaded()); | |
239 auto iterator = entries_->find(url); | |
240 if (iterator == entries_->end()) { | |
241 return nullptr; | |
242 } | |
243 return &(iterator->second); | |
244 } | |
245 | |
246 void ReadingListModelImpl::SyncAddEntry( | |
247 std::unique_ptr<ReadingListEntry> entry) { | |
248 DCHECK(CalledOnValidThread()); | |
249 DCHECK(loaded()); | |
250 // entry must not already exist. | |
251 DCHECK(GetMutableEntryFromURL(entry->URL()) == nullptr); | |
252 for (auto& observer : observers_) | |
253 observer.ReadingListWillAddEntry(this, *entry); | |
254 UpdateEntryStateCountersOnEntryInsertion(*entry); | |
255 if (!entry->HasBeenSeen()) { | |
256 SetUnseenFlag(); | |
257 } | |
258 GURL url = entry->URL(); | |
259 entries_->insert(std::make_pair(url, std::move(*entry))); | |
260 for (auto& observer : observers_) { | |
261 observer.ReadingListDidAddEntry(this, url, reading_list::ADDED_VIA_SYNC); | |
262 observer.ReadingListDidApplyChanges(this); | |
263 } | |
264 } | |
265 | |
266 ReadingListEntry* ReadingListModelImpl::SyncMergeEntry( | |
267 std::unique_ptr<ReadingListEntry> entry) { | |
268 DCHECK(CalledOnValidThread()); | |
269 DCHECK(loaded()); | |
270 ReadingListEntry* existing_entry = GetMutableEntryFromURL(entry->URL()); | |
271 DCHECK(existing_entry); | |
272 GURL url = entry->URL(); | |
273 | |
274 for (auto& observer : observers_) | |
275 observer.ReadingListWillMoveEntry(this, url); | |
276 | |
277 bool was_seen = existing_entry->HasBeenSeen(); | |
278 UpdateEntryStateCountersOnEntryRemoval(*existing_entry); | |
279 existing_entry->MergeWithEntry(*entry); | |
280 existing_entry = GetMutableEntryFromURL(url); | |
281 UpdateEntryStateCountersOnEntryInsertion(*existing_entry); | |
282 if (was_seen && !existing_entry->HasBeenSeen()) { | |
283 // Only set the flag if a new unseen entry is added. | |
284 SetUnseenFlag(); | |
285 } | |
286 for (auto& observer : observers_) { | |
287 observer.ReadingListDidMoveEntry(this, url); | |
288 observer.ReadingListDidApplyChanges(this); | |
289 } | |
290 | |
291 return existing_entry; | |
292 } | |
293 | |
294 void ReadingListModelImpl::SyncRemoveEntry(const GURL& url) { | |
295 RemoveEntryByURLImpl(url, true); | |
296 } | |
297 | |
298 void ReadingListModelImpl::RemoveEntryByURL(const GURL& url) { | |
299 RemoveEntryByURLImpl(url, false); | |
300 } | |
301 | |
302 void ReadingListModelImpl::RemoveEntryByURLImpl(const GURL& url, | |
303 bool from_sync) { | |
304 DCHECK(CalledOnValidThread()); | |
305 DCHECK(loaded()); | |
306 const ReadingListEntry* entry = GetEntryByURL(url); | |
307 if (!entry) | |
308 return; | |
309 | |
310 for (auto& observer : observers_) | |
311 observer.ReadingListWillRemoveEntry(this, url); | |
312 | |
313 if (storage_layer_ && !from_sync) { | |
314 storage_layer_->RemoveEntry(*entry); | |
315 } | |
316 UpdateEntryStateCountersOnEntryRemoval(*entry); | |
317 | |
318 entries_->erase(url); | |
319 for (auto& observer : observers_) | |
320 observer.ReadingListDidApplyChanges(this); | |
321 } | |
322 | |
323 const ReadingListEntry& ReadingListModelImpl::AddEntry( | |
324 const GURL& url, | |
325 const std::string& title, | |
326 reading_list::EntrySource source) { | |
327 DCHECK(CalledOnValidThread()); | |
328 DCHECK(loaded()); | |
329 DCHECK(url.SchemeIsHTTPOrHTTPS()); | |
330 RemoveEntryByURL(url); | |
331 | |
332 std::string trimmed_title = base::CollapseWhitespaceASCII(title, false); | |
333 | |
334 ReadingListEntry entry(url, trimmed_title, clock_->Now()); | |
335 for (auto& observer : observers_) | |
336 observer.ReadingListWillAddEntry(this, entry); | |
337 UpdateEntryStateCountersOnEntryInsertion(entry); | |
338 SetUnseenFlag(); | |
339 entries_->insert(std::make_pair(url, std::move(entry))); | |
340 | |
341 if (storage_layer_) { | |
342 storage_layer_->SaveEntry(*GetEntryByURL(url)); | |
343 } | |
344 | |
345 for (auto& observer : observers_) { | |
346 observer.ReadingListDidAddEntry(this, url, source); | |
347 observer.ReadingListDidApplyChanges(this); | |
348 } | |
349 | |
350 return entries_->at(url); | |
351 } | |
352 | |
353 void ReadingListModelImpl::SetReadStatus(const GURL& url, bool read) { | |
354 DCHECK(CalledOnValidThread()); | |
355 DCHECK(loaded()); | |
356 auto iterator = entries_->find(url); | |
357 if (iterator == entries_->end()) { | |
358 return; | |
359 } | |
360 ReadingListEntry& entry = iterator->second; | |
361 if (entry.IsRead() == read) { | |
362 return; | |
363 } | |
364 for (ReadingListModelObserver& observer : observers_) { | |
365 observer.ReadingListWillMoveEntry(this, url); | |
366 } | |
367 UpdateEntryStateCountersOnEntryRemoval(entry); | |
368 entry.SetRead(read, clock_->Now()); | |
369 entry.MarkEntryUpdated(clock_->Now()); | |
370 UpdateEntryStateCountersOnEntryInsertion(entry); | |
371 | |
372 if (storage_layer_) { | |
373 storage_layer_->SaveEntry(entry); | |
374 } | |
375 for (ReadingListModelObserver& observer : observers_) { | |
376 observer.ReadingListDidMoveEntry(this, url); | |
377 observer.ReadingListDidApplyChanges(this); | |
378 } | |
379 } | |
380 | |
381 void ReadingListModelImpl::SetEntryTitle(const GURL& url, | |
382 const std::string& title) { | |
383 DCHECK(CalledOnValidThread()); | |
384 DCHECK(loaded()); | |
385 auto iterator = entries_->find(url); | |
386 if (iterator == entries_->end()) { | |
387 return; | |
388 } | |
389 ReadingListEntry& entry = iterator->second; | |
390 std::string trimmed_title = base::CollapseWhitespaceASCII(title, false); | |
391 if (entry.Title() == trimmed_title) { | |
392 return; | |
393 } | |
394 | |
395 for (ReadingListModelObserver& observer : observers_) { | |
396 observer.ReadingListWillUpdateEntry(this, url); | |
397 } | |
398 entry.SetTitle(trimmed_title, clock_->Now()); | |
399 if (storage_layer_) { | |
400 storage_layer_->SaveEntry(entry); | |
401 } | |
402 for (ReadingListModelObserver& observer : observers_) { | |
403 observer.ReadingListDidApplyChanges(this); | |
404 } | |
405 } | |
406 | |
407 void ReadingListModelImpl::SetEntryDistilledInfo( | |
408 const GURL& url, | |
409 const base::FilePath& distilled_path, | |
410 const GURL& distilled_url, | |
411 int64_t distillation_size, | |
412 const base::Time& distillation_date) { | |
413 DCHECK(CalledOnValidThread()); | |
414 DCHECK(loaded()); | |
415 auto iterator = entries_->find(url); | |
416 if (iterator == entries_->end()) { | |
417 return; | |
418 } | |
419 ReadingListEntry& entry = iterator->second; | |
420 if (entry.DistilledState() == ReadingListEntry::PROCESSED && | |
421 entry.DistilledPath() == distilled_path) { | |
422 return; | |
423 } | |
424 | |
425 for (ReadingListModelObserver& observer : observers_) { | |
426 observer.ReadingListWillUpdateEntry(this, url); | |
427 } | |
428 entry.SetDistilledInfo(distilled_path, distilled_url, distillation_size, | |
429 distillation_date); | |
430 if (storage_layer_) { | |
431 storage_layer_->SaveEntry(entry); | |
432 } | |
433 for (ReadingListModelObserver& observer : observers_) { | |
434 observer.ReadingListDidApplyChanges(this); | |
435 } | |
436 } | |
437 | |
438 void ReadingListModelImpl::SetEntryDistilledState( | |
439 const GURL& url, | |
440 ReadingListEntry::DistillationState state) { | |
441 DCHECK(CalledOnValidThread()); | |
442 DCHECK(loaded()); | |
443 auto iterator = entries_->find(url); | |
444 if (iterator == entries_->end()) { | |
445 return; | |
446 } | |
447 ReadingListEntry& entry = iterator->second; | |
448 if (entry.DistilledState() == state) { | |
449 return; | |
450 } | |
451 | |
452 for (ReadingListModelObserver& observer : observers_) { | |
453 observer.ReadingListWillUpdateEntry(this, url); | |
454 } | |
455 entry.SetDistilledState(state); | |
456 if (storage_layer_) { | |
457 storage_layer_->SaveEntry(entry); | |
458 } | |
459 for (ReadingListModelObserver& observer : observers_) { | |
460 observer.ReadingListDidApplyChanges(this); | |
461 } | |
462 } | |
463 | |
464 std::unique_ptr<ReadingListModel::ScopedReadingListBatchUpdate> | |
465 ReadingListModelImpl::CreateBatchToken() { | |
466 return base::MakeUnique<ReadingListModelImpl::ScopedReadingListBatchUpdate>( | |
467 this); | |
468 } | |
469 | |
470 ReadingListModelImpl::ScopedReadingListBatchUpdate:: | |
471 ScopedReadingListBatchUpdate(ReadingListModelImpl* model) | |
472 : ReadingListModel::ScopedReadingListBatchUpdate:: | |
473 ScopedReadingListBatchUpdate(model) { | |
474 if (model->StorageLayer()) { | |
475 storage_token_ = model->StorageLayer()->EnsureBatchCreated(); | |
476 } | |
477 } | |
478 | |
479 ReadingListModelImpl::ScopedReadingListBatchUpdate:: | |
480 ~ScopedReadingListBatchUpdate() { | |
481 storage_token_.reset(); | |
482 } | |
483 | |
484 void ReadingListModelImpl::LeavingBatchUpdates() { | |
485 DCHECK(CalledOnValidThread()); | |
486 if (storage_layer_) { | |
487 SetPersistentHasUnseen(has_unseen_); | |
488 } | |
489 ReadingListModel::LeavingBatchUpdates(); | |
490 } | |
491 | |
492 void ReadingListModelImpl::EnteringBatchUpdates() { | |
493 DCHECK(CalledOnValidThread()); | |
494 ReadingListModel::EnteringBatchUpdates(); | |
495 } | |
496 | |
497 void ReadingListModelImpl::SetPersistentHasUnseen(bool has_unseen) { | |
498 DCHECK(CalledOnValidThread()); | |
499 if (!pref_service_) { | |
500 return; | |
501 } | |
502 pref_service_->SetBoolean(reading_list::prefs::kReadingListHasUnseenEntries, | |
503 has_unseen); | |
504 } | |
505 | |
506 bool ReadingListModelImpl::GetPersistentHasUnseen() { | |
507 DCHECK(CalledOnValidThread()); | |
508 if (!pref_service_) { | |
509 return false; | |
510 } | |
511 return pref_service_->GetBoolean( | |
512 reading_list::prefs::kReadingListHasUnseenEntries); | |
513 } | |
514 | |
515 syncer::ModelTypeSyncBridge* ReadingListModelImpl::GetModelTypeSyncBridge() { | |
516 if (!storage_layer_) | |
517 return nullptr; | |
518 return storage_layer_.get(); | |
519 } | |
520 | |
521 ReadingListModelStorage* ReadingListModelImpl::StorageLayer() { | |
522 return storage_layer_.get(); | |
523 } | |
OLD | NEW |