blob: 0d8567cd5189c8970532d257d55f5e677e3d9a02 [file] [log] [blame]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <iostream>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <base/command_line.h>
#include <base/containers/contains.h>
#include <base/files/file.h>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/functional/callback_helpers.h>
#include <base/json/json_writer.h>
#include <base/run_loop.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <base/task/single_thread_task_executor.h>
#include <base/values.h>
#include <brillo/errors/error.h>
#include <brillo/flag_helper.h>
#include <brillo/process/process.h>
#include <brillo/syslog_logging.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <lorgnette/proto_bindings/lorgnette_service.pb.h>
#include <re2/re2.h>
#include "lorgnette/cli/advanced_scan.h"
#include "lorgnette/cli/commands.h"
#include "lorgnette/cli/discovery_handler.h"
#include "lorgnette/cli/print_config.h"
#include "lorgnette/cli/scan_handler.h"
#include "lorgnette/cli/scan_options.h"
#include "lorgnette/dbus-proxies.h"
#include "lorgnette/guess_source.h"
using lorgnette::cli::Command;
using lorgnette::cli::DiscoveryHandler;
using lorgnette::cli::kCommandMap;
using lorgnette::cli::ScanHandler;
using org::chromium::lorgnette::ManagerProxy;
namespace {
// ListScanners can take a long time if many USB scanners are plugged in.
constexpr int kListScannersTimeoutMs = 60000;
template <class T, class M>
std::vector<T> MapKeys(const M& map) {
std::vector<T> keys;
for (const auto& [k, v] : map) {
keys.push_back(k);
}
return keys;
}
template <class It>
std::optional<lorgnette::DocumentSource> FindMatchingSource(
It begin, It end, lorgnette::SourceType source) {
auto it =
std::find_if(begin, end, [source](const lorgnette::DocumentSource& s) {
return s.type() == source;
});
if (it != end) {
return *it;
}
return std::nullopt;
}
std::optional<std::vector<std::string>> ReadLines(base::File* file) {
std::string buf(1 << 20, '\0');
int read = file->ReadAtCurrentPos(&buf[0], buf.size());
if (read < 0) {
PLOG(ERROR) << "Reading from file failed";
return std::nullopt;
}
buf.resize(read);
return base::SplitString(buf, "\n", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
}
std::optional<lorgnette::CancelScanResponse> CancelScan(
ManagerProxy* manager, const std::string& uuid) {
lorgnette::CancelScanRequest request;
request.set_scan_uuid(uuid);
brillo::ErrorPtr error;
lorgnette::CancelScanResponse response;
if (!manager->CancelScan(request, &response, &error)) {
LOG(ERROR) << "Cancelling scan failed: " << error->GetMessage();
return std::nullopt;
}
return response;
}
std::optional<std::vector<std::string>> ListScanners(ManagerProxy* manager) {
brillo::ErrorPtr error;
lorgnette::ListScannersResponse scanner_list;
if (!manager->ListScanners(&scanner_list, &error, kListScannersTimeoutMs)) {
LOG(ERROR) << "ListScanners failed: " << error->GetMessage();
return std::nullopt;
}
std::vector<std::string> scanner_names;
std::cout << "SANE scanners: " << std::endl;
for (const lorgnette::ScannerInfo& scanner : scanner_list.scanners()) {
std::cout << scanner.name() << ": " << scanner.manufacturer() << " "
<< scanner.model() << "(" << scanner.type() << ")" << std::endl;
scanner_names.push_back(scanner.name());
}
std::cout << scanner_list.scanners_size() << " SANE scanners found."
<< std::endl;
return scanner_names;
}
std::optional<lorgnette::ScannerCapabilities> GetScannerCapabilities(
ManagerProxy* manager, const std::string& scanner_name) {
brillo::ErrorPtr error;
lorgnette::ScannerCapabilities capabilities;
if (!manager->GetScannerCapabilities(scanner_name, &capabilities, &error)) {
LOG(ERROR) << "GetScannerCapabilities failed: " << error->GetMessage();
return std::nullopt;
}
return capabilities;
}
std::optional<lorgnette::ScannerConfig> OpenScanner(
ManagerProxy* manager,
const std::string& scanner_name,
const std::string& client_id) {
brillo::ErrorPtr error;
lorgnette::OpenScannerRequest open_request;
open_request.mutable_scanner_id()->set_connection_string(scanner_name);
open_request.set_client_id(client_id);
lorgnette::OpenScannerResponse open_response;
if (!manager->OpenScanner(open_request, &open_response, &error)) {
LOG(ERROR) << "OpenScanner failed: " << error->GetMessage();
return std::nullopt;
}
if (open_response.result() != lorgnette::OPERATION_RESULT_SUCCESS) {
LOG(ERROR) << "OpenScanner returned error result "
<< lorgnette::OperationResult_Name(open_response.result());
return std::nullopt;
}
return open_response.config();
}
void CloseScanner(ManagerProxy* manager,
const lorgnette::ScannerHandle& scanner) {
brillo::ErrorPtr error;
lorgnette::CloseScannerRequest close_request;
*close_request.mutable_scanner() = scanner;
lorgnette::CloseScannerResponse close_response;
if (!manager->CloseScanner(close_request, &close_response, &error)) {
LOG(WARNING) << "CloseScanner failed: " << error->GetMessage();
return;
}
if (close_response.result() != lorgnette::OPERATION_RESULT_SUCCESS) {
LOG(WARNING) << "CloseScanner returned error result "
<< lorgnette::OperationResult_Name(close_response.result());
}
}
std::optional<lorgnette::ScannerConfig> SetScannerOptions(
ManagerProxy* manager, const lorgnette::SetOptionsRequest& request) {
brillo::ErrorPtr error;
lorgnette::SetOptionsResponse response;
if (!manager->SetOptions(request, &response, &error)) {
LOG(ERROR) << "SetOptions failed: " << error->GetMessage();
return std::nullopt;
}
for (const auto& [option, result] : response.results()) {
if (result != lorgnette::OPERATION_RESULT_SUCCESS) {
LOG(WARNING) << "SetOptions returned error result for " << option << ": "
<< lorgnette::OperationResult_Name(result);
}
// Fall through even if some options failed.
}
return response.config();
}
// RoundThousandth will round the input number at the thousandth place
double RoundThousandth(double num) {
return round(num * 1000) / 1000.0;
}
void PrintScannerCapabilities(
const lorgnette::ScannerCapabilities& capabilities) {
std::cout << "--- Capabilities ---" << std::endl;
std::cout << "Sources:" << std::endl;
for (const lorgnette::DocumentSource& source : capabilities.sources()) {
std::cout << "\t" << source.name() << " ("
<< lorgnette::SourceType_Name(source.type()) << ")" << std::endl;
if (source.has_area()) {
std::cout << "\t\t" << RoundThousandth(source.area().width())
<< "mm wide by " << RoundThousandth(source.area().height())
<< "mm tall" << std::endl;
}
std::cout << "\t\tResolutions:" << std::endl;
for (uint32_t resolution : source.resolutions()) {
std::cout << "\t\t\t" << resolution << std::endl;
}
std::cout << "\t\tColor Modes:" << std::endl;
for (int color_mode : source.color_modes()) {
std::cout << "\t\t\t" << lorgnette::ColorMode_Name(color_mode)
<< std::endl;
}
}
}
std::optional<std::vector<std::string>> ReadAirscanOutput(
brillo::ProcessImpl* discover) {
base::File discover_output(discover->GetPipe(STDOUT_FILENO));
if (!discover_output.IsValid()) {
LOG(ERROR) << "Failed to open airscan-discover output pipe";
return std::nullopt;
}
int ret = discover->Wait();
if (ret != 0) {
LOG(ERROR) << "airscan-discover exited with error " << ret;
return std::nullopt;
}
std::optional<std::vector<std::string>> lines = ReadLines(&discover_output);
if (!lines.has_value()) {
LOG(ERROR) << "Failed to read output from airscan-discover";
return std::nullopt;
}
std::vector<std::string> scanner_names;
for (const std::string& line : lines.value()) {
// Line format is something like:
// " Lexmark MB2236adwe = https://192.168.0.15:443/eSCL/, eSCL"
// We use '.*\S' to match the device name instead of '\S+' so that we can
// properly match internal spaces. Since the regex is greedy by default,
// we need to end the match group with '\S' so that it doesn't capture any
// trailing white-space.
std::string name, url;
if (RE2::FullMatch(line, R"(\s*(.*\S)\s+=\s+(.+), eSCL)", &name, &url)) {
// Replace ':' with '_' because sane-airscan uses ':' to delimit the
// fields of the device_string (i.e."airscan:escl:MyPrinter:[url]) passed
// to it.
base::ReplaceChars(name, ":", "_", &name);
scanner_names.push_back("airscan:escl:" + name + ":" + url);
}
}
return scanner_names;
}
class ScanRunner {
public:
explicit ScanRunner(ManagerProxy* manager) : manager_(manager) {}
void SetResolution(uint32_t resolution) { resolution_ = resolution; }
void SetSource(lorgnette::SourceType source) { source_ = source; }
void SetScanRegion(const lorgnette::ScanRegion& region) { region_ = region; }
void SetColorMode(lorgnette::ColorMode color_mode) {
color_mode_ = color_mode;
}
void SetImageFormat(lorgnette::ImageFormat image_format) {
image_format_ = image_format;
}
bool RunScanner(const std::string& scanner,
const std::string& output_pattern);
private:
ManagerProxy* manager_; // Not owned.
uint32_t resolution_;
lorgnette::SourceType source_;
std::optional<lorgnette::ScanRegion> region_;
lorgnette::ColorMode color_mode_;
lorgnette::ImageFormat image_format_;
};
bool ScanRunner::RunScanner(const std::string& scanner,
const std::string& output_pattern) {
std::cout << "Getting device capabilities for " << scanner << std::endl;
std::optional<lorgnette::ScannerCapabilities> capabilities =
GetScannerCapabilities(manager_, scanner);
if (!capabilities.has_value())
return false;
PrintScannerCapabilities(capabilities.value());
if (!base::Contains(capabilities->resolutions(), resolution_)) {
// Many scanners will round the requested resolution to the nearest
// supported resolution. We will attempt to scan with the given resolution
// since it may still work.
LOG(WARNING) << "Requested scan resolution " << resolution_
<< " is not supported by the selected scanner. "
"Attempting to request it anyways.";
}
// If the user hasn't requested a specific scan source, choose the platen if
// it's available or the ADF if it isn't. Otherwise, require exactly what the
// user has requested.
std::optional<lorgnette::DocumentSource> scan_source;
if (source_ == lorgnette::SOURCE_UNSPECIFIED) {
scan_source = FindMatchingSource(capabilities->sources().begin(),
capabilities->sources().end(),
lorgnette::SOURCE_PLATEN);
if (!scan_source.has_value()) {
scan_source = FindMatchingSource(capabilities->sources().begin(),
capabilities->sources().end(),
lorgnette::SOURCE_ADF_SIMPLEX);
}
if (!scan_source.has_value()) {
LOG(ERROR) << "No automatic source found. Specify scan_source manually.";
return false;
}
std::cout << "Selected source: "
<< lorgnette::SourceType_Name(scan_source->type()) << std::endl;
} else {
scan_source = FindMatchingSource(capabilities->sources().begin(),
capabilities->sources().end(), source_);
}
if (!scan_source.has_value()) {
LOG(ERROR) << "Requested scan source "
<< lorgnette::SourceType_Name(source_)
<< " is not supported by the selected scanner";
return false;
}
if (region_.has_value()) {
if (!scan_source->has_area()) {
LOG(ERROR)
<< "Requested scan source does not support specifying a scan region.";
return false;
}
if (region_->top_left_x() == -1.0)
region_->set_top_left_x(0.0);
if (region_->top_left_y() == -1.0)
region_->set_top_left_y(0.0);
if (region_->bottom_right_x() == -1.0)
region_->set_bottom_right_x(scan_source->area().width());
if (region_->bottom_right_y() == -1.0)
region_->set_bottom_right_y(scan_source->area().height());
}
if (!base::Contains(capabilities->color_modes(), color_mode_)) {
LOG(ERROR) << "Requested scan source does not support color mode "
<< ColorMode_Name(color_mode_);
return false;
}
// Implicitly uses this thread's executor as defined in main.
base::RunLoop run_loop;
ScanHandler handler(run_loop.QuitClosure(), manager_, scanner,
output_pattern);
if (!handler.WaitUntilConnected()) {
return false;
}
std::cout << "Scanning from " << scanner << std::endl;
if (!handler.StartScan(resolution_, scan_source.value(), region_, color_mode_,
image_format_)) {
return false;
}
// Will run until the ScanHandler runs this RunLoop's quit_closure.
run_loop.Run();
return true;
}
std::vector<std::string> BuildScannerList(ManagerProxy* manager) {
// Start the airscan-discover process immediately since it can be slightly
// long-running. We read the output later after we've gotten a scanner list
// from lorgnette.
brillo::ProcessImpl discover;
discover.AddArg("/usr/bin/airscan-discover");
discover.RedirectUsingPipe(STDOUT_FILENO, false);
if (!discover.Start()) {
LOG(ERROR) << "Failed to start airscan-discover process";
return {};
}
std::cout << "Getting scanner list." << std::endl;
std::optional<std::vector<std::string>> sane_scanners = ListScanners(manager);
if (!sane_scanners.has_value())
return {};
std::optional<std::vector<std::string>> airscan_scanners =
ReadAirscanOutput(&discover);
if (!airscan_scanners.has_value())
return {};
std::vector<std::string> scanners = std::move(sane_scanners.value());
scanners.insert(scanners.end(), airscan_scanners.value().begin(),
airscan_scanners.value().end());
return scanners;
}
bool DoScan(std::unique_ptr<ManagerProxy> manager,
uint32_t scan_resolution,
lorgnette::SourceType source_type,
const lorgnette::ScanRegion& region,
lorgnette::ColorMode color_mode,
lorgnette::ImageFormat image_format,
bool scan_from_all_scanners,
const std::string& forced_scanner,
const std::string& output_pattern) {
ScanRunner runner(manager.get());
runner.SetResolution(scan_resolution);
runner.SetSource(source_type);
runner.SetColorMode(color_mode);
runner.SetImageFormat(image_format);
if (region.top_left_x() != -1.0 || region.top_left_y() != -1.0 ||
region.bottom_right_x() != -1.0 || region.bottom_right_y() != -1.0) {
runner.SetScanRegion(region);
}
if (!forced_scanner.empty()) {
return runner.RunScanner(forced_scanner, output_pattern);
}
std::vector<std::string> scanners = BuildScannerList(manager.get());
if (scanners.empty()) {
return false;
}
std::cout << "Choose a scanner (blank to quit):" << std::endl;
for (int i = 0; i < scanners.size(); i++) {
std::cout << i << ". " << scanners[i] << std::endl;
}
if (!scan_from_all_scanners) {
int index = -1;
std::cout << "> ";
std::cin >> index;
if (std::cin.fail()) {
return 0;
}
std::string scanner = scanners[index];
return runner.RunScanner(scanner, output_pattern);
}
std::cout << "Scanning from all scanners." << std::endl;
std::vector<std::string> successes;
std::vector<std::string> failures;
for (const std::string& scanner : scanners) {
if (runner.RunScanner(scanner, output_pattern)) {
successes.push_back(scanner);
} else {
failures.push_back(scanner);
}
}
std::cout << "Successful scans:" << std::endl;
for (const std::string& scanner : successes) {
std::cout << " " << scanner << std::endl;
}
std::cout << "Failed scans:" << std::endl;
for (const std::string& scanner : failures) {
std::cout << " " << scanner << std::endl;
}
return true;
}
std::string ScannerCapabilitiesToJson(
const lorgnette::ScannerCapabilities& caps) {
base::Value::Dict caps_dict;
for (const lorgnette::DocumentSource& source : caps.sources()) {
base::Value::Dict source_dict;
source_dict.Set("Name", source.name());
if (source.has_area()) {
base::Value::Dict area_dict;
area_dict.Set("Width", RoundThousandth(source.area().width()));
area_dict.Set("Height", RoundThousandth(source.area().height()));
source_dict.Set("ScannableArea", std::move(area_dict));
}
base::Value::List resolution_list;
for (const uint32_t resolution : source.resolutions()) {
resolution_list.Append(static_cast<int>(resolution));
}
source_dict.Set("Resolutions", std::move(resolution_list));
base::Value::List color_mode_list;
for (const int color_mode : source.color_modes()) {
color_mode_list.Append(lorgnette::ColorMode_Name(color_mode));
}
source_dict.Set("ColorModes", std::move(color_mode_list));
caps_dict.Set(lorgnette::SourceType_Name(source.type()),
std::move(source_dict));
}
std::string json;
base::JSONWriter::Write(caps_dict, &json);
return json;
}
lorgnette::SetDebugConfigResponse SetLorgnetteDebug(ManagerProxy* manager,
bool enabled) {
lorgnette::SetDebugConfigRequest request;
request.set_enabled(enabled);
lorgnette::SetDebugConfigResponse response;
brillo::ErrorPtr error;
if (!manager->SetDebugConfig(request, &response, &error)) {
LOG(ERROR) << "Failed to call SetDebugConfig: " << error->GetMessage();
}
return response;
}
} // namespace
int main(int argc, char** argv) {
brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderrIfTty |
brillo::kLogHeader);
// Scan options.
DEFINE_uint32(scan_resolution, 100,
"The scan resolution to request from the scanner");
DEFINE_string(scan_source, "Auto",
"The scan source to use for the scanner, (e.g. Platen, ADF "
"Simplex, ADF Duplex)");
DEFINE_string(color_mode, "Color",
"The color mode to use for the scanner, (e.g. Color, Grayscale,"
"Lineart)");
DEFINE_bool(all, false,
"Loop through all detected scanners instead of prompting.");
DEFINE_double(top_left_x, -1.0,
"Top-left X position of the scan region (mm)");
DEFINE_double(top_left_y, -1.0,
"Top-left Y position of the scan region (mm)");
DEFINE_double(bottom_right_x, -1.0,
"Bottom-right X position of the scan region (mm)");
DEFINE_double(bottom_right_y, -1.0,
"Bottom-right Y position of the scan region (mm)");
DEFINE_string(output, "/tmp/scan-%s_page%n.%e",
"Pattern for output files. If present, %s will be replaced "
"with the scanner name, %n will be replaced with the page "
"number, and %e will be replaced with the extension matching "
"the selected image format.");
DEFINE_string(image_format, "PNG",
"Image format for the output file (PNG or JPG)");
DEFINE_bool(debug, false,
"Enable lorgnette debug logging for this operation. The "
"previous debug value will be restored when lorgnette_cli "
"exits.");
// Cancel Scan options
DEFINE_string(uuid, "", "UUID of the scan job to cancel.");
// show_config options
DEFINE_bool(show_inactive, false,
"Show inactive options in scanner config output.");
DEFINE_bool(show_advanced, false,
"Show advanced options in scanner config output.");
// discover options
DEFINE_bool(show_details, false, "Show full scanner info during discovery");
// General scanner operations options.
DEFINE_string(scanner, "",
"Name of the scanner whose capabilities are requested.");
// The client ID to use for the advanced scanning API.
DEFINE_string(client_id, "lorgnette_cli",
"Value of the client ID used in some of the advanced scanning "
"API functions.");
brillo::FlagHelper::Init(argc, argv,
"lorgnette_cli, command-line interface to "
"Chromium OS Scanning Daemon");
const std::vector<std::string>& args =
base::CommandLine::ForCurrentProcess()->GetArgs();
if (args.size() < 1 || !base::Contains(kCommandMap, args[0])) {
std::cerr << "Usage: lorgnette_cli "
<< base::JoinString(MapKeys<std::string_view>(kCommandMap), "|")
<< " [FLAGS...]" << std::endl;
return 1;
}
Command command = kCommandMap.at(args[0]);
base::StringPairs scan_options = lorgnette::cli::GetScanOptions(args);
if (scan_options.size() > 0) {
if (command != Command::kAdvancedScan && command != Command::kShowConfig) {
std::cerr << "set_options can only be used with "
<< "advanced_scan and show_config" << std::endl;
return 1;
}
}
// Create a task executor for this thread. This will automatically be bound
// to the current thread so that it is usable by other code for posting tasks.
base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO);
// Create a FileDescriptorWatcher instance for this thread. The libbase D-Bus
// bindings use this internally via thread-local storage, but do not properly
// instantiate it.
base::FileDescriptorWatcher watcher(executor.task_runner());
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
scoped_refptr<dbus::Bus> bus(new dbus::Bus(options));
auto manager =
std::make_unique<ManagerProxy>(bus, lorgnette::kManagerServiceName);
base::ScopedClosureRunner debug_cleanup;
if (FLAGS_debug) {
lorgnette::SetDebugConfigResponse response =
SetLorgnetteDebug(manager.get(), true);
if (!response.old_enabled()) {
debug_cleanup.ReplaceClosure(base::BindOnce(
[](ManagerProxy* manager) { SetLorgnetteDebug(manager, false); },
manager.get()));
}
if (!response.success()) {
LOG(WARNING) << "Failed to enable lorgnette debugging.";
}
}
switch (command) {
case Command::kScan: {
if (!FLAGS_uuid.empty()) {
LOG(ERROR) << "--uuid flag is not supported in scan mode.";
return 1;
}
std::optional<lorgnette::SourceType> source_type =
GuessSourceType(FLAGS_scan_source);
if (!source_type.has_value() ||
(source_type.value() == lorgnette::SOURCE_UNSPECIFIED &&
!base::EqualsCaseInsensitiveASCII(FLAGS_scan_source, "auto"))) {
LOG(ERROR)
<< "Unknown source type: \"" << FLAGS_scan_source
<< "\". Supported values are \"Platen\", \"ADF\", \"ADF Simplex\""
", and \"ADF Duplex\"";
return 1;
}
lorgnette::ScanRegion region;
region.set_top_left_x(FLAGS_top_left_x);
region.set_top_left_y(FLAGS_top_left_y);
region.set_bottom_right_x(FLAGS_bottom_right_x);
region.set_bottom_right_y(FLAGS_bottom_right_y);
std::string color_mode_string = base::ToLowerASCII(FLAGS_color_mode);
lorgnette::ColorMode color_mode;
if (color_mode_string == "color") {
color_mode = lorgnette::MODE_COLOR;
} else if (color_mode_string == "grayscale" ||
color_mode_string == "gray") {
color_mode = lorgnette::MODE_GRAYSCALE;
} else if (color_mode_string == "lineart" || color_mode_string == "bw") {
color_mode = lorgnette::MODE_LINEART;
} else {
LOG(ERROR) << "Unknown color mode: \"" << color_mode_string
<< "\". Supported values are \"Color\", \"Grayscale\", and "
"\"Lineart\"";
return 1;
}
std::string image_format_string = base::ToLowerASCII(FLAGS_image_format);
lorgnette::ImageFormat image_format;
if (image_format_string == "png") {
image_format = lorgnette::IMAGE_FORMAT_PNG;
} else if (image_format_string == "jpg") {
image_format = lorgnette::IMAGE_FORMAT_JPEG;
} else {
LOG(ERROR) << "Unknown image format: \"" << image_format_string
<< "\". Supported values are \"PNG\" and \"JPG\"";
return 1;
}
bool success =
DoScan(std::move(manager), FLAGS_scan_resolution, source_type.value(),
region, color_mode, image_format, FLAGS_all, FLAGS_scanner,
FLAGS_output);
return success ? 0 : 1;
}
case Command::kList: {
std::vector<std::string> scanners = BuildScannerList(manager.get());
std::cout << "Detected scanners:" << std::endl;
for (int i = 0; i < scanners.size(); i++) {
std::cout << scanners[i] << std::endl;
}
return 0;
}
case Command::kDiscover: {
std::cout << "Discovering scanners: " << std::endl;
base::RunLoop run_loop;
DiscoveryHandler handler(run_loop.QuitClosure(), manager.get());
if (!handler.WaitUntilConnected()) {
LOG(ERROR) << "Failed to set up ScannerListChangedSignal handler";
return 1;
}
handler.SetShowDetails(FLAGS_show_details);
handler.SetScannerPattern(FLAGS_scanner);
if (!handler.StartDiscovery(FLAGS_client_id)) {
LOG(ERROR) << "Failed to start discovery";
return 1;
}
run_loop.Run();
return 0;
}
case Command::kCancelScan: {
if (FLAGS_uuid.empty()) {
LOG(ERROR) << "Must specify scan uuid to cancel using --uuid=[...]";
return 1;
}
std::optional<lorgnette::CancelScanResponse> response =
CancelScan(manager.get(), FLAGS_uuid);
if (!response.has_value())
return 1;
if (!response->success()) {
LOG(ERROR) << "Failed to cancel scan: " << response->failure_reason();
return 1;
}
return 0;
}
case Command::kGetJsonCaps: {
if (FLAGS_scanner.empty()) {
LOG(ERROR) << "Must specify scanner to get capabilities";
return 1;
}
std::optional<lorgnette::ScannerCapabilities> capabilities =
GetScannerCapabilities(manager.get(), FLAGS_scanner);
if (!capabilities.has_value()) {
LOG(ERROR) << "Received null capabilities from lorgnette";
return 1;
}
std::cout << ScannerCapabilitiesToJson(capabilities.value());
return 0;
}
case Command::kShowConfig: {
if (FLAGS_scanner.empty()) {
LOG(ERROR) << "Must specify scanner to get its config";
return 1;
}
std::optional<lorgnette::ScannerConfig> config =
OpenScanner(manager.get(), FLAGS_scanner, FLAGS_client_id);
if (!config.has_value()) {
LOG(ERROR) << "Unable to open scanner " << FLAGS_scanner;
return 1;
}
if (scan_options.size()) {
std::optional<lorgnette::SetOptionsRequest> request =
lorgnette::cli::MakeSetOptionsRequest(config.value(), scan_options);
if (!request.has_value()) {
LOG(ERROR) << "Unable to parse set_options values";
return 1;
}
config = SetScannerOptions(manager.get(), request.value());
if (!config.has_value()) {
LOG(ERROR) << "Unable to set scanner options";
return 1;
}
}
CloseScanner(manager.get(), config->scanner());
lorgnette::cli::PrintScannerConfig(config.value(), FLAGS_show_inactive,
FLAGS_show_advanced, std::cout);
return 0;
}
case Command::kAdvancedScan: {
if (FLAGS_scanner.empty()) {
LOG(ERROR) << "Must specify scanner for advanced scan";
return 1;
}
std::string image_format_string = base::ToLowerASCII(FLAGS_image_format);
std::string mime_type;
if (image_format_string == "png") {
mime_type = "image/png";
} else if (image_format_string == "jpg") {
mime_type = "image/jpeg";
} else {
LOG(ERROR) << "Unknown image format: \"" << image_format_string
<< "\". Supported values are \"PNG\" and \"JPG\"";
return 1;
}
return lorgnette::cli::DoAdvancedScan(manager.get(), FLAGS_scanner,
FLAGS_client_id, scan_options,
mime_type, FLAGS_output)
? 0
: 1;
}
case Command::kSetOptions: {
std::cerr << "set_options must come after the main command" << std::endl;
return 1;
}
}
NOTREACHED();
return 1;
}