1// Copyright 2014 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/api/synced_notifications_private/synced_notifications_shim.h"
6
7#include "extensions/browser/event_router.h"
8#include "sync/api/sync_change.h"
9#include "sync/api/sync_data.h"
10#include "sync/api/sync_error_factory.h"
11#include "sync/protocol/sync.pb.h"
12
13using namespace extensions;
14using namespace extensions::api;
15
16namespace {
17
18synced_notifications_private::ChangeType SyncerChangeTypeToJS(
19    syncer::SyncChange::SyncChangeType change_type) {
20  switch (change_type) {
21   case syncer::SyncChange::ACTION_UPDATE:
22     return synced_notifications_private::CHANGE_TYPE_UPDATED;
23   case syncer::SyncChange::ACTION_DELETE:
24     return synced_notifications_private::CHANGE_TYPE_DELETED;
25   case syncer::SyncChange::ACTION_ADD:
26     return synced_notifications_private::CHANGE_TYPE_ADDED;
27   case syncer::SyncChange::ACTION_INVALID:
28     return synced_notifications_private::CHANGE_TYPE_NONE;
29  }
30  NOTREACHED();
31  return synced_notifications_private::CHANGE_TYPE_NONE;
32}
33
34syncer::ModelType JSDataTypeToSyncer(
35    synced_notifications_private::SyncDataType data_type) {
36  switch (data_type) {
37    case synced_notifications_private::SYNC_DATA_TYPE_APP_INFO:
38      return syncer::SYNCED_NOTIFICATION_APP_INFO;
39    case synced_notifications_private::SYNC_DATA_TYPE_SYNCED_NOTIFICATION:
40      return syncer::SYNCED_NOTIFICATIONS;
41    default:
42      NOTREACHED();
43      return syncer::UNSPECIFIED;
44  }
45}
46
47synced_notifications_private::SyncDataType SyncerModelTypeToJS(
48    syncer::ModelType model_type) {
49  switch (model_type) {
50    case syncer::SYNCED_NOTIFICATION_APP_INFO:
51      return synced_notifications_private::SYNC_DATA_TYPE_APP_INFO;
52    case syncer::SYNCED_NOTIFICATIONS:
53      return synced_notifications_private::SYNC_DATA_TYPE_SYNCED_NOTIFICATION;
54    default:
55      NOTREACHED();
56      return synced_notifications_private::SYNC_DATA_TYPE_NONE;
57  }
58}
59
60bool BuildNewSyncUpdate(
61    const tracked_objects::Location& from_here,
62    const std::string& changed_notification,
63    syncer::SyncChange* sync_change) {
64  sync_pb::EntitySpecifics specifics;
65  sync_pb::SyncedNotificationSpecifics* notification_specifics =
66      specifics.mutable_synced_notification();
67  if (!notification_specifics->ParseFromArray(
68          changed_notification.c_str(), changed_notification.size())) {
69    return false;
70  }
71
72  // TODO(synced notifications): pass the tag via the JS API.
73  const std::string& tag =
74      notification_specifics->coalesced_notification().key();
75  syncer::SyncData sync_data =
76      syncer::SyncData::CreateLocalData(tag, tag, specifics);
77  *sync_change = syncer::SyncChange(
78      from_here, syncer::SyncChange::ACTION_UPDATE, sync_data);
79  return true;
80}
81
82linked_ptr<synced_notifications_private::SyncChange> BuildNewJSSyncChange(
83    const syncer::SyncChange& change) {
84  linked_ptr<synced_notifications_private::SyncChange> js_change =
85      make_linked_ptr<synced_notifications_private::SyncChange>(
86          new synced_notifications_private::SyncChange());
87  js_change->change_type = SyncerChangeTypeToJS(change.change_type());
88  js_change->data.datatype =
89      SyncerModelTypeToJS(change.sync_data().GetDataType());
90  if (change.sync_data().GetDataType() == syncer::SYNCED_NOTIFICATIONS) {
91    const sync_pb::SyncedNotificationSpecifics& specifics =
92        change.sync_data().GetSpecifics().synced_notification();
93    js_change->data.data_item = specifics.SerializeAsString();
94  } else {
95    DCHECK_EQ(change.sync_data().GetDataType(),
96              syncer::SYNCED_NOTIFICATION_APP_INFO);
97    const sync_pb::SyncedNotificationAppInfoSpecifics& specifics =
98        change.sync_data().GetSpecifics().synced_notification_app_info();
99    js_change->data.data_item = specifics.SerializeAsString();
100  }
101  return js_change;
102}
103
104bool PopulateJSDataListFromSync(
105    const syncer::SyncDataList& sync_data_list,
106    std::vector<linked_ptr<synced_notifications_private::SyncData> >*
107        js_data_list) {
108  for (size_t i = 0; i < sync_data_list.size(); ++i) {
109    linked_ptr<synced_notifications_private::SyncData> js_data(
110        new synced_notifications_private::SyncData());
111    syncer::ModelType data_type = sync_data_list[i].GetDataType();
112    js_data->datatype = SyncerModelTypeToJS(data_type);
113    if (data_type == syncer::SYNCED_NOTIFICATIONS) {
114      const sync_pb::SyncedNotificationSpecifics& specifics =
115          sync_data_list[i].GetSpecifics().synced_notification();
116      js_data->data_item = specifics.SerializeAsString();
117    } else if (data_type == syncer::SYNCED_NOTIFICATION_APP_INFO) {
118      const sync_pb::SyncedNotificationAppInfoSpecifics& specifics =
119          sync_data_list[i].GetSpecifics().synced_notification_app_info();
120      js_data->data_item = specifics.SerializeAsString();
121    } else {
122      return false;
123    }
124    js_data_list->push_back(js_data);
125  }
126  return true;
127}
128
129}  // namespace
130
131SyncedNotificationsShim::SyncedNotificationsShim(
132    const EventLauncher& event_launcher)
133    : event_launcher_(event_launcher) {
134}
135
136SyncedNotificationsShim::~SyncedNotificationsShim() {
137}
138
139syncer::SyncMergeResult SyncedNotificationsShim::MergeDataAndStartSyncing(
140    syncer::ModelType type,
141    const syncer::SyncDataList& initial_sync_data,
142    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
143    scoped_ptr<syncer::SyncErrorFactory> error_handler) {
144  if (type == syncer::SYNCED_NOTIFICATIONS)
145    notifications_change_processor_ = sync_processor.Pass();
146  else if (type == syncer::SYNCED_NOTIFICATION_APP_INFO)
147    app_info_change_processor_ = sync_processor.Pass();
148  else
149    NOTREACHED();
150
151  // Only wake up the extension if both sync data types are ready.
152  if (notifications_change_processor_ && app_info_change_processor_) {
153    scoped_ptr<Event> event(new Event(
154        synced_notifications_private::OnSyncStartup::kEventName,
155        synced_notifications_private::OnSyncStartup::Create()));
156    event_launcher_.Run(event.Pass());
157  }
158
159  return syncer::SyncMergeResult(type);
160}
161
162void SyncedNotificationsShim::StopSyncing(syncer::ModelType type) {
163  if (type == syncer::SYNCED_NOTIFICATIONS)
164    notifications_change_processor_.reset();
165  else if (type == syncer::SYNCED_NOTIFICATION_APP_INFO)
166    app_info_change_processor_.reset();
167  else
168    NOTREACHED();
169}
170
171syncer::SyncError SyncedNotificationsShim::ProcessSyncChanges(
172    const tracked_objects::Location& from_here,
173    const syncer::SyncChangeList& changes) {
174  std::vector<linked_ptr<synced_notifications_private::SyncChange> > js_changes;
175  for (size_t i = 0; i < changes.size(); ++i)
176    js_changes.push_back(BuildNewJSSyncChange(changes[i]));
177
178  scoped_ptr<base::ListValue> args(
179      synced_notifications_private::OnDataChanges::Create(js_changes));
180  scoped_ptr<Event> event(new Event(
181      synced_notifications_private::OnDataChanges::kEventName, args.Pass()));
182  event_launcher_.Run(event.Pass());
183  return syncer::SyncError();
184}
185
186syncer::SyncDataList SyncedNotificationsShim::GetAllSyncData(
187      syncer::ModelType type) const {
188  NOTIMPLEMENTED();
189  return syncer::SyncDataList();
190}
191
192bool SyncedNotificationsShim::GetInitialData(
193    synced_notifications_private::SyncDataType data_type,
194    std::vector<linked_ptr<synced_notifications_private::SyncData> >*
195        js_data_list) const {
196  if (!IsSyncReady())
197    return false;
198
199  syncer::SyncDataList sync_data_list;
200  if (JSDataTypeToSyncer(data_type) == syncer::SYNCED_NOTIFICATIONS) {
201    sync_data_list = notifications_change_processor_->GetAllSyncData(
202        syncer::SYNCED_NOTIFICATIONS);
203    if (PopulateJSDataListFromSync(sync_data_list, js_data_list))
204      return true;
205  } else if (JSDataTypeToSyncer(data_type) ==
206                 syncer::SYNCED_NOTIFICATION_APP_INFO) {
207    sync_data_list = app_info_change_processor_->GetAllSyncData(
208        syncer::SYNCED_NOTIFICATION_APP_INFO);
209    if (PopulateJSDataListFromSync(sync_data_list, js_data_list))
210      return true;
211  }
212  return false;
213}
214
215bool SyncedNotificationsShim::UpdateNotification(
216    const std::string& changed_notification) {
217  if (!IsSyncReady())
218    return false;
219
220  syncer::SyncChange sync_change;
221  if (!BuildNewSyncUpdate(FROM_HERE, changed_notification, &sync_change))
222    return false;
223  syncer::SyncError error = notifications_change_processor_->ProcessSyncChanges(
224      FROM_HERE,
225      syncer::SyncChangeList(1, sync_change));
226  return !error.IsSet();
227}
228
229bool SyncedNotificationsShim::SetRenderContext(
230    synced_notifications_private::RefreshRequest refresh_request,
231    const std::string& new_context) {
232  if (!IsSyncReady())
233    return false;
234
235  syncer::SyncChangeProcessor::ContextRefreshStatus sync_refresh_status =
236      refresh_request ==
237              synced_notifications_private::REFRESH_REQUEST_REFRESH_NEEDED
238          ? syncer::SyncChangeProcessor::REFRESH_NEEDED
239          : syncer::SyncChangeProcessor::NO_REFRESH;
240  syncer::SyncError error =
241      notifications_change_processor_->UpdateDataTypeContext(
242          syncer::SYNCED_NOTIFICATIONS, sync_refresh_status, new_context);
243  return !error.IsSet();
244}
245
246bool SyncedNotificationsShim::IsSyncReady() const {
247  return notifications_change_processor_ && app_info_change_processor_;
248}
249