blob: 9d8982efdf267395c28da531248507866e0b1240 [file] [log] [blame] [edit]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "lorgnette/cli/advanced_scan.h"
#include <algorithm>
#include <iostream>
#include <utility>
#include <base/check.h>
#include <base/files/file.h>
#include <base/files/scoped_file.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <brillo/errors/error.h>
#include <lorgnette/proto_bindings/lorgnette_service.pb.h>
#include "lorgnette/cli/file_pattern.h"
#include "lorgnette/cli/scan_options.h"
#include "lorgnette/constants.h"
#include "lorgnette/dbus-proxies.h"
#include "lorgnette/guess_source.h"
using org::chromium::lorgnette::ManagerProxy;
namespace {
constexpr base::TimeDelta kSlowOperationTimeout = base::Seconds(60);
std::string ExtensionForMimeType(const std::string& mime_type) {
if (mime_type == lorgnette::kPngMimeType) {
return "png";
} else if (mime_type == lorgnette::kJpegMimeType) {
return "jpg";
} else {
LOG(ERROR) << "No extension for format " << mime_type;
return "raw";
}
}
bool ReadNextDocument(ManagerProxy* manager,
const lorgnette::JobHandle& job_handle,
const base::FilePath& output_path) {
base::File output_file(
output_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!output_file.IsValid()) {
std::cerr << "Failed to create file " << output_path << ": "
<< output_file.error_details() << std::endl;
return false;
}
lorgnette::ReadScanDataRequest read_request;
*read_request.mutable_job_handle() = job_handle;
bool keep_reading = true;
std::cout << "Progress: " << std::flush;
while (keep_reading) {
lorgnette::ReadScanDataResponse read_response;
brillo::ErrorPtr error;
if (!manager->ReadScanData(read_request, &read_response, &error,
kSlowOperationTimeout.InMilliseconds())) {
std::cerr << "ReadScanData failed: " << error->GetMessage() << std::endl;
return false;
}
switch (read_response.result()) {
case lorgnette::OPERATION_RESULT_EOF:
// Reached the end of the page
keep_reading = false;
// Fall through because data may be non-empty on the final read.
[[fallthrough]];
case lorgnette::OPERATION_RESULT_SUCCESS:
if (read_response.data().empty()) {
// Read succeeded, but no data was available.
base::PlatformThread::Sleep(base::Milliseconds(100));
continue;
}
if (!output_file.WriteAtCurrentPos(read_response.data().data(),
read_response.data().length())) {
std::cerr << "Unable to write " << read_response.data().length()
<< " bytes to output file." << std::endl;
return false;
}
std::cout << read_response.estimated_completion() << "% " << std::flush;
break;
default:
std::cerr << "Error while reading scan data: "
<< lorgnette::OperationResult_Name(read_response.result())
<< std::endl;
return false;
}
}
std::cout << "Done" << std::endl;
return true;
}
} // namespace
namespace lorgnette::cli {
bool DoAdvancedScan(ManagerProxy* manager,
const std::string& scanner_name,
const std::string& client_id,
const base::StringPairs& scan_options,
const std::string& mime_type,
const std::string& output_pattern) {
std::cout << "Opening scanner " << scanner_name << std::endl;
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)) {
std::cerr << "OpenScanner failed: " << error->GetMessage() << std::endl;
return false;
}
if (open_response.result() != lorgnette::OPERATION_RESULT_SUCCESS) {
std::cerr << "OpenScanner returned error result "
<< lorgnette::OperationResult_Name(open_response.result())
<< std::endl;
return false;
}
// Ensure the scanner handle is closed when this function returns.
base::ScopedClosureRunner close_scanner(base::BindOnce(
[](ManagerProxy* manager, lorgnette::ScannerHandle scanner) {
lorgnette::CloseScannerRequest close_request;
*close_request.mutable_scanner() = std::move(scanner);
lorgnette::CloseScannerResponse close_response;
brillo::ErrorPtr error;
if (!manager->CloseScanner(close_request, &close_response, &error)) {
std::cerr << "CloseScanner failed: " << error->GetMessage()
<< std::endl;
return;
}
if (close_response.result() != lorgnette::OPERATION_RESULT_SUCCESS) {
std::cerr << "CloseScanner returned error result "
<< lorgnette::OperationResult_Name(close_response.result())
<< std::endl;
}
},
manager, open_response.config().scanner()));
if (scan_options.size()) {
std::optional<lorgnette::SetOptionsRequest> set_request =
MakeSetOptionsRequest(open_response.config(), scan_options);
if (!set_request.has_value()) {
std::cerr << "Unable to parse set_options values" << std::endl;
return false;
}
std::cout << "Setting " << set_request->options_size() << " options"
<< std::endl;
lorgnette::SetOptionsResponse set_response;
if (!manager->SetOptions(set_request.value(), &set_response, &error)) {
std::cerr << "SetOptions failed: " << error->GetMessage() << std::endl;
return false;
}
for (const auto& requested : set_request->options()) {
lorgnette::OperationResult result =
(*set_response.mutable_results())[requested.name()];
if (result != lorgnette::OPERATION_RESULT_SUCCESS) {
std::cerr << "WARNING: Failed to set " << requested.name() << ": "
<< lorgnette::OperationResult_Name(result) << std::endl;
}
const auto& actual =
(*set_response.mutable_config()->mutable_options())[requested.name()];
switch (actual.option_type()) {
case lorgnette::TYPE_BOOL:
if (actual.bool_value() != requested.bool_value()) {
std::cerr << "WARNING: " << actual.name()
<< " requested=" << requested.bool_value()
<< ", actual=" << actual.bool_value() << std::endl;
}
break;
case lorgnette::TYPE_INT:
if (actual.int_value().value_size() !=
requested.int_value().value_size()) {
std::cerr << "WARNING: " << actual.name() << " requested "
<< requested.int_value().value_size()
<< " values and got back "
<< actual.int_value().value_size() << std::endl;
break;
}
for (int i = 0; i < actual.int_value().value_size(); i++) {
if (actual.int_value().value(i) != requested.int_value().value(i)) {
std::cerr << "WARNING: " << actual.name()
<< " requested=" << requested.int_value().value(i)
<< ", actual=" << actual.int_value().value(i)
<< std::endl;
}
}
break;
case lorgnette::TYPE_FIXED:
if (actual.fixed_value().value_size() !=
requested.fixed_value().value_size()) {
std::cerr << "WARNING: " << actual.name() << " requested "
<< requested.fixed_value().value_size()
<< " values and got back "
<< actual.fixed_value().value_size() << std::endl;
break;
}
for (int i = 0; i < actual.fixed_value().value_size(); i++) {
if (actual.fixed_value().value(i) !=
requested.fixed_value().value(i)) {
std::cerr << "WARNING: " << actual.name()
<< " requested=" << requested.fixed_value().value(i)
<< ", actual=" << actual.fixed_value().value(i)
<< std::endl;
}
}
break;
case lorgnette::TYPE_STRING:
if (actual.string_value() != requested.string_value()) {
std::cerr << "WARNING: " << actual.name() << " requested=\""
<< requested.string_value() << "\", actual=\""
<< actual.string_value() << "\"" << std::endl;
}
break;
default:
// No value to compare.
break;
}
}
}
size_t page = 1;
std::string extension = ExtensionForMimeType(mime_type);
// If the source appears to be an ADF, read pages until the ADF is empty.
// Otherwise read a single page.
bool more_pages = false;
if (open_response.config().options().contains("source")) {
const ScannerOption& source = open_response.config().options().at("source");
lorgnette::SourceType source_type = GuessSourceType(source.string_value());
if (source_type == lorgnette::SOURCE_ADF_SIMPLEX ||
source_type == lorgnette::SOURCE_ADF_DUPLEX) {
more_pages = true;
}
}
do {
base::FilePath output_path =
ExpandPattern(output_pattern, page, scanner_name, extension);
std::cout << "Looking to scan page " << page << " to " << output_path
<< std::endl;
lorgnette::StartPreparedScanRequest scan_request;
*scan_request.mutable_scanner() = open_response.config().scanner();
scan_request.set_image_format(mime_type);
lorgnette::StartPreparedScanResponse scan_response;
if (!manager->StartPreparedScan(scan_request, &scan_response, &error,
kSlowOperationTimeout.InMilliseconds())) {
std::cerr << "StartPreparedScan failed: " << error->GetMessage()
<< std::endl;
return false;
}
switch (scan_response.result()) {
case lorgnette::OPERATION_RESULT_SUCCESS:
break;
case lorgnette::OPERATION_RESULT_ADF_EMPTY:
std::cout << "ADF is empty" << std::endl;
return page > 1; // If we already read a page, scanning succeeded.
break;
default:
std::cerr << "StartPreparedScan returned error result "
<< lorgnette::OperationResult_Name(scan_response.result())
<< std::endl;
return false;
}
if (!ReadNextDocument(manager, scan_response.job_handle(), output_path)) {
return false;
}
++page;
} while (more_pages);
return true;
}
} // namespace lorgnette::cli