blob: 25e7a93ea5d633d026574f55fa2e5139fe83a55f [file] [log] [blame]
// Copyright 2014 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 "chromeos/dbus/lorgnette_manager_client.h"
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/files/scoped_file.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/optional.h"
#include "base/task/post_task.h"
#include "chromeos/dbus/pipe_reader.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/object_proxy.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
namespace chromeos {
// The LorgnetteManagerClient implementation used in production.
class LorgnetteManagerClientImpl : public LorgnetteManagerClient {
public:
LorgnetteManagerClientImpl() = default;
~LorgnetteManagerClientImpl() override = default;
void ListScanners(DBusMethodCallback<ScannerTable> callback) override {
dbus::MethodCall method_call(lorgnette::kManagerServiceInterface,
lorgnette::kListScannersMethod);
lorgnette_daemon_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&LorgnetteManagerClientImpl::OnListScanners,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
// LorgnetteManagerClient override.
void ScanImageToString(std::string device_name,
const ScanProperties& properties,
DBusMethodCallback<std::string> callback) override {
auto scan_data_reader = std::make_unique<ScanDataReader>();
base::ScopedFD fd = scan_data_reader->Start();
// Issue the dbus request to scan an image.
dbus::MethodCall method_call(lorgnette::kManagerServiceInterface,
lorgnette::kScanImageMethod);
dbus::MessageWriter writer(&method_call);
writer.AppendString(device_name);
writer.AppendFileDescriptor(fd.get());
dbus::MessageWriter option_writer(nullptr);
dbus::MessageWriter element_writer(nullptr);
writer.OpenArray("{sv}", &option_writer);
if (!properties.mode.empty()) {
option_writer.OpenDictEntry(&element_writer);
element_writer.AppendString(lorgnette::kScanPropertyMode);
element_writer.AppendVariantOfString(properties.mode);
option_writer.CloseContainer(&element_writer);
}
if (properties.resolution_dpi) {
option_writer.OpenDictEntry(&element_writer);
element_writer.AppendString(lorgnette::kScanPropertyResolution);
element_writer.AppendVariantOfUint32(properties.resolution_dpi);
option_writer.CloseContainer(&element_writer);
}
writer.CloseContainer(&option_writer);
lorgnette_daemon_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&LorgnetteManagerClientImpl::OnScanImageComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(scan_data_reader)));
}
protected:
void Init(dbus::Bus* bus) override {
lorgnette_daemon_proxy_ =
bus->GetObjectProxy(lorgnette::kManagerServiceName,
dbus::ObjectPath(lorgnette::kManagerServicePath));
}
private:
// Reads scan data on a blocking sequence.
class ScanDataReader {
public:
// In case of success, std::string holds the read data. Otherwise,
// nullopt.
using CompletionCallback =
base::OnceCallback<void(base::Optional<std::string> data)>;
ScanDataReader() = default;
// Creates a pipe to read the scan data from the D-Bus service.
// Returns a write-side FD.
base::ScopedFD Start() {
DCHECK(!pipe_reader_.get());
DCHECK(!data_.has_value());
pipe_reader_ = std::make_unique<chromeos::PipeReader>(
base::CreateTaskRunnerWithTraits(
{base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
return pipe_reader_->StartIO(base::BindOnce(
&ScanDataReader::OnDataRead, weak_ptr_factory_.GetWeakPtr()));
}
// Waits for the data read completion. If it is already done, |callback|
// will be called synchronously.
void Wait(CompletionCallback callback) {
DCHECK(callback_.is_null());
callback_ = std::move(callback);
MaybeCompleted();
}
private:
// Called when a |pipe_reader_| completes reading scan data to a string.
void OnDataRead(base::Optional<std::string> data) {
DCHECK(!data_read_);
data_read_ = true;
data_ = std::move(data);
pipe_reader_.reset();
MaybeCompleted();
}
void MaybeCompleted() {
// If data reading is not yet completed, or D-Bus call does not yet
// return, wait for the other.
if (!data_read_ || callback_.is_null())
return;
std::move(callback_).Run(std::move(data_));
}
std::unique_ptr<chromeos::PipeReader> pipe_reader_;
// Set to true on data read completion.
bool data_read_ = false;
// Available only when |data_read_| is true.
base::Optional<std::string> data_;
CompletionCallback callback_;
base::WeakPtrFactory<ScanDataReader> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ScanDataReader);
};
// Called when ListScanners completes.
void OnListScanners(DBusMethodCallback<ScannerTable> callback,
dbus::Response* response) {
dbus::MessageReader table_reader(nullptr);
if (!response || !dbus::MessageReader(response).PopArray(&table_reader)) {
std::move(callback).Run(base::nullopt);
return;
}
ScannerTable scanners;
while (table_reader.HasMoreData()) {
std::string device_name;
dbus::MessageReader device_entry_reader(nullptr);
dbus::MessageReader device_element_reader(nullptr);
if (!table_reader.PopDictEntry(&device_entry_reader) ||
!device_entry_reader.PopString(&device_name) ||
!device_entry_reader.PopArray(&device_element_reader)) {
LOG(ERROR) << "Failed to decode response from ListScanners";
std::move(callback).Run(base::nullopt);
return;
}
ScannerTableEntry scanner_entry;
while (device_element_reader.HasMoreData()) {
std::string attribute;
std::string value;
dbus::MessageReader device_attribute_reader(nullptr);
if (!device_element_reader.PopDictEntry(&device_attribute_reader) ||
!device_attribute_reader.PopString(&attribute) ||
!device_attribute_reader.PopString(&value)) {
LOG(ERROR) << "Failed to decode response from ListScanners";
std::move(callback).Run(base::nullopt);
return;
}
scanner_entry.emplace(std::move(attribute), std::move(value));
}
scanners.emplace(std::move(device_name), std::move(scanner_entry));
}
std::move(callback).Run(std::move(scanners));
}
// Called when a response for ScanImage() is received.
void OnScanImageComplete(DBusMethodCallback<std::string> callback,
std::unique_ptr<ScanDataReader> scan_data_reader,
dbus::Response* response) {
if (!response) {
LOG(ERROR) << "Failed to scan image";
// Do not touch |scan_data_reader|, so that RAII deletes it and
// cancels the inflight operation.
std::move(callback).Run(base::nullopt);
return;
}
auto* reader = scan_data_reader.get();
reader->Wait(
base::BindOnce(&LorgnetteManagerClientImpl::OnScanDataCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback),
std::move(scan_data_reader)));
}
// Called when scan data read is completed.
// This is to maintain the lifetime of ScanDataReader instance.
void OnScanDataCompleted(DBusMethodCallback<std::string> callback,
std::unique_ptr<ScanDataReader> scan_data_reader,
base::Optional<std::string> data) {
std::move(callback).Run(std::move(data));
}
dbus::ObjectProxy* lorgnette_daemon_proxy_ = nullptr;
base::WeakPtrFactory<LorgnetteManagerClientImpl> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(LorgnetteManagerClientImpl);
};
LorgnetteManagerClient::LorgnetteManagerClient() = default;
LorgnetteManagerClient::~LorgnetteManagerClient() = default;
// static
std::unique_ptr<LorgnetteManagerClient> LorgnetteManagerClient::Create() {
return std::make_unique<LorgnetteManagerClientImpl>();
}
} // namespace chromeos