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    const base::Closure& refresh_request)
134    : event_launcher_(event_launcher),
135      refresh_request_(refresh_request) {
136}
137
138SyncedNotificationsShim::~SyncedNotificationsShim() {
139}
140
141syncer::SyncMergeResult SyncedNotificationsShim::MergeDataAndStartSyncing(
142    syncer::ModelType type,
143    const syncer::SyncDataList& initial_sync_data,
144    scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
145    scoped_ptr<syncer::SyncErrorFactory> error_handler) {
146  if (type == syncer::SYNCED_NOTIFICATIONS)
147    notifications_change_processor_ = sync_processor.Pass();
148  else if (type == syncer::SYNCED_NOTIFICATION_APP_INFO)
149    app_info_change_processor_ = sync_processor.Pass();
150  else
151    NOTREACHED();
152
153  // Only wake up the extension if both sync data types are ready.
154  if (notifications_change_processor_ && app_info_change_processor_) {
155    scoped_ptr<Event> event(new Event(
156        synced_notifications_private::OnSyncStartup::kEventName,
157        synced_notifications_private::OnSyncStartup::Create()));
158    event_launcher_.Run(event.Pass());
159  }
160
161  return syncer::SyncMergeResult(type);
162}
163
164void SyncedNotificationsShim::StopSyncing(syncer::ModelType type) {
165  if (type == syncer::SYNCED_NOTIFICATIONS)
166    notifications_change_processor_.reset();
167  else if (type == syncer::SYNCED_NOTIFICATION_APP_INFO)
168    app_info_change_processor_.reset();
169  else
170    NOTREACHED();
171}
172
173syncer::SyncError SyncedNotificationsShim::ProcessSyncChanges(
174    const tracked_objects::Location& from_here,
175    const syncer::SyncChangeList& changes) {
176  std::vector<linked_ptr<synced_notifications_private::SyncChange> > js_changes;
177  for (size_t i = 0; i < changes.size(); ++i)
178    js_changes.push_back(BuildNewJSSyncChange(changes[i]));
179
180  scoped_ptr<base::ListValue> args(
181      synced_notifications_private::OnDataChanges::Create(js_changes));
182  scoped_ptr<Event> event(new Event(
183      synced_notifications_private::OnDataChanges::kEventName, args.Pass()));
184  event_launcher_.Run(event.Pass());
185  return syncer::SyncError();
186}
187
188syncer::SyncDataList SyncedNotificationsShim::GetAllSyncData(
189      syncer::ModelType type) const {
190  NOTIMPLEMENTED();
191  return syncer::SyncDataList();
192}
193
194bool SyncedNotificationsShim::GetInitialData(
195    synced_notifications_private::SyncDataType data_type,
196    std::vector<linked_ptr<synced_notifications_private::SyncData> >*
197        js_data_list) const {
198  if (!IsSyncReady())
199    return false;
200
201  syncer::SyncDataList sync_data_list;
202  if (JSDataTypeToSyncer(data_type) == syncer::SYNCED_NOTIFICATIONS) {
203    sync_data_list = notifications_change_processor_->GetAllSyncData(
204        syncer::SYNCED_NOTIFICATIONS);
205    if (PopulateJSDataListFromSync(sync_data_list, js_data_list))
206      return true;
207  } else if (JSDataTypeToSyncer(data_type) ==
208                 syncer::SYNCED_NOTIFICATION_APP_INFO) {
209    sync_data_list = app_info_change_processor_->GetAllSyncData(
210        syncer::SYNCED_NOTIFICATION_APP_INFO);
211    if (PopulateJSDataListFromSync(sync_data_list, js_data_list))
212      return true;
213  }
214  return false;
215}
216
217bool SyncedNotificationsShim::UpdateNotification(
218    const std::string& changed_notification) {
219  if (!IsSyncReady())
220    return false;
221
222  syncer::SyncChange sync_change;
223  if (!BuildNewSyncUpdate(FROM_HERE, changed_notification, &sync_change))
224    return false;
225  syncer::SyncError error = notifications_change_processor_->ProcessSyncChanges(
226      FROM_HERE,
227      syncer::SyncChangeList(1, sync_change));
228  return !error.IsSet();
229}
230
231bool SyncedNotificationsShim::SetRenderContext(
232    synced_notifications_private::RefreshRequest refresh_request,
233    const std::string& new_context) {
234  if (!IsSyncReady())
235    return false;
236
237  syncer::SyncChangeProcessor::ContextRefreshStatus sync_refresh_status =
238      refresh_request ==
239              synced_notifications_private::REFRESH_REQUEST_REFRESH_NEEDED
240          ? syncer::SyncChangeProcessor::REFRESH_NEEDED
241          : syncer::SyncChangeProcessor::NO_REFRESH;
242  syncer::SyncError error =
243      notifications_change_processor_->UpdateDataTypeContext(
244          syncer::SYNCED_NOTIFICATIONS, sync_refresh_status, new_context);
245
246  if (sync_refresh_status == syncer::SyncChangeProcessor::REFRESH_NEEDED &&
247      !refresh_request_.is_null()) {
248    refresh_request_.Run();
249  }
250
251  return !error.IsSet();
252}
253
254bool SyncedNotificationsShim::IsSyncReady() const {
255  return notifications_change_processor_ && app_info_change_processor_;
256}
257