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 "content/browser/notifications/platform_notification_context_impl.h"
7 #include "base/bind_helpers.h"
8 #include "base/files/file_util.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/threading/sequenced_worker_pool.h"
11 #include "content/browser/notifications/notification_database.h"
12 #include "content/browser/service_worker/service_worker_context_wrapper.h"
13 #include "content/public/browser/browser_thread.h"
14 #include "content/public/browser/notification_database_data.h"
16 using base::DoNothing
;
20 // Name of the directory in the user's profile directory where the notification
21 // database files should be stored.
22 const base::FilePath::CharType kPlatformNotificationsDirectory
[] =
23 FILE_PATH_LITERAL("Platform Notifications");
25 PlatformNotificationContextImpl::PlatformNotificationContextImpl(
26 const base::FilePath
& path
,
27 const scoped_refptr
<ServiceWorkerContextWrapper
>& service_worker_context
)
29 service_worker_context_(service_worker_context
) {
30 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
33 PlatformNotificationContextImpl::~PlatformNotificationContextImpl() {
34 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
36 // If the database has been initialized, it must be deleted on the task runner
37 // thread as closing it may cause file I/O.
40 task_runner_
->DeleteSoon(FROM_HERE
, database_
.release());
44 void PlatformNotificationContextImpl::Initialize() {
45 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
46 BrowserThread::PostTask(
49 base::Bind(&PlatformNotificationContextImpl::InitializeOnIO
, this));
52 void PlatformNotificationContextImpl::InitializeOnIO() {
53 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
55 // |service_worker_context_| may be NULL in tests.
56 if (service_worker_context_
)
57 service_worker_context_
->AddObserver(this);
60 void PlatformNotificationContextImpl::Shutdown() {
61 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
62 BrowserThread::PostTask(
65 base::Bind(&PlatformNotificationContextImpl::ShutdownOnIO
, this));
68 void PlatformNotificationContextImpl::ShutdownOnIO() {
69 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
71 // |service_worker_context_| may be NULL in tests.
72 if (service_worker_context_
)
73 service_worker_context_
->RemoveObserver(this);
76 void PlatformNotificationContextImpl::ReadNotificationData(
77 int64_t notification_id
,
79 const ReadResultCallback
& callback
) {
80 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
82 base::Bind(&PlatformNotificationContextImpl::DoReadNotificationData
,
83 this, notification_id
, origin
, callback
),
84 base::Bind(callback
, false /* success */, NotificationDatabaseData()));
87 void PlatformNotificationContextImpl::DoReadNotificationData(
88 int64_t notification_id
,
90 const ReadResultCallback
& callback
) {
91 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
93 NotificationDatabaseData database_data
;
94 NotificationDatabase::Status status
=
95 database_
->ReadNotificationData(notification_id
,
99 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadResult",
100 status
, NotificationDatabase::STATUS_COUNT
);
102 if (status
== NotificationDatabase::STATUS_OK
) {
103 BrowserThread::PostTask(BrowserThread::IO
,
111 // Blow away the database if reading data failed due to corruption.
112 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
115 BrowserThread::PostTask(
118 base::Bind(callback
, false /* success */, NotificationDatabaseData()));
121 void PlatformNotificationContextImpl::
122 ReadAllNotificationDataForServiceWorkerRegistration(
124 int64_t service_worker_registration_id
,
125 const ReadAllResultCallback
& callback
) {
126 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
128 base::Bind(&PlatformNotificationContextImpl::
129 DoReadAllNotificationDataForServiceWorkerRegistration
,
130 this, origin
, service_worker_registration_id
, callback
),
133 std::vector
<NotificationDatabaseData
>()));
136 void PlatformNotificationContextImpl::
137 DoReadAllNotificationDataForServiceWorkerRegistration(
139 int64_t service_worker_registration_id
,
140 const ReadAllResultCallback
& callback
) {
141 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
143 std::vector
<NotificationDatabaseData
> notification_datas
;
145 NotificationDatabase::Status status
=
146 database_
->ReadAllNotificationDataForServiceWorkerRegistration(
147 origin
, service_worker_registration_id
, ¬ification_datas
);
149 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.ReadForServiceWorkerResult",
150 status
, NotificationDatabase::STATUS_COUNT
);
152 if (status
== NotificationDatabase::STATUS_OK
) {
153 BrowserThread::PostTask(BrowserThread::IO
,
157 notification_datas
));
161 // Blow away the database if reading data failed due to corruption.
162 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
165 BrowserThread::PostTask(
170 std::vector
<NotificationDatabaseData
>()));
173 void PlatformNotificationContextImpl::WriteNotificationData(
175 const NotificationDatabaseData
& database_data
,
176 const WriteResultCallback
& callback
) {
177 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
179 base::Bind(&PlatformNotificationContextImpl::DoWriteNotificationData
,
180 this, origin
, database_data
, callback
),
181 base::Bind(callback
, false /* success */, 0 /* notification_id */));
184 void PlatformNotificationContextImpl::DoWriteNotificationData(
186 const NotificationDatabaseData
& database_data
,
187 const WriteResultCallback
& callback
) {
188 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
190 int64_t notification_id
= 0;
191 NotificationDatabase::Status status
=
192 database_
->WriteNotificationData(origin
,
196 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.WriteResult",
197 status
, NotificationDatabase::STATUS_COUNT
);
199 if (status
== NotificationDatabase::STATUS_OK
) {
200 DCHECK_GT(notification_id
, 0);
201 BrowserThread::PostTask(BrowserThread::IO
,
209 // Blow away the database if writing data failed due to corruption.
210 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
213 BrowserThread::PostTask(
216 base::Bind(callback
, false /* success */, 0 /* notification_id */));
219 void PlatformNotificationContextImpl::DeleteNotificationData(
220 int64_t notification_id
,
222 const DeleteResultCallback
& callback
) {
223 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
225 base::Bind(&PlatformNotificationContextImpl::DoDeleteNotificationData
,
226 this, notification_id
, origin
, callback
),
227 base::Bind(callback
, false /* success */));
230 void PlatformNotificationContextImpl::DoDeleteNotificationData(
231 int64_t notification_id
,
233 const DeleteResultCallback
& callback
) {
234 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
236 NotificationDatabase::Status status
=
237 database_
->DeleteNotificationData(notification_id
, origin
);
239 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DeleteResult",
240 status
, NotificationDatabase::STATUS_COUNT
);
242 bool success
= status
== NotificationDatabase::STATUS_OK
;
244 // Blow away the database if deleting data failed due to corruption. Following
245 // the contract of the delete methods, consider this to be a success as the
246 // caller's goal has been achieved: the data is gone.
247 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
) {
252 BrowserThread::PostTask(BrowserThread::IO
,
254 base::Bind(callback
, success
));
257 void PlatformNotificationContextImpl::OnRegistrationDeleted(
258 int64_t registration_id
,
259 const GURL
& pattern
) {
260 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
262 base::Bind(&PlatformNotificationContextImpl::
263 DoDeleteNotificationsForServiceWorkerRegistration
,
264 this, pattern
.GetOrigin(), registration_id
),
265 base::Bind(&DoNothing
));
268 void PlatformNotificationContextImpl::
269 DoDeleteNotificationsForServiceWorkerRegistration(
271 int64_t service_worker_registration_id
) {
272 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
274 std::set
<int64_t> deleted_notifications_set
;
275 NotificationDatabase::Status status
=
276 database_
->DeleteAllNotificationDataForServiceWorkerRegistration(
277 origin
, service_worker_registration_id
, &deleted_notifications_set
);
279 UMA_HISTOGRAM_ENUMERATION(
280 "Notifications.Database.DeleteServiceWorkerRegistrationResult",
281 status
, NotificationDatabase::STATUS_COUNT
);
283 // Blow away the database if a corruption error occurred during the deletion.
284 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
287 // TODO(peter): Close the notifications in |deleted_notifications_set|.
290 void PlatformNotificationContextImpl::OnStorageWiped() {
291 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
293 base::Bind(base::IgnoreResult(
294 &PlatformNotificationContextImpl::DestroyDatabase
), this),
295 base::Bind(&DoNothing
));
298 void PlatformNotificationContextImpl::LazyInitialize(
299 const base::Closure
& success_closure
,
300 const base::Closure
& failure_closure
) {
301 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
304 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
305 base::SequencedWorkerPool::SequenceToken token
= pool
->GetSequenceToken();
307 task_runner_
= pool
->GetSequencedTaskRunner(token
);
310 task_runner_
->PostTask(
312 base::Bind(&PlatformNotificationContextImpl::OpenDatabase
,
313 this, success_closure
, failure_closure
));
316 void PlatformNotificationContextImpl::OpenDatabase(
317 const base::Closure
& success_closure
,
318 const base::Closure
& failure_closure
) {
319 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
322 success_closure
.Run();
326 database_
.reset(new NotificationDatabase(GetDatabasePath()));
327 NotificationDatabase::Status status
=
328 database_
->Open(true /* create_if_missing */);
330 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.OpenResult",
331 status
, NotificationDatabase::STATUS_COUNT
);
333 // When the database could not be opened due to corruption, destroy it, blow
334 // away the contents of the directory and try re-opening the database.
335 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
) {
336 if (DestroyDatabase()) {
337 status
= database_
->Open(true /* create_if_missing */);
339 UMA_HISTOGRAM_ENUMERATION(
340 "Notifications.Database.OpenAfterCorruptionResult",
341 status
, NotificationDatabase::STATUS_COUNT
);
345 if (status
== NotificationDatabase::STATUS_OK
) {
346 success_closure
.Run();
352 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
, failure_closure
);
355 bool PlatformNotificationContextImpl::DestroyDatabase() {
356 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
359 NotificationDatabase::Status status
= database_
->Destroy();
360 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DestroyResult",
361 status
, NotificationDatabase::STATUS_COUNT
);
365 // TODO(peter): Close any existing persistent notifications on the platform.
367 // Remove all files in the directory that the database was previously located
368 // in, to make sure that any left-over files are gone as well.
369 base::FilePath database_path
= GetDatabasePath();
370 if (!database_path
.empty())
371 return base::DeleteFile(database_path
, true);
376 base::FilePath
PlatformNotificationContextImpl::GetDatabasePath() const {
380 return path_
.Append(kPlatformNotificationsDirectory
);
383 void PlatformNotificationContextImpl::SetTaskRunnerForTesting(
384 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
) {
385 task_runner_
= task_runner
;
388 } // namespace content