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 "components/sync_driver/device_info_sync_service.h"
6
7#include "base/strings/stringprintf.h"
8#include "components/sync_driver/local_device_info_provider.h"
9#include "sync/api/sync_change.h"
10#include "sync/protocol/sync.pb.h"
11#include "sync/util/time.h"
12
13namespace sync_driver {
14
15using syncer::ModelType;
16using syncer::SyncChange;
17using syncer::SyncChangeList;
18using syncer::SyncChangeProcessor;
19using syncer::SyncData;
20using syncer::SyncDataList;
21using syncer::SyncErrorFactory;
22using syncer::SyncMergeResult;
23
24DeviceInfoSyncService::DeviceInfoSyncService(
25    LocalDeviceInfoProvider* local_device_info_provider)
26    : local_device_backup_time_(-1),
27      local_device_info_provider_(local_device_info_provider) {
28  DCHECK(local_device_info_provider);
29}
30
31DeviceInfoSyncService::~DeviceInfoSyncService() {
32}
33
34SyncMergeResult DeviceInfoSyncService::MergeDataAndStartSyncing(
35    ModelType type,
36    const SyncDataList& initial_sync_data,
37    scoped_ptr<SyncChangeProcessor> sync_processor,
38    scoped_ptr<SyncErrorFactory> error_handler) {
39  DCHECK(sync_processor.get());
40  DCHECK(error_handler.get());
41  DCHECK_EQ(type, syncer::DEVICE_INFO);
42
43  DCHECK(all_data_.empty());
44
45  sync_processor_ = sync_processor.Pass();
46  error_handler_ = error_handler.Pass();
47
48  // Initialization should be completed before this type is enabled
49  // and local device info must be available.
50  const DeviceInfo* local_device_info =
51      local_device_info_provider_->GetLocalDeviceInfo();
52  DCHECK(local_device_info != NULL);
53
54  // Indicates whether a local device has been added or updated.
55  // |change_type| defaults to ADD and might be changed to
56  // UPDATE to INVALID down below if the initial data contains
57  // data matching the local device ID.
58  SyncChange::SyncChangeType change_type = SyncChange::ACTION_ADD;
59  size_t num_items_new = 0;
60  size_t num_items_updated = 0;
61
62  // Iterate over all initial sync data and copy it to the cache.
63  for (SyncDataList::const_iterator iter = initial_sync_data.begin();
64       iter != initial_sync_data.end();
65       ++iter) {
66    DCHECK_EQ(syncer::DEVICE_INFO, iter->GetDataType());
67
68    const std::string& id = iter->GetSpecifics().device_info().cache_guid();
69
70    if (id == local_device_info->guid()) {
71      // |initial_sync_data| contains data matching the local device.
72      scoped_ptr<DeviceInfo> synced_local_device_info =
73          make_scoped_ptr(CreateDeviceInfo(*iter));
74
75      // Retrieve local device backup timestamp value from the sync data.
76      bool has_synced_backup_time =
77          iter->GetSpecifics().device_info().has_backup_timestamp();
78      int64 synced_backup_time =
79          has_synced_backup_time
80              ? iter->GetSpecifics().device_info().backup_timestamp()
81              : -1;
82
83      // Overwrite |local_device_backup_time_| with this value if it
84      // hasn't been set yet.
85      if (!has_local_device_backup_time() && has_synced_backup_time) {
86        set_local_device_backup_time(synced_backup_time);
87      }
88
89      // Store the synced device info for the local device only
90      // it is the same as the local info. Otherwise store the local
91      // device info and issue a change further below after finishing
92      // processing the |initial_sync_data|.
93      if (synced_local_device_info->Equals(*local_device_info) &&
94          synced_backup_time == local_device_backup_time()) {
95        change_type = SyncChange::ACTION_INVALID;
96      } else {
97        num_items_updated++;
98        change_type = SyncChange::ACTION_UPDATE;
99        continue;
100      }
101    } else {
102      // A new device that doesn't match the local device.
103      num_items_new++;
104    }
105
106    StoreSyncData(id, *iter);
107  }
108
109  syncer::SyncMergeResult result(type);
110
111  // Add SyncData for the local device if it is new or different than
112  // the synced one, and also add it to the |change_list|.
113  if (change_type != SyncChange::ACTION_INVALID) {
114    SyncData local_data = CreateLocalData(local_device_info);
115    StoreSyncData(local_device_info->guid(), local_data);
116
117    SyncChangeList change_list;
118    change_list.push_back(SyncChange(FROM_HERE, change_type, local_data));
119    result.set_error(
120        sync_processor_->ProcessSyncChanges(FROM_HERE, change_list));
121  }
122
123  result.set_num_items_before_association(1);
124  result.set_num_items_after_association(all_data_.size());
125  result.set_num_items_added(num_items_new);
126  result.set_num_items_modified(num_items_updated);
127  result.set_num_items_deleted(0);
128
129  NotifyObservers();
130
131  return result;
132}
133
134void DeviceInfoSyncService::StopSyncing(syncer::ModelType type) {
135  all_data_.clear();
136  sync_processor_.reset();
137  error_handler_.reset();
138  clear_local_device_backup_time();
139}
140
141SyncDataList DeviceInfoSyncService::GetAllSyncData(
142    syncer::ModelType type) const {
143  SyncDataList list;
144
145  for (SyncDataMap::const_iterator iter = all_data_.begin();
146       iter != all_data_.end();
147       ++iter) {
148    list.push_back(iter->second);
149  }
150
151  return list;
152}
153
154syncer::SyncError DeviceInfoSyncService::ProcessSyncChanges(
155    const tracked_objects::Location& from_here,
156    const SyncChangeList& change_list) {
157  syncer::SyncError error;
158
159  DCHECK(local_device_info_provider_->GetLocalDeviceInfo());
160  const std::string& local_device_id =
161      local_device_info_provider_->GetLocalDeviceInfo()->guid();
162
163  bool has_changes = false;
164
165  // Iterate over all chanages and merge entries.
166  for (SyncChangeList::const_iterator iter = change_list.begin();
167       iter != change_list.end();
168       ++iter) {
169    const SyncData& sync_data = iter->sync_data();
170    DCHECK_EQ(syncer::DEVICE_INFO, sync_data.GetDataType());
171
172    const std::string& client_id =
173        sync_data.GetSpecifics().device_info().cache_guid();
174    // Ignore device info matching the local device.
175    if (local_device_id == client_id) {
176      DVLOG(1) << "Ignoring sync changes for the local DEVICE_INFO";
177      continue;
178    }
179
180    if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
181      has_changes = true;
182      DeleteSyncData(client_id);
183    } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE ||
184               iter->change_type() == syncer::SyncChange::ACTION_ADD) {
185      has_changes = true;
186      StoreSyncData(client_id, sync_data);
187    } else {
188      error.Reset(FROM_HERE, "Invalid action received.", syncer::DEVICE_INFO);
189    }
190  }
191
192  if (has_changes) {
193    NotifyObservers();
194  }
195
196  return error;
197}
198
199scoped_ptr<DeviceInfo> DeviceInfoSyncService::GetDeviceInfo(
200    const std::string& client_id) const {
201  SyncDataMap::const_iterator iter = all_data_.find(client_id);
202  if (iter == all_data_.end()) {
203    return scoped_ptr<DeviceInfo>();
204  }
205
206  return make_scoped_ptr(CreateDeviceInfo(iter->second));
207}
208
209ScopedVector<DeviceInfo> DeviceInfoSyncService::GetAllDeviceInfo() const {
210  ScopedVector<DeviceInfo> list;
211
212  for (SyncDataMap::const_iterator iter = all_data_.begin();
213       iter != all_data_.end();
214       ++iter) {
215    list.push_back(CreateDeviceInfo(iter->second));
216  }
217
218  return list.Pass();
219}
220
221void DeviceInfoSyncService::AddObserver(Observer* observer) {
222  observers_.AddObserver(observer);
223}
224
225void DeviceInfoSyncService::RemoveObserver(Observer* observer) {
226  observers_.RemoveObserver(observer);
227}
228
229void DeviceInfoSyncService::NotifyObservers() {
230  FOR_EACH_OBSERVER(Observer, observers_, OnDeviceInfoChange());
231}
232
233void DeviceInfoSyncService::UpdateLocalDeviceBackupTime(
234    base::Time backup_time) {
235  set_local_device_backup_time(syncer::TimeToProtoTime(backup_time));
236
237  if (sync_processor_.get()) {
238    // Local device info must be available in advance
239    DCHECK(local_device_info_provider_->GetLocalDeviceInfo());
240    const std::string& local_id =
241        local_device_info_provider_->GetLocalDeviceInfo()->guid();
242
243    SyncDataMap::iterator iter = all_data_.find(local_id);
244    DCHECK(iter != all_data_.end());
245
246    syncer::SyncData& data = iter->second;
247    if (UpdateBackupTime(&data)) {
248      // Local device backup time has changed.
249      // Push changes to the server via the |sync_processor_|.
250      SyncChangeList change_list;
251      change_list.push_back(SyncChange(
252          FROM_HERE, syncer::SyncChange::ACTION_UPDATE, data));
253      sync_processor_->ProcessSyncChanges(FROM_HERE, change_list);
254    }
255  }
256}
257
258bool DeviceInfoSyncService::UpdateBackupTime(syncer::SyncData* sync_data) {
259  DCHECK(has_local_device_backup_time());
260  DCHECK(sync_data->GetSpecifics().has_device_info());
261  const sync_pb::DeviceInfoSpecifics& source_specifics =
262      sync_data->GetSpecifics().device_info();
263
264  if (!source_specifics.has_backup_timestamp() ||
265      source_specifics.backup_timestamp() != local_device_backup_time()) {
266    sync_pb::EntitySpecifics entity(sync_data->GetSpecifics());
267    entity.mutable_device_info()->set_backup_timestamp(
268        local_device_backup_time());
269    *sync_data = CreateLocalData(entity);
270
271    return true;
272  }
273
274  return false;
275}
276
277base::Time DeviceInfoSyncService::GetLocalDeviceBackupTime() const {
278  return has_local_device_backup_time()
279             ? syncer::ProtoTimeToTime(local_device_backup_time())
280             : base::Time();
281}
282
283SyncData DeviceInfoSyncService::CreateLocalData(const DeviceInfo* info) {
284  sync_pb::EntitySpecifics entity;
285  sync_pb::DeviceInfoSpecifics& specifics = *entity.mutable_device_info();
286
287  specifics.set_cache_guid(info->guid());
288  specifics.set_client_name(info->client_name());
289  specifics.set_chrome_version(info->chrome_version());
290  specifics.set_sync_user_agent(info->sync_user_agent());
291  specifics.set_device_type(info->device_type());
292  specifics.set_signin_scoped_device_id(info->signin_scoped_device_id());
293
294  if (has_local_device_backup_time()) {
295    specifics.set_backup_timestamp(local_device_backup_time());
296  }
297
298  return CreateLocalData(entity);
299}
300
301SyncData DeviceInfoSyncService::CreateLocalData(
302    const sync_pb::EntitySpecifics& entity) {
303  const sync_pb::DeviceInfoSpecifics& specifics = entity.device_info();
304
305  std::string local_device_tag =
306      base::StringPrintf("DeviceInfo_%s", specifics.cache_guid().c_str());
307
308  return SyncData::CreateLocalData(
309      local_device_tag, specifics.client_name(), entity);
310}
311
312DeviceInfo* DeviceInfoSyncService::CreateDeviceInfo(
313    const syncer::SyncData sync_data) {
314  const sync_pb::DeviceInfoSpecifics& specifics =
315      sync_data.GetSpecifics().device_info();
316
317  return new DeviceInfo(specifics.cache_guid(),
318                        specifics.client_name(),
319                        specifics.chrome_version(),
320                        specifics.sync_user_agent(),
321                        specifics.device_type(),
322                        specifics.signin_scoped_device_id());
323}
324
325void DeviceInfoSyncService::StoreSyncData(const std::string& client_id,
326                                          const SyncData& sync_data) {
327  DVLOG(1) << "Storing DEVICE_INFO for "
328           << sync_data.GetSpecifics().device_info().client_name()
329           << " with ID " << client_id;
330  all_data_[client_id] = sync_data;
331}
332
333void DeviceInfoSyncService::DeleteSyncData(const std::string& client_id) {
334  SyncDataMap::iterator iter = all_data_.find(client_id);
335  if (iter != all_data_.end()) {
336    DVLOG(1) << "Deleting DEVICE_INFO for "
337             << iter->second.GetSpecifics().device_info().client_name()
338             << " with ID " << client_id;
339    all_data_.erase(iter);
340  }
341}
342
343}  // namespace sync_driver
344