Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(487)

Side by Side Diff: chrome/browser/extensions/app_notification_manager.cc

Issue 12680004: Remove chrome/ code to handle App Notifications (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix merge conflicts. Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "chrome/browser/extensions/app_notification_manager.h"
6
7 #include "base/auto_reset.h"
8 #include "base/bind.h"
9 #include "base/files/file_path.h"
10 #include "base/location.h"
11 #include "base/metrics/histogram.h"
12 #include "base/perftimer.h"
13 #include "base/stl_util.h"
14 #include "base/time.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/common/chrome_notification_types.h"
17 #include "chrome/common/extensions/extension.h"
18 #include "content/public/browser/notification_service.h"
19 #include "sync/api/sync_error_factory.h"
20 #include "sync/protocol/app_notification_specifics.pb.h"
21 #include "sync/protocol/sync.pb.h"
22
23 using content::BrowserThread;
24
25 typedef std::map<std::string, syncer::SyncData> SyncDataMap;
26
27 namespace extensions {
28
29 namespace {
30
31 class GuidComparator
32 : public std::binary_function<linked_ptr<AppNotification>,
33 std::string,
34 bool> {
35 public:
36 bool operator() (linked_ptr<AppNotification> notif,
37 const std::string& guid) const {
38 return notif->guid() == guid;
39 }
40 };
41
42 const AppNotification* FindByGuid(const AppNotificationList& list,
43 const std::string& guid) {
44 AppNotificationList::const_iterator iter = std::find_if(
45 list.begin(), list.end(), std::bind2nd(GuidComparator(), guid));
46 return iter == list.end() ? NULL : iter->get();
47 }
48
49 void RemoveByGuid(AppNotificationList* list, const std::string& guid) {
50 if (!list)
51 return;
52
53 AppNotificationList::iterator iter = std::find_if(
54 list->begin(), list->end(), std::bind2nd(GuidComparator(), guid));
55 if (iter != list->end())
56 list->erase(iter);
57 }
58
59 void PopulateGuidToSyncDataMap(const syncer::SyncDataList& sync_data,
60 SyncDataMap* data_map) {
61 for (syncer::SyncDataList::const_iterator iter = sync_data.begin();
62 iter != sync_data.end(); ++iter) {
63 (*data_map)[iter->GetSpecifics().app_notification().guid()] = *iter;
64 }
65 }
66 } // namespace
67
68 const unsigned int AppNotificationManager::kMaxNotificationPerApp = 5;
69
70 AppNotificationManager::AppNotificationManager(Profile* profile)
71 : profile_(profile),
72 models_associated_(false),
73 processing_syncer_changes_(false) {
74 registrar_.Add(this,
75 chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
76 content::Source<Profile>(profile_));
77 }
78
79 void AppNotificationManager::Init() {
80 base::FilePath storage_path =
81 profile_->GetPath().AppendASCII("App Notifications");
82 load_timer_.reset(new PerfTimer());
83 BrowserThread::PostTask(
84 BrowserThread::FILE,
85 FROM_HERE,
86 base::Bind(&AppNotificationManager::LoadOnFileThread,
87 this, storage_path));
88 }
89
90 bool AppNotificationSortPredicate(const linked_ptr<AppNotification> a1,
91 const linked_ptr<AppNotification> a2) {
92 return a1.get()->creation_time() < a2.get()->creation_time();
93 }
94
95 bool AppNotificationManager::Add(AppNotification* item) {
96 // Do this first since we own the incoming item and hence want to delete
97 // it in error paths.
98 linked_ptr<AppNotification> linked_item(item);
99 if (!loaded())
100 return false;
101 const std::string& extension_id = item->extension_id();
102 AppNotificationList& list = GetAllInternal(extension_id);
103 list.push_back(linked_item);
104
105 SyncAddChange(*linked_item);
106
107 std::sort(list.begin(), list.end(), AppNotificationSortPredicate);
108
109 if (list.size() > AppNotificationManager::kMaxNotificationPerApp) {
110 AppNotification* removed = list.begin()->get();
111 SyncRemoveChange(*removed);
112 list.erase(list.begin());
113 }
114
115 if (storage_.get()) {
116 BrowserThread::PostTask(
117 BrowserThread::FILE,
118 FROM_HERE,
119 base::Bind(&AppNotificationManager::SaveOnFileThread,
120 this, extension_id, CopyAppNotificationList(list)));
121 }
122
123 content::NotificationService::current()->Notify(
124 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
125 content::Source<Profile>(profile_),
126 content::Details<const std::string>(&extension_id));
127
128 return true;
129 }
130
131 const AppNotificationList* AppNotificationManager::GetAll(
132 const std::string& extension_id) const {
133 if (!loaded())
134 return NULL;
135 if (ContainsKey(*notifications_, extension_id))
136 return &((*notifications_)[extension_id]);
137 return NULL;
138 }
139
140 const AppNotification* AppNotificationManager::GetLast(
141 const std::string& extension_id) {
142 if (!loaded())
143 return NULL;
144 NotificationMap::iterator found = notifications_->find(extension_id);
145 if (found == notifications_->end())
146 return NULL;
147 const AppNotificationList& list = found->second;
148 if (list.empty())
149 return NULL;
150 return list.rbegin()->get();
151 }
152
153 void AppNotificationManager::ClearAll(const std::string& extension_id) {
154 if (!loaded())
155 return;
156 NotificationMap::iterator found = notifications_->find(extension_id);
157 if (found != notifications_->end()) {
158 SyncClearAllChange(found->second);
159 notifications_->erase(found);
160 }
161
162 if (storage_.get()) {
163 BrowserThread::PostTask(
164 BrowserThread::FILE,
165 FROM_HERE,
166 base::Bind(&AppNotificationManager::DeleteOnFileThread,
167 this, extension_id));
168 }
169
170 content::NotificationService::current()->Notify(
171 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
172 content::Source<Profile>(profile_),
173 content::Details<const std::string>(&extension_id));
174 }
175
176 void AppNotificationManager::Observe(
177 int type,
178 const content::NotificationSource& source,
179 const content::NotificationDetails& details) {
180 CHECK(type == chrome::NOTIFICATION_EXTENSION_UNINSTALLED);
181 ClearAll(content::Details<const Extension>(details).ptr()->id());
182 }
183
184 syncer::SyncDataList AppNotificationManager::GetAllSyncData(
185 syncer::ModelType type) const {
186 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
187 DCHECK(loaded());
188 DCHECK_EQ(syncer::APP_NOTIFICATIONS, type);
189 syncer::SyncDataList data;
190 for (NotificationMap::const_iterator iter = notifications_->begin();
191 iter != notifications_->end(); ++iter) {
192
193 // Skip local notifications since they should not be synced.
194 const AppNotificationList list = (*iter).second;
195 for (AppNotificationList::const_iterator list_iter = list.begin();
196 list_iter != list.end(); ++list_iter) {
197 const AppNotification* notification = (*list_iter).get();
198 if (notification->is_local()) {
199 continue;
200 }
201 data.push_back(CreateSyncDataFromNotification(*notification));
202 }
203 }
204
205 return data;
206 }
207
208 syncer::SyncError AppNotificationManager::ProcessSyncChanges(
209 const tracked_objects::Location& from_here,
210 const syncer::SyncChangeList& change_list) {
211 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
212 DCHECK(loaded());
213 if (!models_associated_) {
214 return sync_error_factory_->CreateAndUploadError(
215 FROM_HERE,
216 "Models not yet associated.");
217 }
218
219 base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
220
221 syncer::SyncError error;
222 for (syncer::SyncChangeList::const_iterator iter = change_list.begin();
223 iter != change_list.end(); ++iter) {
224 syncer::SyncData sync_data = iter->sync_data();
225 DCHECK_EQ(syncer::APP_NOTIFICATIONS, sync_data.GetDataType());
226 syncer::SyncChange::SyncChangeType change_type = iter->change_type();
227
228 scoped_ptr<AppNotification> new_notif(CreateNotificationFromSyncData(
229 sync_data));
230 if (!new_notif.get()) {
231 NOTREACHED() << "Failed to read notification.";
232 continue;
233 }
234 const AppNotification* existing_notif = GetNotification(
235 new_notif->extension_id(), new_notif->guid());
236 if (existing_notif && existing_notif->is_local()) {
237 NOTREACHED() << "Matched with notification marked as local";
238 error = sync_error_factory_->CreateAndUploadError(
239 FROM_HERE,
240 "ProcessSyncChanges received a local only notification" +
241 syncer::SyncChange::ChangeTypeToString(change_type));
242 continue;
243 }
244
245 switch (change_type) {
246 case syncer::SyncChange::ACTION_ADD:
247 if (!existing_notif) {
248 Add(new_notif.release());
249 } else {
250 DLOG(ERROR) << "Got ADD change for an existing item.\n"
251 << "Existing item: " << existing_notif->ToString()
252 << "\nItem in ADD change: " << new_notif->ToString();
253 }
254 break;
255 case syncer::SyncChange::ACTION_DELETE:
256 if (existing_notif) {
257 Remove(new_notif->extension_id(), new_notif->guid());
258 } else {
259 // This should never happen. But we are seeting this sometimes, and
260 // it stops all of sync. See bug http://crbug.com/108088
261 // So until we figure out the root cause, log an error and ignore.
262 DLOG(ERROR) << "Got DELETE change for non-existing item.\n"
263 << "Item in DELETE change: " << new_notif->ToString();
264 }
265 break;
266 case syncer::SyncChange::ACTION_UPDATE:
267 // Although app notifications are immutable from the model perspective,
268 // sync can send UPDATE changes due to encryption / meta-data changes.
269 // So ignore UPDATE changes when the exitsing and new notification
270 // objects are the same. Log an error otherwise.
271 if (!existing_notif) {
272 DLOG(ERROR) << "Got UPDATE change for non-existing item."
273 << "Item in UPDATE change: " << new_notif->ToString();
274 } else if (!existing_notif->Equals(*new_notif)) {
275 DLOG(ERROR) << "Got invalid UPDATE change:"
276 << "New and existing notifications should be the same.\n"
277 << "Existing item: " << existing_notif->ToString() << "\n"
278 << "Item in UPDATE change: " << new_notif->ToString();
279 }
280 break;
281 default:
282 break;
283 }
284 }
285
286 return error;
287 }
288
289 syncer::SyncMergeResult AppNotificationManager::MergeDataAndStartSyncing(
290 syncer::ModelType type,
291 const syncer::SyncDataList& initial_sync_data,
292 scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
293 scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) {
294 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
295 syncer::SyncMergeResult merge_result(type);
296 // AppNotificationDataTypeController ensures that modei is fully should before
297 // this method is called by waiting until the load notification is received
298 // from AppNotificationManager.
299 DCHECK(loaded());
300 DCHECK_EQ(type, syncer::APP_NOTIFICATIONS);
301 DCHECK(!sync_processor_.get());
302 DCHECK(sync_processor.get());
303 DCHECK(sync_error_factory.get());
304 sync_processor_ = sync_processor.Pass();
305 sync_error_factory_ = sync_error_factory.Pass();
306
307 // We may add, or remove notifications here, so ensure we don't step on
308 // our own toes.
309 base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
310
311 SyncDataMap local_data_map;
312 PopulateGuidToSyncDataMap(GetAllSyncData(syncer::APP_NOTIFICATIONS),
313 &local_data_map);
314
315 for (syncer::SyncDataList::const_iterator iter = initial_sync_data.begin();
316 iter != initial_sync_data.end(); ++iter) {
317 const syncer::SyncData& sync_data = *iter;
318 DCHECK_EQ(syncer::APP_NOTIFICATIONS, sync_data.GetDataType());
319 scoped_ptr<AppNotification> sync_notif(CreateNotificationFromSyncData(
320 sync_data));
321 CHECK(sync_notif.get());
322 const AppNotification* local_notif = GetNotification(
323 sync_notif->extension_id(), sync_notif->guid());
324 if (local_notif) {
325 local_data_map.erase(sync_notif->guid());
326 // Local notification should always match with sync notification as
327 // notifications are immutable.
328 if (local_notif->is_local() || !sync_notif->Equals(*local_notif)) {
329 merge_result.set_error(sync_error_factory_->CreateAndUploadError(
330 FROM_HERE,
331 "MergeDataAndStartSyncing failed: local notification and sync "
332 "notification have same guid but different data."));
333 return merge_result;
334 }
335 } else {
336 // Sync model has a notification that local model does not, add it.
337 Add(sync_notif.release());
338 }
339 }
340
341 // TODO(munjal): crbug.com/10059. Work with Lingesh/Antony to resolve.
342 syncer::SyncChangeList new_changes;
343 for (SyncDataMap::const_iterator iter = local_data_map.begin();
344 iter != local_data_map.end(); ++iter) {
345 new_changes.push_back(
346 syncer::SyncChange(FROM_HERE,
347 syncer::SyncChange::ACTION_ADD,
348 iter->second));
349 }
350
351 if (new_changes.size() > 0) {
352 merge_result.set_error(
353 sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
354 }
355 models_associated_ = !merge_result.error().IsSet();
356 return merge_result;
357 }
358
359 void AppNotificationManager::StopSyncing(syncer::ModelType type) {
360 DCHECK_EQ(type, syncer::APP_NOTIFICATIONS);
361 models_associated_ = false;
362 sync_processor_.reset();
363 sync_error_factory_.reset();
364 }
365
366 AppNotificationManager::~AppNotificationManager() {
367 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
368 // Post a task to delete our storage on the file thread.
369 BrowserThread::DeleteSoon(BrowserThread::FILE,
370 FROM_HERE,
371 storage_.release());
372 }
373
374 void AppNotificationManager::LoadOnFileThread(
375 const base::FilePath& storage_path) {
376 PerfTimer timer;
377 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
378 DCHECK(!loaded());
379
380 storage_.reset(AppNotificationStorage::Create(storage_path));
381 if (!storage_.get())
382 return;
383 scoped_ptr<NotificationMap> result(new NotificationMap());
384 std::set<std::string> ids;
385 if (!storage_->GetExtensionIds(&ids))
386 return;
387 std::set<std::string>::const_iterator i;
388 for (i = ids.begin(); i != ids.end(); ++i) {
389 const std::string& id = *i;
390 AppNotificationList& list = (*result)[id];
391 if (!storage_->Get(id, &list))
392 result->erase(id);
393 }
394
395 BrowserThread::PostTask(
396 BrowserThread::UI,
397 FROM_HERE,
398 base::Bind(&AppNotificationManager::HandleLoadResults,
399 this, result.release()));
400
401 UMA_HISTOGRAM_LONG_TIMES("AppNotification.MgrFileThreadLoadTime",
402 timer.Elapsed());
403 }
404
405 void AppNotificationManager::HandleLoadResults(NotificationMap* map) {
406 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
407 DCHECK(map);
408 DCHECK(!loaded());
409 notifications_.reset(map);
410 UMA_HISTOGRAM_LONG_TIMES("AppNotification.MgrLoadDelay",
411 load_timer_->Elapsed());
412 load_timer_.reset();
413
414 // Generate STATE_CHANGED notifications for extensions that have at
415 // least one notification loaded.
416 int app_count = 0;
417 int notification_count = 0;
418 NotificationMap::const_iterator i;
419 for (i = map->begin(); i != map->end(); ++i) {
420 const std::string& id = i->first;
421 if (i->second.empty())
422 continue;
423 app_count++;
424 notification_count += i->second.size();
425 content::NotificationService::current()->Notify(
426 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
427 content::Source<Profile>(profile_),
428 content::Details<const std::string>(&id));
429 }
430 UMA_HISTOGRAM_COUNTS("AppNotification.MgrLoadAppCount", app_count);
431 UMA_HISTOGRAM_COUNTS("AppNotification.MgrLoadTotalCount",
432 notification_count);
433
434 // Generate MANAGER_LOADED notification.
435 content::NotificationService::current()->Notify(
436 chrome::NOTIFICATION_APP_NOTIFICATION_MANAGER_LOADED,
437 content::Source<AppNotificationManager>(this),
438 content::NotificationService::NoDetails());
439 }
440
441 void AppNotificationManager::SaveOnFileThread(const std::string& extension_id,
442 AppNotificationList* list) {
443 // Own the |list|.
444 scoped_ptr<AppNotificationList> scoped_list(list);
445 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
446 storage_->Set(extension_id, *scoped_list);
447 }
448
449 void AppNotificationManager::DeleteOnFileThread(
450 const std::string& extension_id) {
451 CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
452 storage_->Delete(extension_id);
453 }
454
455 AppNotificationList& AppNotificationManager::GetAllInternal(
456 const std::string& extension_id) {
457 NotificationMap::iterator found = notifications_->find(extension_id);
458 if (found == notifications_->end()) {
459 (*notifications_)[extension_id] = AppNotificationList();
460 found = notifications_->find(extension_id);
461 }
462 CHECK(found != notifications_->end());
463 return found->second;
464 }
465
466 void AppNotificationManager::Remove(const std::string& extension_id,
467 const std::string& guid) {
468 DCHECK(loaded());
469 AppNotificationList& list = GetAllInternal(extension_id);
470 RemoveByGuid(&list, guid);
471
472 if (storage_.get()) {
473 BrowserThread::PostTask(
474 BrowserThread::FILE,
475 FROM_HERE,
476 base::Bind(&AppNotificationManager::SaveOnFileThread,
477 this, extension_id, CopyAppNotificationList(list)));
478 }
479
480 content::NotificationService::current()->Notify(
481 chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
482 content::Source<Profile>(profile_),
483 content::Details<const std::string>(&extension_id));
484 }
485
486 const AppNotification* AppNotificationManager::GetNotification(
487 const std::string& extension_id, const std::string& guid) {
488 DCHECK(loaded());
489 const AppNotificationList& list = GetAllInternal(extension_id);
490 return FindByGuid(list, guid);
491 }
492
493 void AppNotificationManager::SyncAddChange(const AppNotification& notif) {
494 // Skip if either:
495 // - Notification is marked as local.
496 // - Sync is not enabled by user.
497 // - Change is generated from within the manager.
498 if (notif.is_local() || !models_associated_ || processing_syncer_changes_)
499 return;
500
501 // TODO(munjal): crbug.com/10059. Work with Lingesh/Antony to resolve.
502
503 syncer::SyncChangeList changes;
504 syncer::SyncData sync_data = CreateSyncDataFromNotification(notif);
505 changes.push_back(
506 syncer::SyncChange(FROM_HERE,
507 syncer::SyncChange::ACTION_ADD,
508 sync_data));
509 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
510 }
511
512 void AppNotificationManager::SyncRemoveChange(const AppNotification& notif) {
513 // Skip if either:
514 // - Sync is not enabled by user.
515 // - Change is generated from within the manager.
516 if (notif.is_local() || !models_associated_) {
517 return;
518 }
519
520 syncer::SyncChangeList changes;
521 syncer::SyncData sync_data = CreateSyncDataFromNotification(notif);
522 changes.push_back(
523 syncer::SyncChange(FROM_HERE,
524 syncer::SyncChange::ACTION_DELETE,
525 sync_data));
526 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
527 }
528
529 void AppNotificationManager::SyncClearAllChange(
530 const AppNotificationList& list) {
531 // Skip if either:
532 // - Sync is not enabled by user.
533 // - Change is generated from within the manager.
534 if (!models_associated_ || processing_syncer_changes_)
535 return;
536
537 syncer::SyncChangeList changes;
538 for (AppNotificationList::const_iterator iter = list.begin();
539 iter != list.end(); ++iter) {
540 const AppNotification& notif = *iter->get();
541 // Skip notifications marked as local.
542 if (notif.is_local())
543 continue;
544 changes.push_back(syncer::SyncChange(
545 FROM_HERE,
546 syncer::SyncChange::ACTION_DELETE,
547 CreateSyncDataFromNotification(notif)));
548 }
549 sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
550 }
551
552 // static
553 syncer::SyncData AppNotificationManager::CreateSyncDataFromNotification(
554 const AppNotification& notification) {
555 DCHECK(!notification.is_local());
556 sync_pb::EntitySpecifics specifics;
557 sync_pb::AppNotification* notif_specifics =
558 specifics.mutable_app_notification();
559 notif_specifics->set_app_id(notification.extension_id());
560 notif_specifics->set_creation_timestamp_ms(
561 notification.creation_time().ToInternalValue());
562 notif_specifics->set_body_text(notification.body());
563 notif_specifics->set_guid(notification.guid());
564 notif_specifics->set_link_text(notification.link_text());
565 notif_specifics->set_link_url(notification.link_url().spec());
566 notif_specifics->set_title(notification.title());
567 return syncer::SyncData::CreateLocalData(
568 notif_specifics->guid(), notif_specifics->app_id(), specifics);
569 }
570
571 // static
572 AppNotification* AppNotificationManager::CreateNotificationFromSyncData(
573 const syncer::SyncData& sync_data) {
574 sync_pb::AppNotification specifics =
575 sync_data.GetSpecifics().app_notification();
576
577 // Check for mandatory fields.
578 if (!specifics.has_app_id() || !specifics.has_guid() ||
579 !specifics.has_title() || !specifics.has_body_text() ||
580 !specifics.has_creation_timestamp_ms()) {
581 return NULL;
582 }
583
584 AppNotification* notification = new AppNotification(
585 false, base::Time::FromInternalValue(specifics.creation_timestamp_ms()),
586 specifics.guid(), specifics.app_id(),
587 specifics.title(), specifics.body_text());
588 if (specifics.has_link_text())
589 notification->set_link_text(specifics.link_text());
590 if (specifics.has_link_url())
591 notification->set_link_url(GURL(specifics.link_url()));
592 return notification;
593 }
594
595 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698