1 // Copyright 2017 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/chromeos/printing/printers_sync_bridge.h"
6
7 #include <set>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/metrics/histogram_functions.h"
12 #include "base/stl_util.h"
13 #include "base/task/post_task.h"
14 #include "chrome/browser/chromeos/printing/specifics_translation.h"
15 #include "chromeos/printing/printer_configuration.h"
16 #include "components/sync/base/report_unrecoverable_error.h"
17 #include "components/sync/model/model_type_change_processor.h"
18 #include "components/sync/model/mutable_data_batch.h"
19 #include "components/sync/model_impl/client_tag_based_model_type_processor.h"
20 #include "components/sync/protocol/model_type_state.pb.h"
21 #include "components/sync/protocol/sync.pb.h"
22
23 namespace chromeos {
24
25 namespace {
26
27 using syncer::ClientTagBasedModelTypeProcessor;
28 using syncer::ConflictResolution;
29 using syncer::EntityChange;
30 using syncer::EntityChangeList;
31 using syncer::EntityData;
32 using syncer::MetadataChangeList;
33 using syncer::ModelTypeChangeProcessor;
34 using syncer::ModelTypeStore;
35
CopyToEntityData(const sync_pb::PrinterSpecifics & specifics)36 std::unique_ptr<EntityData> CopyToEntityData(
37 const sync_pb::PrinterSpecifics& specifics) {
38 auto entity_data = std::make_unique<EntityData>();
39 *entity_data->specifics.mutable_printer() = specifics;
40 entity_data->name =
41 specifics.display_name().empty() ? "PRINTER" : specifics.display_name();
42 return entity_data;
43 }
44
45 // Computes the make_and_model field for old |specifics| where it is missing.
46 // Returns true if an update was made. make_and_model is computed from the
47 // manufacturer and model strings.
MigrateMakeAndModel(sync_pb::PrinterSpecifics * specifics)48 bool MigrateMakeAndModel(sync_pb::PrinterSpecifics* specifics) {
49 if (specifics->has_make_and_model()) {
50 base::UmaHistogramBoolean("Printing.CUPS.MigratedMakeAndModel", false);
51 return false;
52 }
53
54 specifics->set_make_and_model(
55 chromeos::MakeAndModel(specifics->manufacturer(), specifics->model()));
56 base::UmaHistogramBoolean("Printing.CUPS.MigratedMakeAndModel", true);
57 return true;
58 }
59
60 // If |specifics|'s PPD reference has both autoconf and another option selected,
61 // we strip the autoconf flag and return true, false otherwise.
ResolveInvalidPpdReference(sync_pb::PrinterSpecifics * specifics)62 bool ResolveInvalidPpdReference(sync_pb::PrinterSpecifics* specifics) {
63 auto* ppd_ref = specifics->mutable_ppd_reference();
64
65 if (!ppd_ref->autoconf())
66 return false;
67
68 if (!ppd_ref->has_user_supplied_ppd_url() &&
69 !ppd_ref->has_effective_make_and_model()) {
70 return false;
71 }
72
73 ppd_ref->clear_autoconf();
74 return true;
75 }
76
77 } // namespace
78
79 // Delegate class which helps to manage the ModelTypeStore.
80 class PrintersSyncBridge::StoreProxy {
81 public:
StoreProxy(PrintersSyncBridge * owner,syncer::OnceModelTypeStoreFactory callback)82 StoreProxy(PrintersSyncBridge* owner,
83 syncer::OnceModelTypeStoreFactory callback)
84 : owner_(owner) {
85 std::move(callback).Run(syncer::PRINTERS,
86 base::BindOnce(&StoreProxy::OnStoreCreated,
87 weak_ptr_factory_.GetWeakPtr()));
88 }
89
90 // Returns true if the store has been initialized.
Ready()91 bool Ready() { return store_.get() != nullptr; }
92
93 // Returns a new WriteBatch.
CreateWriteBatch()94 std::unique_ptr<ModelTypeStore::WriteBatch> CreateWriteBatch() {
95 DCHECK(store_);
96 return store_->CreateWriteBatch();
97 }
98
99 // Commits writes to the database and updates metadata.
Commit(std::unique_ptr<ModelTypeStore::WriteBatch> batch)100 void Commit(std::unique_ptr<ModelTypeStore::WriteBatch> batch) {
101 DCHECK(store_);
102 store_->CommitWriteBatch(
103 std::move(batch),
104 base::BindOnce(&StoreProxy::OnCommit, weak_ptr_factory_.GetWeakPtr()));
105 owner_->NotifyPrintersUpdated();
106 }
107
108 private:
109 // Callback for ModelTypeStore initialization.
OnStoreCreated(const base::Optional<syncer::ModelError> & error,std::unique_ptr<ModelTypeStore> store)110 void OnStoreCreated(const base::Optional<syncer::ModelError>& error,
111 std::unique_ptr<ModelTypeStore> store) {
112 if (error) {
113 owner_->change_processor()->ReportError(*error);
114 return;
115 }
116
117 store_ = std::move(store);
118 store_->ReadAllData(base::BindOnce(&StoreProxy::OnReadAllData,
119 weak_ptr_factory_.GetWeakPtr()));
120 }
121
OnReadAllData(const base::Optional<syncer::ModelError> & error,std::unique_ptr<ModelTypeStore::RecordList> record_list)122 void OnReadAllData(const base::Optional<syncer::ModelError>& error,
123 std::unique_ptr<ModelTypeStore::RecordList> record_list) {
124 if (error) {
125 owner_->change_processor()->ReportError(*error);
126 return;
127 }
128
129 bool parse_error = false;
130 {
131 base::AutoLock lock(owner_->data_lock_);
132 for (const ModelTypeStore::Record& r : *record_list) {
133 auto specifics = std::make_unique<sync_pb::PrinterSpecifics>();
134 if (specifics->ParseFromString(r.value)) {
135 auto& dest = owner_->all_data_[specifics->id()];
136 dest = std::move(specifics);
137 } else {
138 parse_error = true;
139 }
140 }
141 }
142 owner_->NotifyPrintersUpdated();
143
144 if (parse_error) {
145 owner_->change_processor()->ReportError(
146 {FROM_HERE, "Failed to deserialize all specifics."});
147 return;
148 }
149
150 // Data loaded. Load metadata.
151 store_->ReadAllMetadata(base::BindOnce(&StoreProxy::OnReadAllMetadata,
152 weak_ptr_factory_.GetWeakPtr()));
153 }
154
155 // Callback to handle commit errors.
OnCommit(const base::Optional<syncer::ModelError> & error)156 void OnCommit(const base::Optional<syncer::ModelError>& error) {
157 if (error) {
158 LOG(WARNING) << "Failed to commit operation to store";
159 owner_->change_processor()->ReportError(*error);
160 return;
161 }
162 }
163
OnReadAllMetadata(const base::Optional<syncer::ModelError> & error,std::unique_ptr<syncer::MetadataBatch> metadata_batch)164 void OnReadAllMetadata(
165 const base::Optional<syncer::ModelError>& error,
166 std::unique_ptr<syncer::MetadataBatch> metadata_batch) {
167 if (error) {
168 owner_->change_processor()->ReportError(*error);
169 return;
170 }
171
172 owner_->change_processor()->ModelReadyToSync(std::move(metadata_batch));
173 }
174
175 PrintersSyncBridge* owner_;
176
177 std::unique_ptr<ModelTypeStore> store_;
178 base::WeakPtrFactory<StoreProxy> weak_ptr_factory_{this};
179 };
180
PrintersSyncBridge(syncer::OnceModelTypeStoreFactory callback,base::RepeatingClosure error_callback)181 PrintersSyncBridge::PrintersSyncBridge(
182 syncer::OnceModelTypeStoreFactory callback,
183 base::RepeatingClosure error_callback)
184 : ModelTypeSyncBridge(std::make_unique<ClientTagBasedModelTypeProcessor>(
185 syncer::PRINTERS,
186 std::move(error_callback))),
187 store_delegate_(std::make_unique<StoreProxy>(this, std::move(callback))),
188 observers_(new base::ObserverListThreadSafe<Observer>()) {}
189
190 PrintersSyncBridge::~PrintersSyncBridge() = default;
191
192 std::unique_ptr<MetadataChangeList>
CreateMetadataChangeList()193 PrintersSyncBridge::CreateMetadataChangeList() {
194 return ModelTypeStore::WriteBatch::CreateMetadataChangeList();
195 }
196
MergeSyncData(std::unique_ptr<MetadataChangeList> metadata_change_list,syncer::EntityChangeList entity_data)197 base::Optional<syncer::ModelError> PrintersSyncBridge::MergeSyncData(
198 std::unique_ptr<MetadataChangeList> metadata_change_list,
199 syncer::EntityChangeList entity_data) {
200 DCHECK(change_processor()->IsTrackingMetadata());
201
202 std::unique_ptr<ModelTypeStore::WriteBatch> batch =
203 store_delegate_->CreateWriteBatch();
204 std::set<std::string> sync_entity_ids;
205 {
206 base::AutoLock lock(data_lock_);
207 // Store the new data locally.
208 for (const auto& change : entity_data) {
209 const sync_pb::PrinterSpecifics& specifics =
210 change->data().specifics.printer();
211
212 DCHECK_EQ(change->storage_key(), specifics.id());
213 sync_entity_ids.insert(specifics.id());
214
215 // Write the update to local storage even if we already have it.
216 StoreSpecifics(std::make_unique<sync_pb::PrinterSpecifics>(specifics),
217 batch.get());
218 }
219
220 // Inform the change processor of the new local entities and generate
221 // appropriate metadata.
222 for (const auto& entry : all_data_) {
223 const std::string& local_entity_id = entry.first;
224
225 // TODO(crbug.com/737809): Remove when all data is expected to have been
226 // migrated.
227 bool migrated = MigrateMakeAndModel(entry.second.get());
228
229 // TODO(crbug.com/987869): Remove when all data is expected to have been
230 // resolved.
231 bool resolved = ResolveInvalidPpdReference(entry.second.get());
232
233 if (migrated || resolved ||
234 !base::Contains(sync_entity_ids, local_entity_id)) {
235 // Only local objects which were not updated are uploaded. Objects for
236 // which there was a remote copy are overwritten.
237 change_processor()->Put(local_entity_id,
238 CopyToEntityData(*entry.second),
239 metadata_change_list.get());
240 }
241 }
242 }
243
244 NotifyPrintersUpdated();
245 batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
246 store_delegate_->Commit(std::move(batch));
247 return {};
248 }
249
ApplySyncChanges(std::unique_ptr<MetadataChangeList> metadata_change_list,EntityChangeList entity_changes)250 base::Optional<syncer::ModelError> PrintersSyncBridge::ApplySyncChanges(
251 std::unique_ptr<MetadataChangeList> metadata_change_list,
252 EntityChangeList entity_changes) {
253 std::unique_ptr<ModelTypeStore::WriteBatch> batch =
254 store_delegate_->CreateWriteBatch();
255 {
256 base::AutoLock lock(data_lock_);
257 // For all the entities from the server, apply changes.
258 for (const std::unique_ptr<EntityChange>& change : entity_changes) {
259 // We register the entity's storage key as our printer ids since they're
260 // globally unique.
261 const std::string& id = change->storage_key();
262 if (change->type() == EntityChange::ACTION_DELETE) {
263 // Server says delete, try to remove locally.
264 DeleteSpecifics(id, batch.get());
265 } else {
266 // Server says update, overwrite whatever is local. Conflict resolution
267 // guarantees that this will be the newest version of the object.
268 const sync_pb::PrinterSpecifics& specifics =
269 change->data().specifics.printer();
270 DCHECK_EQ(id, specifics.id());
271 StoreSpecifics(std::make_unique<sync_pb::PrinterSpecifics>(specifics),
272 batch.get());
273 }
274 }
275 }
276
277 NotifyPrintersUpdated();
278 // Update the local database with metadata for the incoming changes.
279 batch->TakeMetadataChangesFrom(std::move(metadata_change_list));
280
281 store_delegate_->Commit(std::move(batch));
282 return {};
283 }
284
GetData(StorageKeyList storage_keys,DataCallback callback)285 void PrintersSyncBridge::GetData(StorageKeyList storage_keys,
286 DataCallback callback) {
287 auto batch = std::make_unique<syncer::MutableDataBatch>();
288 {
289 base::AutoLock lock(data_lock_);
290 for (const auto& key : storage_keys) {
291 auto found = all_data_.find(key);
292 if (found != all_data_.end()) {
293 batch->Put(key, CopyToEntityData(*found->second));
294 }
295 }
296 }
297 std::move(callback).Run(std::move(batch));
298 }
299
GetAllDataForDebugging(DataCallback callback)300 void PrintersSyncBridge::GetAllDataForDebugging(DataCallback callback) {
301 auto batch = std::make_unique<syncer::MutableDataBatch>();
302 {
303 base::AutoLock lock(data_lock_);
304 for (const auto& entry : all_data_) {
305 batch->Put(entry.first, CopyToEntityData(*entry.second));
306 }
307 }
308 std::move(callback).Run(std::move(batch));
309 }
310
GetClientTag(const EntityData & entity_data)311 std::string PrintersSyncBridge::GetClientTag(const EntityData& entity_data) {
312 // Printers were never synced prior to USS so this can match GetStorageKey.
313 return GetStorageKey(entity_data);
314 }
315
GetStorageKey(const EntityData & entity_data)316 std::string PrintersSyncBridge::GetStorageKey(const EntityData& entity_data) {
317 DCHECK(entity_data.specifics.has_printer());
318 return entity_data.specifics.printer().id();
319 }
320
321 // Picks the entity with the most recent updated time as the canonical version.
ResolveConflict(const std::string & storage_key,const EntityData & remote_data) const322 ConflictResolution PrintersSyncBridge::ResolveConflict(
323 const std::string& storage_key,
324 const EntityData& remote_data) const {
325 DCHECK(remote_data.specifics.has_printer());
326
327 auto iter = all_data_.find(storage_key);
328 // If the local printer doesn't exist, it must have been deleted. In this
329 // case, use the remote one.
330 if (iter == all_data_.end()) {
331 return ConflictResolution::kUseRemote;
332 }
333 const sync_pb::PrinterSpecifics& local_printer = *iter->second;
334
335 const sync_pb::PrinterSpecifics& remote_printer =
336 remote_data.specifics.printer();
337
338 if (local_printer.updated_timestamp() > remote_printer.updated_timestamp()) {
339 return ConflictResolution::kUseLocal;
340 }
341
342 return ConflictResolution::kUseRemote;
343 }
344
AddPrinter(std::unique_ptr<sync_pb::PrinterSpecifics> printer)345 void PrintersSyncBridge::AddPrinter(
346 std::unique_ptr<sync_pb::PrinterSpecifics> printer) {
347 {
348 base::AutoLock lock(data_lock_);
349 AddPrinterLocked(std::move(printer));
350 }
351 NotifyPrintersUpdated();
352 }
353
UpdatePrinter(std::unique_ptr<sync_pb::PrinterSpecifics> printer)354 bool PrintersSyncBridge::UpdatePrinter(
355 std::unique_ptr<sync_pb::PrinterSpecifics> printer) {
356 bool res;
357 {
358 base::AutoLock lock(data_lock_);
359 res = UpdatePrinterLocked(std::move(printer));
360 }
361 NotifyPrintersUpdated();
362 return res;
363 }
364
UpdatePrinterLocked(std::unique_ptr<sync_pb::PrinterSpecifics> printer)365 bool PrintersSyncBridge::UpdatePrinterLocked(
366 std::unique_ptr<sync_pb::PrinterSpecifics> printer) {
367 data_lock_.AssertAcquired();
368 DCHECK(printer->has_id());
369 auto iter = all_data_.find(printer->id());
370 if (iter == all_data_.end()) {
371 AddPrinterLocked(std::move(printer));
372 return true;
373 }
374
375 // Modify the printer in-place then notify the change processor.
376 sync_pb::PrinterSpecifics* merged = iter->second.get();
377 MergePrinterToSpecifics(*SpecificsToPrinter(*printer), merged);
378 merged->set_updated_timestamp(base::Time::Now().ToJavaTime());
379 CommitPrinterPut(*merged);
380
381 return false;
382 }
383
RemovePrinter(const std::string & id)384 bool PrintersSyncBridge::RemovePrinter(const std::string& id) {
385 DCHECK(store_delegate_->Ready());
386
387 std::unique_ptr<ModelTypeStore::WriteBatch> batch =
388 store_delegate_->CreateWriteBatch();
389 {
390 base::AutoLock lock(data_lock_);
391 if (!DeleteSpecifics(id, batch.get())) {
392 LOG(WARNING) << "Could not find printer" << id;
393 return false;
394 }
395 }
396
397 if (change_processor()->IsTrackingMetadata()) {
398 change_processor()->Delete(id, batch->GetMetadataChangeList());
399 }
400 store_delegate_->Commit(std::move(batch));
401
402 return true;
403 }
404
GetAllPrinters() const405 std::vector<sync_pb::PrinterSpecifics> PrintersSyncBridge::GetAllPrinters()
406 const {
407 base::AutoLock lock(data_lock_);
408 std::vector<sync_pb::PrinterSpecifics> printers;
409 for (auto& entry : all_data_) {
410 printers.push_back(*entry.second);
411 }
412
413 return printers;
414 }
415
GetPrinter(const std::string & id) const416 base::Optional<sync_pb::PrinterSpecifics> PrintersSyncBridge::GetPrinter(
417 const std::string& id) const {
418 base::AutoLock lock(data_lock_);
419 auto iter = all_data_.find(id);
420 if (iter == all_data_.end()) {
421 return {};
422 }
423
424 return {*iter->second};
425 }
426
HasPrinter(const std::string & id) const427 bool PrintersSyncBridge::HasPrinter(const std::string& id) const {
428 base::AutoLock lock(data_lock_);
429 return all_data_.find(id) != all_data_.end();
430 }
431
CommitPrinterPut(const sync_pb::PrinterSpecifics & printer)432 void PrintersSyncBridge::CommitPrinterPut(
433 const sync_pb::PrinterSpecifics& printer) {
434 std::unique_ptr<ModelTypeStore::WriteBatch> batch =
435 store_delegate_->CreateWriteBatch();
436 if (change_processor()->IsTrackingMetadata()) {
437 change_processor()->Put(printer.id(), CopyToEntityData(printer),
438 batch->GetMetadataChangeList());
439 }
440 batch->WriteData(printer.id(), printer.SerializeAsString());
441
442 store_delegate_->Commit(std::move(batch));
443 }
444
AddPrinterLocked(std::unique_ptr<sync_pb::PrinterSpecifics> printer)445 void PrintersSyncBridge::AddPrinterLocked(
446 std::unique_ptr<sync_pb::PrinterSpecifics> printer) {
447 // TODO(skau): Benchmark this code. Make sure it doesn't hold onto the lock
448 // for too long.
449 data_lock_.AssertAcquired();
450 printer->set_updated_timestamp(base::Time::Now().ToJavaTime());
451
452 CommitPrinterPut(*printer);
453 auto& dest = all_data_[printer->id()];
454 dest = std::move(printer);
455 }
456
StoreSpecifics(std::unique_ptr<sync_pb::PrinterSpecifics> specifics,ModelTypeStore::WriteBatch * batch)457 void PrintersSyncBridge::StoreSpecifics(
458 std::unique_ptr<sync_pb::PrinterSpecifics> specifics,
459 ModelTypeStore::WriteBatch* batch) {
460 data_lock_.AssertAcquired();
461 const std::string id = specifics->id();
462 batch->WriteData(id, specifics->SerializeAsString());
463 all_data_[id] = std::move(specifics);
464 }
465
DeleteSpecifics(const std::string & id,ModelTypeStore::WriteBatch * batch)466 bool PrintersSyncBridge::DeleteSpecifics(const std::string& id,
467 ModelTypeStore::WriteBatch* batch) {
468 data_lock_.AssertAcquired();
469 auto iter = all_data_.find(id);
470 if (iter != all_data_.end()) {
471 batch->DeleteData(id);
472 all_data_.erase(iter);
473 return true;
474 }
475
476 return false;
477 }
478
AddObserver(Observer * obs)479 void PrintersSyncBridge::AddObserver(Observer* obs) {
480 observers_->AddObserver(obs);
481 }
482
RemoveObserver(Observer * obs)483 void PrintersSyncBridge::RemoveObserver(Observer* obs) {
484 observers_->RemoveObserver(obs);
485 }
486
NotifyPrintersUpdated()487 void PrintersSyncBridge::NotifyPrintersUpdated() {
488 observers_->Notify(FROM_HERE,
489 &PrintersSyncBridge::Observer::OnPrintersUpdated);
490 }
491
492 } // namespace chromeos
493