1 // Copyright 2015 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.
5 #include "components/offline_pages/offline_page_model.h"
10 #include "base/files/file_util.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram_macros.h"
14 #include "base/sequenced_task_runner.h"
15 #include "base/time/time.h"
16 #include "components/bookmarks/browser/bookmark_model.h"
17 #include "components/bookmarks/browser/bookmark_node.h"
18 #include "components/offline_pages/offline_page_item.h"
19 #include "components/offline_pages/offline_page_metadata_store.h"
22 using ArchiverResult
= offline_pages::OfflinePageArchiver::ArchiverResult
;
23 using SavePageResult
= offline_pages::OfflinePageModel::SavePageResult
;
25 namespace offline_pages
{
29 // Threshold for how old offline copy of a page should be before we offer to
30 // delete it to free up space.
31 const base::TimeDelta kPageCleanUpThreshold
= base::TimeDelta::FromDays(30);
33 SavePageResult
ToSavePageResult(ArchiverResult archiver_result
) {
34 SavePageResult result
;
35 switch (archiver_result
) {
36 case ArchiverResult::SUCCESSFULLY_CREATED
:
37 result
= SavePageResult::SUCCESS
;
39 case ArchiverResult::ERROR_DEVICE_FULL
:
40 result
= SavePageResult::DEVICE_FULL
;
42 case ArchiverResult::ERROR_CONTENT_UNAVAILABLE
:
43 result
= SavePageResult::CONTENT_UNAVAILABLE
;
45 case ArchiverResult::ERROR_ARCHIVE_CREATION_FAILED
:
46 result
= SavePageResult::ARCHIVE_CREATION_FAILED
;
48 case ArchiverResult::ERROR_CANCELED
:
49 result
= SavePageResult::CANCELLED
;
53 result
= SavePageResult::CONTENT_UNAVAILABLE
;
58 void DeleteArchiveFiles(const std::vector
<base::FilePath
>& paths_to_delete
,
61 for (const auto& file_path
: paths_to_delete
) {
62 // Make sure delete happens on the left of || so that it is always executed.
63 *success
= base::DeleteFile(file_path
, false) || *success
;
67 void EmptyDeleteCallback(OfflinePageModel::DeletePageResult
/* result */) {
72 OfflinePageModel::OfflinePageModel(
73 scoped_ptr
<OfflinePageMetadataStore
> store
,
74 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
)
75 : store_(store
.Pass()),
77 task_runner_(task_runner
),
78 scoped_observer_(this),
79 weak_ptr_factory_(this) {
80 store_
->Load(base::Bind(&OfflinePageModel::OnLoadDone
,
81 weak_ptr_factory_
.GetWeakPtr()));
84 OfflinePageModel::~OfflinePageModel() {
87 void OfflinePageModel::Start(bookmarks::BookmarkModel
* model
) {
88 scoped_observer_
.Add(model
);
91 void OfflinePageModel::Shutdown() {
92 scoped_observer_
.RemoveAll();
95 void OfflinePageModel::AddObserver(Observer
* observer
) {
96 observers_
.AddObserver(observer
);
99 void OfflinePageModel::RemoveObserver(Observer
* observer
) {
100 observers_
.RemoveObserver(observer
);
103 void OfflinePageModel::SavePage(const GURL
& url
,
105 scoped_ptr
<OfflinePageArchiver
> archiver
,
106 const SavePageCallback
& callback
) {
108 DCHECK(archiver
.get());
109 archiver
->CreateArchive(base::Bind(&OfflinePageModel::OnCreateArchiveDone
,
110 weak_ptr_factory_
.GetWeakPtr(), url
,
111 bookmark_id
, callback
));
112 pending_archivers_
.push_back(archiver
.Pass());
115 void OfflinePageModel::DeletePageByBookmarkId(
117 const DeletePageCallback
& callback
) {
119 std::vector
<int64
> bookmark_ids_to_delete
;
120 bookmark_ids_to_delete
.push_back(bookmark_id
);
121 DeletePagesByBookmarkId(bookmark_ids_to_delete
, callback
);
124 void OfflinePageModel::DeletePagesByBookmarkId(
125 const std::vector
<int64
>& bookmark_ids
,
126 const DeletePageCallback
& callback
) {
129 std::vector
<base::FilePath
> paths_to_delete
;
130 for (const auto& bookmark_id
: bookmark_ids
) {
131 auto iter
= offline_pages_
.find(bookmark_id
);
132 if (iter
!= offline_pages_
.end()) {
133 paths_to_delete
.push_back(iter
->second
.file_path
);
137 if (paths_to_delete
.empty()) {
138 InformDeletePageDone(callback
, DeletePageResult::NOT_FOUND
);
142 bool* success
= new bool(false);
143 task_runner_
->PostTaskAndReply(
145 base::Bind(&DeleteArchiveFiles
, paths_to_delete
, success
),
146 base::Bind(&OfflinePageModel::OnDeleteArchiveFilesDone
,
147 weak_ptr_factory_
.GetWeakPtr(),
150 base::Owned(success
)));
153 const std::vector
<OfflinePageItem
> OfflinePageModel::GetAllPages() const {
155 std::vector
<OfflinePageItem
> offline_pages
;
156 for (const auto& id_page_pair
: offline_pages_
)
157 offline_pages
.push_back(id_page_pair
.second
);
158 return offline_pages
;
161 const std::vector
<OfflinePageItem
> OfflinePageModel::GetPagesToCleanUp() const {
163 std::vector
<OfflinePageItem
> offline_pages
;
164 base::Time now
= base::Time::Now();
165 for (const auto& id_page_pair
: offline_pages_
) {
166 if (now
- id_page_pair
.second
.creation_time
> kPageCleanUpThreshold
)
167 offline_pages
.push_back(id_page_pair
.second
);
169 return offline_pages
;
172 bool OfflinePageModel::GetPageByBookmarkId(
174 OfflinePageItem
* offline_page
) const {
175 DCHECK(offline_page
);
177 const auto iter
= offline_pages_
.find(bookmark_id
);
178 if (iter
!= offline_pages_
.end()) {
179 *offline_page
= iter
->second
;
186 const OfflinePageItem
* OfflinePageModel::GetPageByOfflineURL(
187 const GURL
& offline_url
) const {
188 for (auto iter
= offline_pages_
.begin();
189 iter
!= offline_pages_
.end();
191 if (iter
->second
.GetOfflineURL() == offline_url
)
192 return &(iter
->second
);
197 OfflinePageMetadataStore
* OfflinePageModel::GetStoreForTesting() {
201 void OfflinePageModel::OnCreateArchiveDone(const GURL
& requested_url
,
203 const SavePageCallback
& callback
,
204 OfflinePageArchiver
* archiver
,
205 ArchiverResult archiver_result
,
207 const base::FilePath
& file_path
,
209 if (requested_url
!= url
) {
210 DVLOG(1) << "Saved URL does not match requested URL.";
211 // TODO(fgorski): We have created an archive for a wrong URL. It should be
212 // deleted from here, once archiver has the right functionality.
213 InformSavePageDone(callback
, SavePageResult::ARCHIVE_CREATION_FAILED
);
214 DeletePendingArchiver(archiver
);
218 if (archiver_result
!= ArchiverResult::SUCCESSFULLY_CREATED
) {
219 SavePageResult result
= ToSavePageResult(archiver_result
);
220 InformSavePageDone(callback
, result
);
221 DeletePendingArchiver(archiver
);
225 OfflinePageItem
offline_page_item(url
, bookmark_id
, file_path
, file_size
,
227 store_
->AddOfflinePage(
229 base::Bind(&OfflinePageModel::OnAddOfflinePageDone
,
230 weak_ptr_factory_
.GetWeakPtr(), archiver
, callback
,
234 void OfflinePageModel::OnAddOfflinePageDone(OfflinePageArchiver
* archiver
,
235 const SavePageCallback
& callback
,
236 const OfflinePageItem
& offline_page
,
238 SavePageResult result
;
240 offline_pages_
[offline_page
.bookmark_id
] = offline_page
;
241 result
= SavePageResult::SUCCESS
;
242 UMA_HISTOGRAM_MEMORY_KB(
243 "OfflinePages.PageSize", offline_page
.file_size
/ 1024);
245 result
= SavePageResult::STORE_FAILURE
;
247 InformSavePageDone(callback
, result
);
248 DeletePendingArchiver(archiver
);
251 void OfflinePageModel::BookmarkModelChanged() {
254 void OfflinePageModel::BookmarkNodeRemoved(
255 bookmarks::BookmarkModel
* model
,
256 const bookmarks::BookmarkNode
* parent
,
258 const bookmarks::BookmarkNode
* node
,
259 const std::set
<GURL
>& removed_urls
) {
261 delayed_tasks_
.push_back(
262 base::Bind(&OfflinePageModel::DeletePageByBookmarkId
,
263 weak_ptr_factory_
.GetWeakPtr(),
265 base::Bind(&EmptyDeleteCallback
)));
268 DeletePageByBookmarkId(node
->id(), base::Bind(&EmptyDeleteCallback
));
271 void OfflinePageModel::OnLoadDone(
273 const std::vector
<OfflinePageItem
>& offline_pages
) {
278 for (const auto& offline_page
: offline_pages
)
279 offline_pages_
[offline_page
.bookmark_id
] = offline_page
;
282 // Run all the delayed tasks.
283 for (const auto& delayed_task
: delayed_tasks_
)
285 delayed_tasks_
.clear();
287 FOR_EACH_OBSERVER(Observer
, observers_
, OfflinePageModelLoaded(this));
290 void OfflinePageModel::InformSavePageDone(const SavePageCallback
& callback
,
291 SavePageResult result
) {
292 UMA_HISTOGRAM_ENUMERATION(
293 "OfflinePages.SavePageResult",
294 static_cast<int>(result
),
295 static_cast<int>(SavePageResult::RESULT_COUNT
));
296 callback
.Run(result
);
299 void OfflinePageModel::DeletePendingArchiver(OfflinePageArchiver
* archiver
) {
300 pending_archivers_
.erase(std::find(
301 pending_archivers_
.begin(), pending_archivers_
.end(), archiver
));
304 void OfflinePageModel::OnDeleteArchiveFilesDone(
305 const std::vector
<int64
>& bookmark_ids
,
306 const DeletePageCallback
& callback
,
307 const bool* success
) {
311 InformDeletePageDone(callback
, DeletePageResult::DEVICE_FAILURE
);
315 store_
->RemoveOfflinePages(
317 base::Bind(&OfflinePageModel::OnRemoveOfflinePagesDone
,
318 weak_ptr_factory_
.GetWeakPtr(), bookmark_ids
, callback
));
321 void OfflinePageModel::OnRemoveOfflinePagesDone(
322 const std::vector
<int64
>& bookmark_ids
,
323 const DeletePageCallback
& callback
,
325 // Delete the offline page from the in memory cache regardless of success in
327 base::Time now
= base::Time::Now();
328 for (int64 bookmark_id
: bookmark_ids
) {
329 auto iter
= offline_pages_
.find(bookmark_id
);
330 if (iter
== offline_pages_
.end())
332 UMA_HISTOGRAM_CUSTOM_COUNTS("OfflinePages.PageLifetime",
333 (now
- iter
->second
.creation_time
).InMinutes(),
335 base::TimeDelta::FromDays(365).InMinutes(),
337 offline_pages_
.erase(iter
);
339 // Deleting multiple pages always succeeds when it gets to this point.
340 InformDeletePageDone(
342 (success
|| bookmark_ids
.size() > 1) ? DeletePageResult::SUCCESS
343 : DeletePageResult::STORE_FAILURE
);
346 void OfflinePageModel::InformDeletePageDone(const DeletePageCallback
& callback
,
347 DeletePageResult result
) {
348 UMA_HISTOGRAM_ENUMERATION(
349 "OfflinePages.DeletePageResult",
350 static_cast<int>(result
),
351 static_cast<int>(DeletePageResult::RESULT_COUNT
));
352 callback
.Run(result
);
355 } // namespace offline_pages