summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormunjal@chromium.org <munjal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-18 20:11:42 +0000
committermunjal@chromium.org <munjal@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-10-18 20:11:42 +0000
commit851881b6addae55ecb2f2c02383962f2ca1baab4 (patch)
tree117bc357c78906fdff5fb81358a08305bf92f4fa
parent8866f623dfd914c9f4c26ab96f90aecf4a372ef9 (diff)
downloadchromium_src-851881b6addae55ecb2f2c02383962f2ca1baab4.zip
chromium_src-851881b6addae55ecb2f2c02383962f2ca1baab4.tar.gz
chromium_src-851881b6addae55ecb2f2c02383962f2ca1baab4.tar.bz2
Implement SyncableService in AppNotificationsManager:
- Implement all methods of SyncableService - Modify existing methods that change the model (Add and ClearAll) to push changes to sync. - Add some extra properties to AppNotification: guid and extension id - Disallow operations on model until storage is loaded. - Ton of unit tests for sync methods There is anotehr part for the full notifications sync to work that will be done in a separate CL. Review URL: http://codereview.chromium.org/8263002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@106110 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/app_notification.cc46
-rw-r--r--chrome/browser/extensions/app_notification.h18
-rw-r--r--chrome/browser/extensions/app_notification_manager.cc422
-rw-r--r--chrome/browser/extensions/app_notification_manager.h97
-rw-r--r--chrome/browser/extensions/app_notification_manager_sync_unittest.cc588
-rw-r--r--chrome/browser/extensions/app_notification_manager_unittest.cc18
-rw-r--r--chrome/browser/extensions/app_notification_storage_unittest.cc10
-rw-r--r--chrome/browser/extensions/app_notification_test_util.cc18
-rw-r--r--chrome/browser/extensions/app_notification_test_util.h9
-rw-r--r--chrome/browser/extensions/extension_app_api.cc5
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/chrome_notification_types.h4
12 files changed, 1142 insertions, 94 deletions
diff --git a/chrome/browser/extensions/app_notification.cc b/chrome/browser/extensions/app_notification.cc
index f8e60e3..2dbae2b 100644
--- a/chrome/browser/extensions/app_notification.cc
+++ b/chrome/browser/extensions/app_notification.cc
@@ -5,15 +5,13 @@
#include "chrome/browser/extensions/app_notification.h"
#include "base/memory/scoped_ptr.h"
-
-AppNotification::AppNotification(const std::string& title,
- const std::string& body)
- : title_(title), body_(body) {}
-
-AppNotification::~AppNotification() {}
+#include "chrome/common/guid.h"
namespace {
+const char* kIsLocalKey = "is_local";
+const char* kGuidKey = "guid";
+const char* kExtensionIdKey = "extension_id";
const char* kTitleKey = "title";
const char* kBodyKey = "body";
const char* kLinkUrlKey = "link_url";
@@ -21,8 +19,27 @@ const char* kLinkTextKey = "link_text";
} // namespace
+AppNotification::AppNotification(bool is_local,
+ const std::string& guid,
+ const std::string& extension_id,
+ const std::string& title,
+ const std::string& body)
+ : is_local_(is_local),
+ extension_id_(extension_id),
+ title_(title),
+ body_(body) {
+ guid_ = guid.empty() ? guid::GenerateGUID() : guid;
+}
+
+AppNotification::~AppNotification() {}
+
void AppNotification::ToDictionaryValue(DictionaryValue* result) {
CHECK(result);
+ result->SetBoolean(kIsLocalKey, is_local_);
+ if (!guid_.empty())
+ result->SetString(kGuidKey, guid_);
+ if (!extension_id_.empty())
+ result->SetString(kExtensionIdKey, extension_id_);
if (!title_.empty())
result->SetString(kTitleKey, title_);
if (!body_.empty())
@@ -36,9 +53,17 @@ void AppNotification::ToDictionaryValue(DictionaryValue* result) {
// static
AppNotification* AppNotification::FromDictionaryValue(
const DictionaryValue& value) {
+ scoped_ptr<AppNotification> result(new AppNotification(true, "", "", "", ""));
- scoped_ptr<AppNotification> result(new AppNotification("", ""));
-
+ if (value.HasKey(kIsLocalKey) && !value.GetBoolean(
+ kIsLocalKey, &result->is_local_)) {
+ return NULL;
+ }
+ if (value.HasKey(kGuidKey) && !value.GetString(kGuidKey, &result->guid_))
+ return NULL;
+ if (value.HasKey(kExtensionIdKey) &&
+ !value.GetString(kExtensionIdKey, &result->extension_id_))
+ return NULL;
if (value.HasKey(kTitleKey) && !value.GetString(kTitleKey, &result->title_))
return NULL;
if (value.HasKey(kBodyKey) && !value.GetString(kBodyKey, &result->body_))
@@ -60,7 +85,10 @@ AppNotification* AppNotification::FromDictionaryValue(
}
bool AppNotification::Equals(const AppNotification& other) const {
- return (title_ == other.title_ &&
+ return (is_local_ == other.is_local_ &&
+ guid_ == other.guid_ &&
+ extension_id_ == other.extension_id_ &&
+ title_ == other.title_ &&
body_ == other.body_ &&
link_url_ == other.link_url_ &&
link_text_ == other.link_text_);
diff --git a/chrome/browser/extensions/app_notification.h b/chrome/browser/extensions/app_notification.h
index 8fd7743..4a9deb6 100644
--- a/chrome/browser/extensions/app_notification.h
+++ b/chrome/browser/extensions/app_notification.h
@@ -17,7 +17,14 @@
// displayed on the New Tab Page.
class AppNotification {
public:
- AppNotification(const std::string& title, const std::string& body);
+ // Creates an instance with the given properties.
+ // If |is_local| is true, notification is not synced.
+ // If |guid| is empty, a new guid is automatically created.
+ AppNotification(bool is_local,
+ const std::string& guid,
+ const std::string& extension_id,
+ const std::string& title,
+ const std::string& body);
~AppNotification();
// Setters for optional properties.
@@ -25,6 +32,9 @@ class AppNotification {
void set_link_text(const std::string& text) { link_text_ = text; }
// Accessors.
+ bool is_local() const { return is_local_; }
+ const std::string& guid() const { return guid_; }
+ const std::string& extension_id() const { return extension_id_; }
const std::string& title() const { return title_; }
const std::string& body() const { return body_; }
const GURL& link_url() const { return link_url_; }
@@ -41,6 +51,12 @@ class AppNotification {
// If you add to the list of data members, make sure to add appropriate checks
// to the Equals and {To,From}DictionaryValue methods, keeping in mind
// backwards compatibility.
+
+ // Whether notification is local only, which means it is not synced
+ // across machines.
+ bool is_local_;
+ std::string guid_;
+ std::string extension_id_;
std::string title_;
std::string body_;
GURL link_url_;
diff --git a/chrome/browser/extensions/app_notification_manager.cc b/chrome/browser/extensions/app_notification_manager.cc
index 01a2bbc..53b753d4 100644
--- a/chrome/browser/extensions/app_notification_manager.cc
+++ b/chrome/browser/extensions/app_notification_manager.cc
@@ -4,31 +4,75 @@
#include "chrome/browser/extensions/app_notification_manager.h"
+#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/file_path.h"
+#include "base/location.h"
#include "base/stl_util.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/protocol/app_notification_specifics.pb.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/extensions/extension.h"
#include "content/browser/browser_thread.h"
#include "content/common/notification_service.h"
-AppNotificationManager::AppNotificationManager(Profile* profile)
- : profile_(profile) {
- registrar_.Add(this,
- chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
- Source<Profile>(profile_));
-}
+typedef std::map<std::string, SyncData> SyncDataMap;
namespace {
+class GuidComparator
+ : public std::binary_function<linked_ptr<AppNotification>,
+ std::string,
+ bool> {
+ public:
+ bool operator() (linked_ptr<AppNotification> notif,
+ const std::string& guid) const {
+ return notif->guid() == guid;
+ }
+};
+
void DeleteStorageOnFileThread(AppNotificationStorage* storage) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
delete storage;
}
+const AppNotification* FindByGuid(const AppNotificationList& list,
+ const std::string& guid) {
+ AppNotificationList::const_iterator iter = std::find_if(
+ list.begin(), list.end(), std::bind2nd(GuidComparator(), guid));
+ return iter == list.end() ? NULL : iter->get();
+}
+
+void RemoveByGuid(AppNotificationList* list, const std::string& guid) {
+ if (!list)
+ return;
+
+ AppNotificationList::iterator iter = std::find_if(
+ list->begin(), list->end(), std::bind2nd(GuidComparator(), guid));
+ if (iter != list->end())
+ list->erase(iter);
+}
+
+void PopulateGuidToSyncDataMap(const SyncDataList& sync_data,
+ SyncDataMap* data_map) {
+ for (SyncDataList::const_iterator iter = sync_data.begin();
+ iter != sync_data.end(); ++iter) {
+ (*data_map)[iter->GetSpecifics().GetExtension(
+ sync_pb::app_notification).guid()] = *iter;
+ }
+}
} // namespace
+AppNotificationManager::AppNotificationManager(Profile* profile)
+ : profile_(profile),
+ sync_processor_(NULL),
+ models_associated_(false),
+ processing_syncer_changes_(false) {
+ registrar_.Add(this,
+ chrome::NOTIFICATION_EXTENSION_UNINSTALLED,
+ Source<Profile>(profile_));
+}
+
AppNotificationManager::~AppNotificationManager() {
// Post a task to delete our storage on the file thread.
BrowserThread::PostTask(
@@ -42,59 +86,79 @@ void AppNotificationManager::Init() {
BrowserThread::PostTask(
BrowserThread::FILE,
FROM_HERE,
- base::Bind(
- &AppNotificationManager::LoadOnFileThread, this, storage_path));
+ base::Bind(&AppNotificationManager::LoadOnFileThread,
+ this, storage_path));
}
-void AppNotificationManager::Add(const std::string& extension_id,
- AppNotification* item) {
- NotificationMap::iterator found = notifications_.find(extension_id);
- if (found == notifications_.end()) {
- notifications_[extension_id] = AppNotificationList();
- found = notifications_.find(extension_id);
- }
- CHECK(found != notifications_.end());
- AppNotificationList& list = (*found).second;
- list.push_back(linked_ptr<AppNotification>(item));
+bool AppNotificationManager::Add(AppNotification* item) {
+ // Use a scoped_ptr to delete in error paths.
+ scoped_ptr<AppNotification> scoped_item(item);
+ if (!loaded())
+ return false;
+ const std::string& extension_id = item->extension_id();
+ AppNotificationList& list = GetAllInternal(extension_id);
+ linked_ptr<AppNotification> linked_item(scoped_item.release());
+ list.push_back(linked_ptr<AppNotification>(linked_item));
- if (!storage_.get())
- return;
+ SyncAddChange(*linked_item);
- BrowserThread::PostTask(
- BrowserThread::FILE,
- FROM_HERE,
- base::Bind(
- &AppNotificationManager::SaveOnFileThread, this, extension_id, list));
+ if (storage_.get()) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&AppNotificationManager::SaveOnFileThread,
+ this, extension_id, list));
+ }
+ return true;
}
const AppNotificationList* AppNotificationManager::GetAll(
- const std::string& extension_id) {
- if (ContainsKey(notifications_, extension_id))
- return &notifications_[extension_id];
+ const std::string& extension_id) const {
+ if (!loaded())
+ return NULL;
+ if (ContainsKey(*notifications_, extension_id))
+ return &((*notifications_)[extension_id]);
return NULL;
}
+AppNotificationList& AppNotificationManager::GetAllInternal(
+ const std::string& extension_id) {
+ NotificationMap::iterator found = notifications_->find(extension_id);
+ if (found == notifications_->end()) {
+ (*notifications_)[extension_id] = AppNotificationList();
+ found = notifications_->find(extension_id);
+ }
+ CHECK(found != notifications_->end());
+ return found->second;
+}
+
const AppNotification* AppNotificationManager::GetLast(
const std::string& extension_id) {
- NotificationMap::iterator found = notifications_.find(extension_id);
- if (found == notifications_.end())
+ if (!loaded())
+ return NULL;
+ NotificationMap::iterator found = notifications_->find(extension_id);
+ if (found == notifications_->end())
return NULL;
const AppNotificationList& list = found->second;
return list.rbegin()->get();
}
void AppNotificationManager::ClearAll(const std::string& extension_id) {
- NotificationMap::iterator found = notifications_.find(extension_id);
- if (found != notifications_.end())
- notifications_.erase(found);
-
- if (!storage_.get())
+ if (!loaded())
return;
- BrowserThread::PostTask(
- BrowserThread::FILE,
- FROM_HERE,
- base::Bind(
- &AppNotificationManager::DeleteOnFileThread, this, extension_id));
+ NotificationMap::iterator found = notifications_->find(extension_id);
+ if (found != notifications_->end()) {
+ SyncClearAllChange(found->second);
+ notifications_->erase(found);
+ }
+
+ if (storage_.get()) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&AppNotificationManager::DeleteOnFileThread,
+ this, extension_id));
+ }
}
void AppNotificationManager::Observe(int type,
@@ -106,43 +170,53 @@ void AppNotificationManager::Observe(int type,
void AppNotificationManager::LoadOnFileThread(const FilePath& storage_path) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
+ DCHECK(!loaded());
storage_.reset(AppNotificationStorage::Create(storage_path));
if (!storage_.get())
return;
- NotificationMap result;
+ scoped_ptr<NotificationMap> result(new NotificationMap());
std::set<std::string> ids;
if (!storage_->GetExtensionIds(&ids))
return;
std::set<std::string>::const_iterator i;
for (i = ids.begin(); i != ids.end(); ++i) {
const std::string& id = *i;
- AppNotificationList& list = result[id];
+ AppNotificationList& list = (*result)[id];
if (!storage_->Get(id, &list))
- result.erase(id);
+ result->erase(id);
}
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
- base::Bind(&AppNotificationManager::HandleLoadResults, this, result));
+ base::Bind(&AppNotificationManager::HandleLoadResults,
+ this, result.release()));
}
-void AppNotificationManager::HandleLoadResults(const NotificationMap& map) {
+void AppNotificationManager::HandleLoadResults(NotificationMap* map) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(map);
+ DCHECK(!loaded());
+ notifications_.reset(map);
+
+ // Generate STATE_CHAGNED notifications for extensions that have at
+ // least one notification loaded.
NotificationMap::const_iterator i;
- for (i = map.begin(); i != map.end(); ++i) {
+ for (i = map->begin(); i != map->end(); ++i) {
const std::string& id = i->first;
- const AppNotificationList& list = i->second;
- if (list.empty())
+ if (i->second.empty())
continue;
- notifications_[id].insert(notifications_[id].begin(),
- list.begin(),
- list.end());
NotificationService::current()->Notify(
chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
Source<Profile>(profile_),
Details<const std::string>(&id));
}
+
+ // Generate MANAGER_LOADED notification.
+ NotificationService::current()->Notify(
+ chrome::NOTIFICATION_APP_NOTIFICATION_MANAGER_LOADED,
+ Source<AppNotificationManager>(this),
+ NotificationService::NoDetails());
}
void AppNotificationManager::SaveOnFileThread(const std::string& extension_id,
@@ -156,3 +230,249 @@ void AppNotificationManager::DeleteOnFileThread(
CHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
storage_->Delete(extension_id);
}
+
+SyncDataList AppNotificationManager::GetAllSyncData(
+ syncable::ModelType type) const {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(loaded());
+ DCHECK_EQ(syncable::APP_NOTIFICATIONS, type);
+ SyncDataList data;
+ for (NotificationMap::const_iterator iter = notifications_->begin();
+ iter != notifications_->end(); ++iter) {
+
+ // Skip local notifications since they should not be synced.
+ const AppNotificationList list = (*iter).second;
+ for (AppNotificationList::const_iterator list_iter = list.begin();
+ list_iter != list.end(); ++list_iter) {
+ const AppNotification* notification = (*list_iter).get();
+ if (notification->is_local()) {
+ continue;
+ }
+ data.push_back(CreateSyncDataFromNotification(*notification));
+ }
+ }
+
+ return data;
+}
+
+SyncError AppNotificationManager::ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const SyncChangeList& change_list) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(loaded());
+ if (!models_associated_)
+ return SyncError(FROM_HERE, "Models not yet associated.",
+ syncable::APP_NOTIFICATIONS);
+
+ AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
+
+ SyncError error;
+ for (SyncChangeList::const_iterator iter = change_list.begin();
+ iter != change_list.end(); ++iter) {
+ SyncData sync_data = iter->sync_data();
+ DCHECK_EQ(syncable::APP_NOTIFICATIONS, sync_data.GetDataType());
+ SyncChange::SyncChangeType change_type = iter->change_type();
+
+ scoped_ptr<AppNotification> new_notif(CreateNotificationFromSyncData(
+ sync_data));
+ if (!new_notif.get()) {
+ NOTREACHED() << "Failed to read notification.";
+ continue;
+ }
+ const AppNotification* existing_notif = GetNotification(
+ new_notif->extension_id(), new_notif->guid());
+ if (existing_notif && existing_notif->is_local()) {
+ NOTREACHED() << "Matched with notification marked as local";
+ error = SyncError(FROM_HERE,
+ "ProcessSyncChanges received a local only notification" +
+ SyncChange::ChangeTypeToString(change_type),
+ syncable::APP_NOTIFICATIONS);
+ continue;
+ }
+
+ if (change_type == SyncChange::ACTION_ADD && !existing_notif) {
+ Add(new_notif.release());
+ } else if (change_type == SyncChange::ACTION_DELETE && existing_notif) {
+ Remove(existing_notif->extension_id(), existing_notif->guid());
+ } else {
+ // Something really unexpected happened. Either we received an
+ // ACTION_INVALID, or Sync is in a crazy state:
+ // - Trying to UPDATE: notifications are immutable.
+ // . Trying to DELETE a non-existent item.
+ // - Trying to ADD an item that already exists.
+ NOTREACHED() << "Unexpected sync change state.";
+ error = SyncError(FROM_HERE, "ProcessSyncChanges failed on ChangeType " +
+ SyncChange::ChangeTypeToString(change_type),
+ syncable::APP_NOTIFICATIONS);
+ continue;
+ }
+ }
+
+ return error;
+}
+
+SyncError AppNotificationManager::MergeDataAndStartSyncing(
+ syncable::ModelType type,
+ const SyncDataList& initial_sync_data,
+ SyncChangeProcessor* sync_processor) {
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ // AppNotificationDataTypeController ensures that modei is fully should before
+ // this method is called by waiting until the load notification is received
+ // from AppNotificationManager.
+ DCHECK(loaded());
+ DCHECK_EQ(type, syncable::APP_NOTIFICATIONS);
+ DCHECK(!sync_processor_);
+ sync_processor_ = sync_processor;
+
+ // We may add, or remove notifications here, so ensure we don't step on
+ // our own toes.
+ AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
+
+ SyncDataMap local_data_map;
+ PopulateGuidToSyncDataMap(GetAllSyncData(syncable::APP_NOTIFICATIONS),
+ &local_data_map);
+
+ for (SyncDataList::const_iterator iter = initial_sync_data.begin();
+ iter != initial_sync_data.end(); ++iter) {
+ const SyncData& sync_data = *iter;
+ DCHECK_EQ(syncable::APP_NOTIFICATIONS, sync_data.GetDataType());
+ scoped_ptr<AppNotification> sync_notif(CreateNotificationFromSyncData(
+ sync_data));
+ CHECK(sync_notif.get());
+ const AppNotification* local_notif = GetNotification(
+ sync_notif->extension_id(), sync_notif->guid());
+ if (local_notif) {
+ local_data_map.erase(sync_notif->guid());
+ // Local notification should always match with sync notification as
+ // notifications are immutable.
+ if (local_notif->is_local() || !sync_notif->Equals(*local_notif)) {
+ return SyncError(FROM_HERE,
+ "MergeDataAndStartSyncing failed: local notification and sync "
+ "notification have same guid but different data.",
+ syncable::APP_NOTIFICATIONS);
+ }
+ } else {
+ // Sync model has a notification that local model does not, add it.
+ Add(sync_notif.release());
+ }
+ }
+
+ // TODO(munjal): crbug.com/10059. Work with Lingesh/Antony to resolve.
+ SyncChangeList new_changes;
+ for (SyncDataMap::const_iterator iter = local_data_map.begin();
+ iter != local_data_map.end(); ++iter) {
+ new_changes.push_back(SyncChange(SyncChange::ACTION_ADD, iter->second));
+ }
+
+ SyncError error;
+ if (new_changes.size() > 0)
+ error = sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes);
+ models_associated_ = !error.IsSet();
+ return error;
+}
+
+void AppNotificationManager::StopSyncing(syncable::ModelType type) {
+ DCHECK_EQ(type, syncable::APP_NOTIFICATIONS);
+ models_associated_ = false;
+ sync_processor_ = NULL;
+}
+
+void AppNotificationManager::SyncAddChange(const AppNotification& notif) {
+ // Skip if either:
+ // - Notification is marked as local.
+ // - Sync is not enabled by user.
+ // - Change is generated from within the manager.
+ if (notif.is_local() || !models_associated_ || processing_syncer_changes_)
+ return;
+
+ // TODO(munjal): crbug.com/10059. Work with Lingesh/Antony to resolve.
+
+ SyncChangeList changes;
+ SyncData sync_data = CreateSyncDataFromNotification(notif);
+ changes.push_back(SyncChange(SyncChange::ACTION_ADD, sync_data));
+ sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
+}
+
+void AppNotificationManager::SyncClearAllChange(
+ const AppNotificationList& list) {
+ // Skip if either:
+ // - Sync is not enabled by user.
+ // - Change is generated from within the manager.
+ if (!models_associated_ || processing_syncer_changes_)
+ return;
+
+ SyncChangeList changes;
+ for (AppNotificationList::const_iterator iter = list.begin();
+ iter != list.end(); ++iter) {
+ const AppNotification& notif = *iter->get();
+ // Skip notifications marked as local.
+ if (notif.is_local())
+ continue;
+ changes.push_back(SyncChange(
+ SyncChange::ACTION_DELETE,
+ CreateSyncDataFromNotification(notif)));
+ }
+ sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
+}
+
+const AppNotification* AppNotificationManager::GetNotification(
+ const std::string& extension_id, const std::string& guid) {
+ DCHECK(loaded());
+ const AppNotificationList& list = GetAllInternal(extension_id);
+ return FindByGuid(list, guid);
+}
+
+void AppNotificationManager::Remove(const std::string& extension_id,
+ const std::string& guid) {
+ DCHECK(loaded());
+ AppNotificationList& list = GetAllInternal(extension_id);
+ RemoveByGuid(&list, guid);
+
+ if (storage_.get()) {
+ BrowserThread::PostTask(
+ BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&AppNotificationManager::SaveOnFileThread,
+ this, extension_id, list));
+ }
+}
+
+// static
+SyncData AppNotificationManager::CreateSyncDataFromNotification(
+ const AppNotification& notification) {
+ DCHECK(!notification.is_local());
+ sync_pb::EntitySpecifics specifics;
+ sync_pb::AppNotificationSpecifics* notif_specifics =
+ specifics.MutableExtension(sync_pb::app_notification);
+ notif_specifics->set_app_id(notification.extension_id());
+ notif_specifics->set_body_text(notification.body());
+ notif_specifics->set_guid(notification.guid());
+ notif_specifics->set_link_text(notification.link_text());
+ notif_specifics->set_link_url(notification.link_url().spec());
+ notif_specifics->set_title(notification.title());
+
+ return SyncData::CreateLocalData(
+ notif_specifics->guid(), notif_specifics->app_id(), specifics);
+}
+
+// static
+AppNotification* AppNotificationManager::CreateNotificationFromSyncData(
+ const SyncData& sync_data) {
+ sync_pb::AppNotificationSpecifics specifics =
+ sync_data.GetSpecifics().GetExtension(sync_pb::app_notification);
+
+ // Check for mandatory fields.
+ if (!specifics.has_app_id() || !specifics.has_guid() ||
+ !specifics.has_title() || !specifics.has_body_text()) {
+ return NULL;
+ }
+
+ AppNotification* notification = new AppNotification(
+ false, specifics.guid(), specifics.app_id(),
+ specifics.title(), specifics.body_text());
+ if (specifics.has_link_text())
+ notification->set_link_text(specifics.link_text());
+ if (specifics.has_link_url())
+ notification->set_link_url(GURL(specifics.link_url()));
+ return notification;
+}
diff --git a/chrome/browser/extensions/app_notification_manager.h b/chrome/browser/extensions/app_notification_manager.h
index 8148a08..7c37cc7 100644
--- a/chrome/browser/extensions/app_notification_manager.h
+++ b/chrome/browser/extensions/app_notification_manager.h
@@ -9,10 +9,15 @@
#include <map>
#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
+#include "base/synchronization/waitable_event.h"
#include "chrome/browser/extensions/app_notification.h"
#include "chrome/browser/extensions/app_notification_storage.h"
+#include "chrome/browser/sync/api/sync_change.h"
+#include "chrome/browser/sync/api/syncable_service.h"
+#include "content/common/notification_details.h"
#include "content/common/notification_observer.h"
#include "content/common/notification_registrar.h"
@@ -21,17 +26,20 @@ class Profile;
// This class keeps track of notifications for installed apps.
class AppNotificationManager
: public base::RefCountedThreadSafe<AppNotificationManager>,
- public NotificationObserver {
+ public NotificationObserver,
+ public SyncableService {
public:
explicit AppNotificationManager(Profile* profile);
+ virtual ~AppNotificationManager();
// Starts up the process of reading from persistent storage.
void Init();
- // Takes ownership of |notification|.
- void Add(const std::string& extension_id, AppNotification* item);
+ // Returns whether add was succcessful.
+ // Takes ownership of |item| in all cases.
+ bool Add(AppNotification* item);
- const AppNotificationList* GetAll(const std::string& extension_id);
+ const AppNotificationList* GetAll(const std::string& extension_id) const;
// Returns the most recently added notification for |extension_id| if there
// are any, or NULL otherwise.
@@ -45,32 +53,105 @@ class AppNotificationManager
const NotificationSource& source,
const NotificationDetails& details) OVERRIDE;
+ bool loaded() const { return notifications_.get() != NULL; }
+
+ // SyncableService implementation.
+
+ // Returns all syncable notifications from this model as SyncData.
+ virtual SyncDataList GetAllSyncData(syncable::ModelType type) const OVERRIDE;
+ // Process notifications related changes from Sync, merging them into
+ // our model.
+ virtual SyncError ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const SyncChangeList& change_list) OVERRIDE;
+ // Associate and merge sync data model with our data model.
+ virtual SyncError MergeDataAndStartSyncing(
+ syncable::ModelType type,
+ const SyncDataList& initial_sync_data,
+ SyncChangeProcessor* sync_processor) OVERRIDE;
+ virtual void StopSyncing(syncable::ModelType type) OVERRIDE;
+
private:
+ friend class AppNotificationManagerSyncTest;
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ NotificationToSyncDataToNotification);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ GetAllSyncDataNoLocal);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ GetAllSyncDataSomeLocal);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ModelAssocModelEmpty);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ModelAssocBothNonEmptyNoOverlap);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ModelAssocBothNonEmptySomeOverlap);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ModelAssocBothNonEmptyTitleMismatch);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ModelAssocBothNonEmptyMatchesLocal);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ProcessSyncChangesErrorModelAssocNotDone);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ProcessSyncChangesEmptyModel);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ StopSyncing);
+ FRIEND_TEST_ALL_PREFIXES(AppNotificationManagerSyncTest,
+ ClearAllGetsSynced);
+
friend class base::RefCountedThreadSafe<AppNotificationManager>;
// Maps extension id to a list of notifications for that extension.
typedef std::map<std::string, AppNotificationList> NotificationMap;
- virtual ~AppNotificationManager();
-
// Starts loading storage_ using |storage_path|.
void LoadOnFileThread(const FilePath& storage_path);
// Called on the UI thread to handle the loaded results from storage_.
- void HandleLoadResults(const NotificationMap& map);
+ void HandleLoadResults(NotificationMap* map);
void SaveOnFileThread(const std::string& extension_id,
const AppNotificationList& list);
void DeleteOnFileThread(const std::string& extension_id);
+ // Gets notficiations for a given extension id.
+ AppNotificationList& GetAllInternal(const std::string& extension_id);
+
+ // Removes the notification with given extension id and given guid.
+ void Remove(const std::string& extension_id, const std::string& guid);
+
+ // Returns the notification for the given |extension_id| and |guid|,
+ // NULL if no such notification exists.
+ const AppNotification* GetNotification(const std::string& extension_id,
+ const std::string& guid);
+
+ // Sends a change to syncer to add the given notification.
+ void SyncAddChange(const AppNotification& notif);
+
+ // Sends changes to syncer to remove all notifications in the given list.
+ void SyncClearAllChange(const AppNotificationList& list);
+
+ // Converters from AppNotification to SyncData and vice versa.
+ static SyncData CreateSyncDataFromNotification(
+ const AppNotification& notification);
+ static AppNotification* CreateNotificationFromSyncData(
+ const SyncData& sync_data);
+
Profile* profile_;
NotificationRegistrar registrar_;
- NotificationMap notifications_;
+ scoped_ptr<NotificationMap> notifications_;
// This should only be used on the FILE thread.
scoped_ptr<AppNotificationStorage> storage_;
+ // Sync change processor we use to push all our changes.
+ SyncChangeProcessor* sync_processor_;
+ // Whether the sync model is associated with the local model.
+ // In other words, whether we are ready to apply sync changes.
+ bool models_associated_;
+ // Whether syncer changes are being processed right now.
+ bool processing_syncer_changes_;
+
DISALLOW_COPY_AND_ASSIGN(AppNotificationManager);
};
diff --git a/chrome/browser/extensions/app_notification_manager_sync_unittest.cc b/chrome/browser/extensions/app_notification_manager_sync_unittest.cc
new file mode 100644
index 0000000..41589d7
--- /dev/null
+++ b/chrome/browser/extensions/app_notification_manager_sync_unittest.cc
@@ -0,0 +1,588 @@
+// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/string_number_conversions.h"
+#include "chrome/browser/extensions/app_notification_manager.h"
+#include "chrome/browser/extensions/app_notification.h"
+#include "chrome/browser/sync/protocol/app_notification_specifics.pb.h"
+#include "chrome/test/base/testing_profile.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Extract notification guid from SyncData.
+std::string GetGuid(const SyncData& sync_data) {
+ return sync_data.GetSpecifics().GetExtension(
+ sync_pb::app_notification).guid();
+}
+
+// Dummy SyncChangeProcessor used to help review what SyncChanges are pushed
+// back up to Sync.
+class TestChangeProcessor : public SyncChangeProcessor {
+ public:
+ TestChangeProcessor() { }
+ virtual ~TestChangeProcessor() { }
+
+ // Store a copy of all the changes passed in so we can examine them later.
+ virtual SyncError ProcessSyncChanges(
+ const tracked_objects::Location& from_here,
+ const SyncChangeList& change_list) {
+ // change_map_.erase(change_map_.begin(), change_map_.end());
+ for (SyncChangeList::const_iterator iter = change_list.begin();
+ iter != change_list.end(); ++iter) {
+ change_map_[GetGuid(iter->sync_data())] = *iter;
+ }
+
+ return SyncError();
+ }
+
+ bool ContainsGuid(const std::string& guid) {
+ return change_map_.find(guid) != change_map_.end();
+ }
+
+ SyncChange GetChangeByGuid(const std::string& guid) {
+ DCHECK(ContainsGuid(guid));
+ return change_map_[guid];
+ }
+
+ int change_list_size() { return change_map_.size(); }
+
+ private:
+ // Track the changes received in ProcessSyncChanges.
+ std::map<std::string, SyncChange> change_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestChangeProcessor);
+};
+
+} // namespace
+
+class AppNotificationManagerSyncTest : public testing::Test {
+ public:
+ AppNotificationManagerSyncTest()
+ : ui_thread_(BrowserThread::UI, &ui_loop_),
+ file_thread_(BrowserThread::FILE) { }
+
+ ~AppNotificationManagerSyncTest() {
+ model_ = NULL;
+ }
+
+ virtual void SetUp() {
+ ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ file_thread_.Start();
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ profile_.reset(new TestingProfile(temp_dir_.path()));
+ model_ = new AppNotificationManager(profile_.get());
+ model_->Init();
+
+ WaitForFileThread();
+ ASSERT_TRUE(model_->loaded());
+ }
+
+ virtual void TearDown() { }
+
+ static void PostQuitToUIThread() {
+ BrowserThread::PostTask(BrowserThread::UI,
+ FROM_HERE,
+ new MessageLoop::QuitTask());
+ }
+
+ static void WaitForFileThread() {
+ BrowserThread::PostTask(BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&PostQuitToUIThread));
+ MessageLoop::current()->Run();
+ }
+
+ AppNotificationManager* model() { return model_.get(); }
+ TestChangeProcessor* processor() { return &processor_; }
+
+ // Creates a notification whose properties are set from the given integer.
+ static AppNotification* CreateNotification(int suffix) {
+ return CreateNotification(false, suffix);
+ }
+ static AppNotification* CreateNotification(bool is_local, int suffix) {
+ std::string s = base::IntToString(suffix);
+ return CreateNotification(
+ is_local, "guid" + s, "ext" + s, "text" + s, "body" + s,
+ "http://www.url" + s + ".com", "link text " + s);
+ }
+ static AppNotification* CreateNotification(
+ bool is_local, int suffix, const std::string& extension_id) {
+ std::string s = base::IntToString(suffix);
+ return CreateNotification(
+ is_local, "guid" + s, extension_id, "text" + s, "body" + s,
+ "http://www.url" + s + ".com", "link text " + s);
+ }
+
+ // Creates a notification whose properties are set from the given integer
+ // but does not set link url and link text.
+ static AppNotification* CreateNotificationNoLink(int suffix) {
+ return CreateNotificationNoLink(false, suffix);
+ }
+ static AppNotification* CreateNotificationNoLink(bool is_local, int suffix) {
+ std::string s = base::IntToString(suffix);
+ return CreateNotification(
+ is_local, "guid" + s, "ext" + s, "text" + s, "body" + s, "", "");
+ }
+
+ // link_url and link_text are only set if the passed in values are not empty.
+ static AppNotification* CreateNotification(bool is_local,
+ const std::string& guid,
+ const std::string& extension_id,
+ const std::string& title,
+ const std::string& body,
+ const std::string& link_url,
+ const std::string& link_text) {
+ AppNotification* notif = new AppNotification(
+ is_local, guid, extension_id, title, body);
+ if (!link_url.empty())
+ notif->set_link_url(GURL(link_url));
+ if (!link_text.empty())
+ notif->set_link_text(link_text);
+ return notif;
+ }
+
+ static AppNotification* CreateNotification(const AppNotification& notif) {
+ return CreateNotification(
+ notif.is_local(), notif.guid(), notif.extension_id(), notif.title(),
+ notif.body(), notif.link_url().spec(), notif.link_text());
+ }
+
+ static SyncData CreateSyncData(int suffix) {
+ scoped_ptr<AppNotification> notif(CreateNotification(suffix));
+ return AppNotificationManager::CreateSyncDataFromNotification(*notif);
+ }
+ static SyncData CreateSyncData(int suffix, const std::string& extension_id) {
+ scoped_ptr<AppNotification> notif(
+ CreateNotification(false, suffix, extension_id));
+ return AppNotificationManager::CreateSyncDataFromNotification(*notif);
+ }
+
+ // Helper to create SyncChange. Takes ownership of |notif|.
+ static SyncChange CreateSyncChange(SyncChange::SyncChangeType type,
+ AppNotification* notif) {
+ // Take control of notif to clean it up after we create data out of it.
+ scoped_ptr<AppNotification> scoped_notif(notif);
+ return SyncChange(
+ type, AppNotificationManager::CreateSyncDataFromNotification(*notif));
+ }
+
+ void AssertSyncChange(const SyncChange& change,
+ SyncChange::SyncChangeType type,
+ const AppNotification& notif) {
+ ASSERT_EQ(type, change.change_type());
+ const AppNotification* notif2 =
+ AppNotificationManager::CreateNotificationFromSyncData(
+ change.sync_data());
+ ASSERT_TRUE(notif.Equals(*notif2));
+ }
+
+ protected:
+ MessageLoop ui_loop_;
+ BrowserThread ui_thread_;
+ BrowserThread file_thread_;
+
+ // We keep two TemplateURLServices to test syncing between them.
+ ScopedTempDir temp_dir_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_refptr<AppNotificationManager> model_;
+
+ TestChangeProcessor processor_;
+
+ DISALLOW_COPY_AND_ASSIGN(AppNotificationManagerSyncTest);
+};
+
+// Create an AppNotification, convert it to SyncData and convert it back.
+TEST_F(AppNotificationManagerSyncTest, NotificationToSyncDataToNotification) {
+ { // Partial properties set.
+ scoped_ptr<AppNotification> notif1(CreateNotificationNoLink(1));
+ SyncData sync_data =
+ AppNotificationManager::CreateSyncDataFromNotification(*notif1);
+ scoped_ptr<AppNotification> notif2(
+ AppNotificationManager::CreateNotificationFromSyncData(sync_data));
+ EXPECT_TRUE(notif2.get());
+ EXPECT_TRUE(notif1->Equals(*notif2));
+ }
+ { // All properties set.
+ scoped_ptr<AppNotification> notif1(CreateNotification(1));
+ SyncData sync_data =
+ AppNotificationManager::CreateSyncDataFromNotification(*notif1);
+ scoped_ptr<AppNotification> notif2(
+ AppNotificationManager::CreateNotificationFromSyncData(sync_data));
+ EXPECT_TRUE(notif2.get());
+ EXPECT_TRUE(notif1->Equals(*notif2));
+ }
+}
+
+// GetAllSyncData returns all notifications since none are marked local only.
+TEST_F(AppNotificationManagerSyncTest, GetAllSyncDataNoLocal) {
+ model()->Add(CreateNotificationNoLink(1));
+ model()->Add(CreateNotification(2));
+ model()->Add(CreateNotification(3));
+ SyncDataList all_sync_data =
+ model()->GetAllSyncData(syncable::APP_NOTIFICATIONS);
+
+ EXPECT_EQ(3U, all_sync_data.size());
+
+ for (SyncDataList::const_iterator iter = all_sync_data.begin();
+ iter != all_sync_data.end(); ++iter) {
+ scoped_ptr<AppNotification> notif1(
+ AppNotificationManager::CreateNotificationFromSyncData(*iter));
+
+ const std::string& guid = notif1->guid();
+ const std::string& ext_id = notif1->extension_id();
+
+ const AppNotification* notif2 = model()->GetNotification(ext_id, guid);
+ ASSERT_TRUE(notif1->Equals(*notif2));
+ }
+}
+
+// GetAllSyncData should not return notifications marked as local only.
+TEST_F(AppNotificationManagerSyncTest, GetAllSyncDataSomeLocal) {
+ model()->Add(CreateNotificationNoLink(1));
+ model()->Add(CreateNotification(true, 2));
+ model()->Add(CreateNotification(3));
+ model()->Add(CreateNotification(true, 4));
+ model()->Add(CreateNotification(5));
+ SyncDataList all_sync_data =
+ model()->GetAllSyncData(syncable::APP_NOTIFICATIONS);
+
+ EXPECT_EQ(3U, all_sync_data.size());
+
+ for (SyncDataList::const_iterator iter = all_sync_data.begin();
+ iter != all_sync_data.end(); ++iter) {
+ scoped_ptr<AppNotification> notif1(
+ AppNotificationManager::CreateNotificationFromSyncData(*iter));
+ const std::string& guid = notif1->guid();
+ const std::string& ext_id = notif1->extension_id();
+
+ const AppNotification* notif2 = model()->GetNotification(ext_id, guid);
+ ASSERT_TRUE(notif1->Equals(*notif2));
+ }
+}
+
+// Model assocation: both models are empty.
+TEST_F(AppNotificationManagerSyncTest, ModelAssocBothEmpty) {
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ SyncDataList(), // Empty.
+ processor());
+
+ EXPECT_EQ(0U, model()->GetAllSyncData(syncable::APP_NOTIFICATIONS).size());
+ EXPECT_EQ(0, processor()->change_list_size());
+}
+
+// Model assocation: empty sync model and non-empty local model.
+TEST_F(AppNotificationManagerSyncTest, ModelAssocModelEmpty) {
+ SyncDataList initial_data;
+ initial_data.push_back(CreateSyncData(1));
+ initial_data.push_back(CreateSyncData(2));
+ initial_data.push_back(CreateSyncData(3));
+ initial_data.push_back(CreateSyncData(4));
+
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ initial_data,
+ processor());
+
+ EXPECT_EQ(4U, model()->GetAllSyncData(syncable::APP_NOTIFICATIONS).size());
+ // Model should all of the initial sync data.
+ for (SyncDataList::const_iterator iter = initial_data.begin();
+ iter != initial_data.end(); ++iter) {
+ scoped_ptr<AppNotification> notif1(
+ AppNotificationManager::CreateNotificationFromSyncData(*iter));
+ const std::string& ext_id = notif1->extension_id();
+ const std::string& guid = notif1->guid();
+ const AppNotification* notif2 = model()->GetNotification(ext_id, guid);
+ EXPECT_TRUE(notif2);
+ EXPECT_TRUE(notif1->Equals(*notif2));
+ }
+
+ EXPECT_EQ(0, processor()->change_list_size());
+}
+
+// Model has some notifications, some of them are local only. Sync has some
+// notifications. No items match up.
+TEST_F(AppNotificationManagerSyncTest, ModelAssocBothNonEmptyNoOverlap) {
+ AppNotification* n1 = CreateNotification(1);
+ model()->Add(n1);
+ AppNotification* n2 = CreateNotification(true, 2);
+ model()->Add(n2);
+ AppNotification* n3 = CreateNotification(3);
+ model()->Add(n3);
+
+ SyncDataList initial_data;
+ initial_data.push_back(CreateSyncData(4));
+ initial_data.push_back(CreateSyncData(5));
+ initial_data.push_back(CreateSyncData(6));
+ initial_data.push_back(CreateSyncData(7));
+
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ initial_data,
+ processor());
+
+ EXPECT_EQ(6U, model()->GetAllSyncData(syncable::APP_NOTIFICATIONS).size());
+ for (SyncDataList::const_iterator iter = initial_data.begin();
+ iter != initial_data.end(); ++iter) {
+ scoped_ptr<AppNotification> notif1(
+ AppNotificationManager::CreateNotificationFromSyncData(*iter));
+ const std::string& ext_id = notif1->extension_id();
+ const std::string& guid = notif1->guid();
+ const AppNotification* notif2 = model()->GetNotification(ext_id, guid);
+ EXPECT_TRUE(notif2);
+ EXPECT_TRUE(notif1->Equals(*notif2));
+ }
+ EXPECT_TRUE(model()->GetNotification(n1->extension_id(), n1->guid()));
+ EXPECT_TRUE(model()->GetNotification(n2->extension_id(), n2->guid()));
+ EXPECT_TRUE(model()->GetNotification(n3->extension_id(), n3->guid()));
+
+ EXPECT_EQ(2, processor()->change_list_size());
+ EXPECT_TRUE(processor()->ContainsGuid(n1->guid()));
+ EXPECT_EQ(SyncChange::ACTION_ADD,
+ processor()->GetChangeByGuid(n1->guid()).change_type());
+ EXPECT_FALSE(processor()->ContainsGuid(n2->guid()));
+ EXPECT_TRUE(processor()->ContainsGuid(n3->guid()));
+ EXPECT_EQ(SyncChange::ACTION_ADD,
+ processor()->GetChangeByGuid(n3->guid()).change_type());
+}
+
+// Model has some notifications, some of them are local only. Sync has some
+// notifications. Some items match up.
+TEST_F(AppNotificationManagerSyncTest, ModelAssocBothNonEmptySomeOverlap) {
+ AppNotification* n1 = CreateNotification(1);
+ model()->Add(n1);
+ AppNotification* n2 = CreateNotification(true, 2);
+ model()->Add(n2);
+ AppNotification* n3 = CreateNotification(3);
+ model()->Add(n3);
+ AppNotification* n4 = CreateNotification(4);
+ model()->Add(n4);
+
+ SyncDataList initial_data;
+ initial_data.push_back(CreateSyncData(5));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n1));
+ initial_data.push_back(CreateSyncData(6));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n4));
+ initial_data.push_back(CreateSyncData(7));
+
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ initial_data,
+ processor());
+
+ EXPECT_EQ(6U, model()->GetAllSyncData(syncable::APP_NOTIFICATIONS).size());
+ for (SyncDataList::const_iterator iter = initial_data.begin();
+ iter != initial_data.end(); ++iter) {
+ scoped_ptr<AppNotification> notif1(
+ AppNotificationManager::CreateNotificationFromSyncData(*iter));
+ const std::string& ext_id = notif1->extension_id();
+ const std::string& guid = notif1->guid();
+ const AppNotification* notif2 = model()->GetNotification(ext_id, guid);
+ EXPECT_TRUE(notif2);
+ EXPECT_TRUE(notif1->Equals(*notif2));
+ }
+ EXPECT_TRUE(model()->GetNotification(n1->extension_id(), n1->guid()));
+ EXPECT_TRUE(model()->GetNotification(n2->extension_id(), n2->guid()));
+ EXPECT_TRUE(model()->GetNotification(n3->extension_id(), n3->guid()));
+ EXPECT_TRUE(model()->GetNotification(n4->extension_id(), n4->guid()));
+
+ EXPECT_EQ(1, processor()->change_list_size());
+ EXPECT_FALSE(processor()->ContainsGuid(n1->guid()));
+ EXPECT_FALSE(processor()->ContainsGuid(n2->guid()));
+ EXPECT_TRUE(processor()->ContainsGuid(n3->guid()));
+ EXPECT_EQ(SyncChange::ACTION_ADD,
+ processor()->GetChangeByGuid(n3->guid()).change_type());
+ EXPECT_FALSE(processor()->ContainsGuid(n4->guid()));
+}
+
+// When an item that matches up in model and sync is different, an error
+// should be returned.
+TEST_F(AppNotificationManagerSyncTest, ModelAssocBothNonEmptyTitleMismatch) {
+ AppNotification* n1 = CreateNotification(1);
+ model()->Add(n1);
+ AppNotification* n2 = CreateNotification(true, 2);
+ model()->Add(n2);
+
+ SyncDataList initial_data;
+ initial_data.push_back(CreateSyncData(1));
+ scoped_ptr<AppNotification> n1_a(CreateNotification(
+ n1->is_local(), n1->guid(), n1->extension_id(),
+ n1->title() + "_changed", // different title
+ n1->body(), n1->link_url().spec(), n1->link_text()));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n1_a));
+
+ SyncError sync_error = model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ initial_data,
+ processor());
+
+ EXPECT_TRUE(sync_error.IsSet());
+ EXPECT_EQ(syncable::APP_NOTIFICATIONS, sync_error.type());
+ EXPECT_EQ(0, processor()->change_list_size());
+}
+
+// When an item in sync matches with a local-only item in model, an error
+// should be returned.
+TEST_F(AppNotificationManagerSyncTest, ModelAssocBothNonEmptyMatchesLocal) {
+ AppNotification* n1 = CreateNotification(1);
+ model()->Add(n1);
+ AppNotification* n2 = CreateNotification(true, 2);
+ model()->Add(n2);
+
+ SyncDataList initial_data;
+ initial_data.push_back(CreateSyncData(1));
+ scoped_ptr<AppNotification> n2_a(CreateNotification(2));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n2_a));
+
+ SyncError sync_error = model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ initial_data,
+ processor());
+
+ EXPECT_TRUE(sync_error.IsSet());
+ EXPECT_EQ(syncable::APP_NOTIFICATIONS, sync_error.type());
+ EXPECT_EQ(0, processor()->change_list_size());
+}
+
+// Process sync changes when model is empty.
+TEST_F(AppNotificationManagerSyncTest, ProcessSyncChangesEmptyModel) {
+ // We initially have no data.
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ SyncDataList(),
+ processor());
+
+ // Set up a bunch of ADDs.
+ SyncChangeList changes;
+ changes.push_back(CreateSyncChange(
+ SyncChange::ACTION_ADD, CreateNotification(1)));
+ changes.push_back(CreateSyncChange(
+ SyncChange::ACTION_ADD, CreateNotification(2)));
+ changes.push_back(CreateSyncChange(
+ SyncChange::ACTION_ADD, CreateNotification(3)));
+
+ model()->ProcessSyncChanges(FROM_HERE, changes);
+
+ EXPECT_EQ(3U, model()->GetAllSyncData(syncable::APP_NOTIFICATIONS).size());
+ EXPECT_EQ(0, processor()->change_list_size());
+}
+
+// Process sync changes when model is not empty.
+TEST_F(AppNotificationManagerSyncTest, ProcessSyncChangesNonEmptyModel) {
+ AppNotification* n1 = CreateNotification(1);
+ model()->Add(n1);
+ AppNotification* n2 = CreateNotification(2);
+ model()->Add(n2);
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ SyncDataList(),
+ processor());
+
+ // Some adds and some deletes.
+ SyncChangeList changes;
+ changes.push_back(CreateSyncChange(
+ SyncChange::ACTION_ADD, CreateNotification(3)));
+ changes.push_back(CreateSyncChange(
+ SyncChange::ACTION_DELETE, CreateNotification(*n1)));
+ changes.push_back(CreateSyncChange(
+ SyncChange::ACTION_ADD, CreateNotification(4)));
+
+ model()->ProcessSyncChanges(FROM_HERE, changes);
+
+ EXPECT_EQ(3U, model()->GetAllSyncData(syncable::APP_NOTIFICATIONS).size());
+ EXPECT_EQ(2, processor()->change_list_size());
+}
+
+// Process sync changes should return error if model association is not done.
+TEST_F(AppNotificationManagerSyncTest,
+ ProcessSyncChangesErrorModelAssocNotDone) {
+ SyncChangeList changes;
+
+ SyncError sync_error = model()->ProcessSyncChanges(FROM_HERE, changes);
+ EXPECT_TRUE(sync_error.IsSet());
+ EXPECT_EQ(syncable::APP_NOTIFICATIONS, sync_error.type());
+ EXPECT_EQ(0, processor()->change_list_size());
+}
+
+// Stop syncing sets state correctly.
+TEST_F(AppNotificationManagerSyncTest, StopSyncing) {
+ EXPECT_FALSE(model()->sync_processor_);
+ EXPECT_FALSE(model()->models_associated_);
+
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ SyncDataList(),
+ processor());
+
+ EXPECT_TRUE(model()->sync_processor_);
+ EXPECT_TRUE(model()->models_associated_);
+
+ model()->StopSyncing(syncable::APP_NOTIFICATIONS);
+ EXPECT_FALSE(model()->sync_processor_);
+ EXPECT_FALSE(model()->models_associated_);
+}
+
+// Adds get pushed to sync but local only are skipped.
+TEST_F(AppNotificationManagerSyncTest, AddsGetsSynced) {
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ SyncDataList(),
+ processor());
+
+ AppNotification* n1 = CreateNotification(1);
+ model()->Add(n1);
+ AppNotification* n2 = CreateNotification(2);
+ model()->Add(n2);
+ AppNotification* n3 = CreateNotification(true, 2);
+ model()->Add(n3);
+
+ EXPECT_EQ(2, processor()->change_list_size());
+ EXPECT_TRUE(processor()->ContainsGuid(n1->guid()));
+ SyncChange c1 = processor()->GetChangeByGuid(n1->guid());
+ AssertSyncChange(c1, SyncChange::ACTION_ADD, *n1);
+ SyncChange c2 = processor()->GetChangeByGuid(n2->guid());
+ AssertSyncChange(c2, SyncChange::ACTION_ADD, *n2);
+}
+
+// Clear all gets pushed to sync.
+TEST_F(AppNotificationManagerSyncTest, ClearAllGetsSynced) {
+ const std::string& ext_id = "e1";
+ scoped_ptr<AppNotification> n1(CreateNotification(false, 1, ext_id));
+ scoped_ptr<AppNotification> n2(CreateNotification(false, 2, ext_id));
+ scoped_ptr<AppNotification> n3(CreateNotification(false, 3, ext_id));
+ scoped_ptr<AppNotification> n4(CreateNotification(4));
+
+ SyncDataList initial_data;
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n1));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n2));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n3));
+ initial_data.push_back(
+ AppNotificationManager::CreateSyncDataFromNotification(*n4));
+ model()->MergeDataAndStartSyncing(
+ syncable::APP_NOTIFICATIONS,
+ initial_data,
+ processor());
+
+ model()->ClearAll(ext_id);
+
+ EXPECT_EQ(3, processor()->change_list_size());
+ EXPECT_TRUE(processor()->ContainsGuid(n1->guid()));
+ SyncChange c1 = processor()->GetChangeByGuid(n1->guid());
+ AssertSyncChange(c1, SyncChange::ACTION_DELETE, *n1);
+ SyncChange c2 = processor()->GetChangeByGuid(n2->guid());
+ AssertSyncChange(c2, SyncChange::ACTION_DELETE, *n2);
+ SyncChange c3 = processor()->GetChangeByGuid(n3->guid());
+ AssertSyncChange(c3, SyncChange::ACTION_DELETE, *n3);
+}
diff --git a/chrome/browser/extensions/app_notification_manager_unittest.cc b/chrome/browser/extensions/app_notification_manager_unittest.cc
index 37aa6c6..cdc2ea0 100644
--- a/chrome/browser/extensions/app_notification_manager_unittest.cc
+++ b/chrome/browser/extensions/app_notification_manager_unittest.cc
@@ -71,14 +71,18 @@ class AppNotificationManagerTest : public testing::Test {
TEST_F(AppNotificationManagerTest, Simple) {
std::string id = extension_test_util::MakeId("whatever");
AppNotificationList list;
- util::AddNotifications(&list, 2, "foo");
- util::AddCopiesFromList(mgr_.get(), id, list);
+ util::AddNotifications(&list, id, 2, "foo");
+ EXPECT_TRUE(util::AddCopiesFromList(mgr_.get(), list));
// Cause |mgr_| to be recreated, re-reading from its storage.
InitializeManager();
const AppNotification* tmp = mgr_->GetLast(id);
- EXPECT_TRUE(tmp && list[1]->Equals(*tmp));
+ ASSERT_TRUE(tmp);
+ EXPECT_EQ(list[1]->guid(), tmp->guid());
+ EXPECT_EQ(list[1]->extension_id(), tmp->extension_id());
+ EXPECT_EQ(list[1]->is_local(), tmp->is_local());
+ EXPECT_TRUE(list[1]->Equals(*tmp));
const AppNotificationList* tmp_list = mgr_->GetAll(id);
ASSERT_TRUE(tmp_list != NULL);
util::ExpectListsEqual(list, *tmp_list);
@@ -101,10 +105,10 @@ TEST_F(AppNotificationManagerTest, MAYBE_ExtensionUninstall) {
std::string id2 = extension_test_util::MakeId("id2");
AppNotificationList list1;
AppNotificationList list2;
- util::AddNotifications(&list1, 5, "foo1");
- util::AddNotifications(&list2, 3, "foo2");
- util::AddCopiesFromList(mgr_.get(), id1, list1);
- util::AddCopiesFromList(mgr_.get(), id2, list2);
+ util::AddNotifications(&list1, id1, 5, "foo1");
+ util::AddNotifications(&list2, id2, 3, "foo2");
+ util::AddCopiesFromList(mgr_.get(), list1);
+ util::AddCopiesFromList(mgr_.get(), list2);
util::ExpectListsEqual(list1, *mgr_->GetAll(id1));
util::ExpectListsEqual(list2, *mgr_->GetAll(id2));
diff --git a/chrome/browser/extensions/app_notification_storage_unittest.cc b/chrome/browser/extensions/app_notification_storage_unittest.cc
index 84b836f..2fcb251 100644
--- a/chrome/browser/extensions/app_notification_storage_unittest.cc
+++ b/chrome/browser/extensions/app_notification_storage_unittest.cc
@@ -62,7 +62,7 @@ TEST_F(AppNotificationStorageTest, Basics) {
// Add some items.
AppNotificationList list;
- util::AddNotifications(&list, 2, "whatever");
+ util::AddNotifications(&list, id1, 2, "whatever");
EXPECT_TRUE(storage_->Set(id1, list));
EXPECT_TRUE(DatabaseExistsOnDisk());
@@ -82,12 +82,12 @@ TEST_F(AppNotificationStorageTest, MultipleExtensions) {
// Add items for id1.
AppNotificationList list1;
- util::AddNotifications(&list1, 2, "one");
+ util::AddNotifications(&list1, id1, 2, "one");
EXPECT_TRUE(storage_->Set(id1, list1));
// Add items for id2.
AppNotificationList list2;
- util::AddNotifications(&list2, 3, "two");
+ util::AddNotifications(&list2, id2, 3, "two");
EXPECT_TRUE(storage_->Set(id2, list2));
// Verify the items are present
@@ -124,8 +124,8 @@ TEST_F(AppNotificationStorageTest, ReplaceExisting) {
std::string id = extension_test_util::MakeId("1");
AppNotificationList list1;
AppNotificationList list2;
- util::AddNotifications(&list1, 5, "one");
- util::AddNotifications(&list2, 7, "two");
+ util::AddNotifications(&list1, id, 5, "one");
+ util::AddNotifications(&list2, id, 7, "two");
// Put list1 in, then replace with list2 and verify we get list2 back.
EXPECT_TRUE(storage_->Set(id, list1));
diff --git a/chrome/browser/extensions/app_notification_test_util.cc b/chrome/browser/extensions/app_notification_test_util.cc
index a148e50..acef4e1 100644
--- a/chrome/browser/extensions/app_notification_test_util.cc
+++ b/chrome/browser/extensions/app_notification_test_util.cc
@@ -21,12 +21,15 @@ void ExpectListsEqual(const AppNotificationList& one,
}
void AddNotifications(AppNotificationList* list,
+ const std::string& extension_id,
int count,
- std::string prefix) {
+ const std::string& prefix) {
for (int i = 0; i < count; i++) {
+ std::string guid = prefix + "_guid_" + IntToString(i);
std::string title = prefix + "_title_" + IntToString(i);
std::string body = prefix + "_body_" + IntToString(i);
- AppNotification* item = new AppNotification(title, body);
+ AppNotification* item = new AppNotification(
+ true, guid, extension_id, title, body);
if (i % 2 == 0) {
item->set_link_url(GURL("http://www.example.com/" + prefix));
item->set_link_text(prefix + "_link_" + IntToString(i));
@@ -36,20 +39,23 @@ void AddNotifications(AppNotificationList* list,
}
AppNotification* CopyAppNotification(const AppNotification& source) {
- AppNotification* copy = new AppNotification(source.title(), source.body());
+ AppNotification* copy = new AppNotification(
+ source.is_local(), source.guid(), source.extension_id(),
+ source.title(), source.body());
copy->set_link_url(source.link_url());
copy->set_link_text(source.link_text());
return copy;
}
-void AddCopiesFromList(AppNotificationManager* manager,
- const std::string& extension_id,
+bool AddCopiesFromList(AppNotificationManager* manager,
const AppNotificationList& list) {
+ bool result = true;
for (AppNotificationList::const_iterator i = list.begin();
i != list.end();
++i) {
- manager->Add(extension_id, CopyAppNotification(*(i->get())));
+ result = result && manager->Add(CopyAppNotification(*(i->get())));
}
+ return result;
}
} // namespace app_notification_test_util
diff --git a/chrome/browser/extensions/app_notification_test_util.h b/chrome/browser/extensions/app_notification_test_util.h
index 0f38e5a..d17d0b6 100644
--- a/chrome/browser/extensions/app_notification_test_util.h
+++ b/chrome/browser/extensions/app_notification_test_util.h
@@ -20,16 +20,15 @@ void ExpectListsEqual(const AppNotificationList& one,
// Helper for inserting |count| dummy notifications with |prefix| in their
// title and body into |list|.
void AddNotifications(AppNotificationList* list,
+ const std::string& extension_id,
int count,
- std::string prefix);
+ const std::string& prefix);
// Copy the contents of |source| into a new object.
AppNotification* CopyAppNotification(const AppNotification& source);
-// Adds a copy of each item in |list| to |manager| using |extension_id| as the
-// extension id.
-void AddCopiesFromList(AppNotificationManager* manager,
- const std::string& extension_id,
+// Adds a copy of each item in |list| to |manager|.
+bool AddCopiesFromList(AppNotificationManager* manager,
const AppNotificationList& list);
} // namespace app_notification_test_util
diff --git a/chrome/browser/extensions/extension_app_api.cc b/chrome/browser/extensions/extension_app_api.cc
index d89526c..45e666a 100644
--- a/chrome/browser/extensions/extension_app_api.cc
+++ b/chrome/browser/extensions/extension_app_api.cc
@@ -45,7 +45,8 @@ bool AppNotifyFunction::RunImpl() {
if (details->HasKey(kBodyTextKey))
EXTENSION_FUNCTION_VALIDATE(details->GetString(kBodyTextKey, &body));
- scoped_ptr<AppNotification> item(new AppNotification(title, body));
+ scoped_ptr<AppNotification> item(new AppNotification(
+ true, "", id, title, body));
if (details->HasKey(kLinkUrlKey)) {
std::string link_url;
@@ -68,7 +69,7 @@ bool AppNotifyFunction::RunImpl() {
AppNotificationManager* manager =
profile()->GetExtensionService()->app_notification_manager();
- manager->Add(id, item.release());
+ manager->Add(item.release());
NotificationService::current()->Notify(
chrome::NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 8444fdd..e121b79 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1273,6 +1273,7 @@
'browser/event_disposition.cc',
'browser/event_disposition.h',
'browser/extensions/app_notification_manager_unittest.cc',
+ 'browser/extensions/app_notification_manager_sync_unittest.cc',
'browser/extensions/app_notification_storage_unittest.cc',
'browser/extensions/app_notification_test_util.cc',
'browser/extensions/app_notify_channel_setup_unittest.cc',
diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h
index e6cee1b..4dfcd1b 100644
--- a/chrome/common/chrome_notification_types.h
+++ b/chrome/common/chrome_notification_types.h
@@ -538,6 +538,10 @@ enum NotificationType {
// with the extension id of the app.
NOTIFICATION_APP_NOTIFICATION_STATE_CHANGED,
+ // Finished loading app notification manager.
+ // The source is AppNotificationManager, and the details are NoDetails.
+ NOTIFICATION_APP_NOTIFICATION_MANAGER_LOADED,
+
// Component Updater -------------------------------------------------------
// Sent when the component updater starts doing update checks. If no