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::WriteNotificationData(
123 const NotificationDatabaseData
& database_data
,
124 const WriteResultCallback
& callback
) {
125 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
127 base::Bind(&PlatformNotificationContextImpl::DoWriteNotificationData
,
128 this, origin
, database_data
, callback
),
129 base::Bind(callback
, false /* success */, 0 /* notification_id */));
132 void PlatformNotificationContextImpl::DoWriteNotificationData(
134 const NotificationDatabaseData
& database_data
,
135 const WriteResultCallback
& callback
) {
136 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
138 int64_t notification_id
= 0;
139 NotificationDatabase::Status status
=
140 database_
->WriteNotificationData(origin
,
144 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.WriteResult",
145 status
, NotificationDatabase::STATUS_COUNT
);
147 if (status
== NotificationDatabase::STATUS_OK
) {
148 DCHECK_GT(notification_id
, 0);
149 BrowserThread::PostTask(BrowserThread::IO
,
157 // Blow away the database if writing data failed due to corruption.
158 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
161 BrowserThread::PostTask(
164 base::Bind(callback
, false /* success */, 0 /* notification_id */));
167 void PlatformNotificationContextImpl::DeleteNotificationData(
168 int64_t notification_id
,
170 const DeleteResultCallback
& callback
) {
171 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
173 base::Bind(&PlatformNotificationContextImpl::DoDeleteNotificationData
,
174 this, notification_id
, origin
, callback
),
175 base::Bind(callback
, false /* success */));
178 void PlatformNotificationContextImpl::DoDeleteNotificationData(
179 int64_t notification_id
,
181 const DeleteResultCallback
& callback
) {
182 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
184 NotificationDatabase::Status status
=
185 database_
->DeleteNotificationData(notification_id
, origin
);
187 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DeleteResult",
188 status
, NotificationDatabase::STATUS_COUNT
);
190 bool success
= status
== NotificationDatabase::STATUS_OK
;
192 // Blow away the database if deleting data failed due to corruption. Following
193 // the contract of the delete methods, consider this to be a success as the
194 // caller's goal has been achieved: the data is gone.
195 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
) {
200 BrowserThread::PostTask(BrowserThread::IO
,
202 base::Bind(callback
, success
));
205 void PlatformNotificationContextImpl::OnRegistrationDeleted(
206 int64_t registration_id
,
207 const GURL
& pattern
) {
208 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
210 base::Bind(&PlatformNotificationContextImpl::
211 DoDeleteNotificationsForServiceWorkerRegistration
,
212 this, pattern
.GetOrigin(), registration_id
),
213 base::Bind(&DoNothing
));
216 void PlatformNotificationContextImpl::
217 DoDeleteNotificationsForServiceWorkerRegistration(
219 int64_t service_worker_registration_id
) {
220 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
222 std::set
<int64_t> deleted_notifications_set
;
223 NotificationDatabase::Status status
=
224 database_
->DeleteAllNotificationDataForServiceWorkerRegistration(
225 origin
, service_worker_registration_id
, &deleted_notifications_set
);
227 UMA_HISTOGRAM_ENUMERATION(
228 "Notifications.Database.DeleteServiceWorkerRegistrationResult",
229 status
, NotificationDatabase::STATUS_COUNT
);
231 // Blow away the database if a corruption error occurred during the deletion.
232 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
)
235 // TODO(peter): Close the notifications in |deleted_notifications_set|.
238 void PlatformNotificationContextImpl::OnStorageWiped() {
239 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
241 base::Bind(base::IgnoreResult(
242 &PlatformNotificationContextImpl::DestroyDatabase
), this),
243 base::Bind(&DoNothing
));
246 void PlatformNotificationContextImpl::LazyInitialize(
247 const base::Closure
& success_closure
,
248 const base::Closure
& failure_closure
) {
249 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
252 base::SequencedWorkerPool
* pool
= BrowserThread::GetBlockingPool();
253 base::SequencedWorkerPool::SequenceToken token
= pool
->GetSequenceToken();
255 task_runner_
= pool
->GetSequencedTaskRunner(token
);
258 task_runner_
->PostTask(
260 base::Bind(&PlatformNotificationContextImpl::OpenDatabase
,
261 this, success_closure
, failure_closure
));
264 void PlatformNotificationContextImpl::OpenDatabase(
265 const base::Closure
& success_closure
,
266 const base::Closure
& failure_closure
) {
267 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
270 success_closure
.Run();
274 database_
.reset(new NotificationDatabase(GetDatabasePath()));
275 NotificationDatabase::Status status
=
276 database_
->Open(true /* create_if_missing */);
278 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.OpenResult",
279 status
, NotificationDatabase::STATUS_COUNT
);
281 // When the database could not be opened due to corruption, destroy it, blow
282 // away the contents of the directory and try re-opening the database.
283 if (status
== NotificationDatabase::STATUS_ERROR_CORRUPTED
) {
284 if (DestroyDatabase()) {
285 status
= database_
->Open(true /* create_if_missing */);
287 // TODO(peter): Record UMA on |status| for re-opening the database after
288 // corruption was detected.
292 if (status
== NotificationDatabase::STATUS_OK
) {
293 success_closure
.Run();
299 BrowserThread::PostTask(BrowserThread::IO
, FROM_HERE
, failure_closure
);
302 bool PlatformNotificationContextImpl::DestroyDatabase() {
303 DCHECK(task_runner_
->RunsTasksOnCurrentThread());
306 NotificationDatabase::Status status
= database_
->Destroy();
307 UMA_HISTOGRAM_ENUMERATION("Notifications.Database.DestroyResult",
308 status
, NotificationDatabase::STATUS_COUNT
);
312 // TODO(peter): Close any existing persistent notifications on the platform.
314 // Remove all files in the directory that the database was previously located
315 // in, to make sure that any left-over files are gone as well.
316 base::FilePath database_path
= GetDatabasePath();
317 if (!database_path
.empty())
318 return base::DeleteFile(database_path
, true);
323 base::FilePath
PlatformNotificationContextImpl::GetDatabasePath() const {
327 return path_
.Append(kPlatformNotificationsDirectory
);
330 void PlatformNotificationContextImpl::SetTaskRunnerForTesting(
331 const scoped_refptr
<base::SequencedTaskRunner
>& task_runner
) {
332 task_runner_
= task_runner
;
335 } // namespace content