Commit f51b8af4 authored by Jesse Schettler's avatar Jesse Schettler Committed by Commit Bot

scanning: Add and implement ScanService::Scan()

Add and implement ScanService::Scan() to allow clients to perform scans.
The ScanService currently saves each scanned image to the user's
"My files" folder, but future changes will allow clients to specify the
file type and save-to location.

Bug: 1059779
Change-Id: I628571f8663498bc8499e9b7e53d1d6b144a0816
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2436385
Commit-Queue: Jesse Schettler <jschettler@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarZentaro Kavanagh <zentaro@chromium.org>
Cr-Commit-Position: refs/heads/master@{#813480}
parent c324dcc5
......@@ -8,7 +8,10 @@
#include "base/bind.h"
#include "base/check.h"
#include "base/files/file_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/scanning/lorgnette_scanner_manager.h"
#include "chrome/browser/chromeos/scanning/scanning_type_converters.h"
......@@ -18,6 +21,9 @@ namespace {
namespace mojo_ipc = scanning::mojom;
// Path to the user's "My files" folder, relative to the root directory.
constexpr char kMyFilesPath[] = "home/chronos/user/MyFiles";
} // namespace
ScanService::ScanService(LorgnetteScannerManager* lorgnette_scanner_manager)
......@@ -36,24 +42,47 @@ void ScanService::GetScanners(GetScannersCallback callback) {
void ScanService::GetScannerCapabilities(
const base::UnguessableToken& scanner_id,
GetScannerCapabilitiesCallback callback) {
const auto it = scanner_names_.find(scanner_id);
if (it == scanner_names_.end()) {
LOG(ERROR) << "Failed to find scanner name using the given scanner id.";
const std::string scanner_name = GetScannerName(scanner_id);
if (scanner_name.empty())
std::move(callback).Run(mojo_ipc::ScannerCapabilities::New());
return;
}
lorgnette_scanner_manager_->GetScannerCapabilities(
it->second,
scanner_name,
base::BindOnce(&ScanService::OnScannerCapabilitiesReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ScanService::Scan(const base::UnguessableToken& scanner_id,
mojo_ipc::ScanSettingsPtr settings,
ScanCallback callback) {
const std::string scanner_name = GetScannerName(scanner_id);
if (scanner_name.empty())
std::move(callback).Run(false);
save_failed_ = false;
// TODO(jschettler): Create a TypeConverter to convert from
// mojo_ipc::ScanSettingsPtr to lorgnette::ScanSettings once the settings are
// finalized.
lorgnette::ScanSettings settings_proto;
settings_proto.set_source_name(settings->source_name);
lorgnette_scanner_manager_->Scan(
scanner_name, settings_proto,
base::BindRepeating(&ScanService::OnPageReceived,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&ScanService::OnScanCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ScanService::BindInterface(
mojo::PendingReceiver<mojo_ipc::ScanService> pending_receiver) {
receiver_.Bind(std::move(pending_receiver));
}
void ScanService::SetRootDirForTesting(const base::FilePath& root_dir) {
root_dir_ = root_dir;
}
void ScanService::Shutdown() {
lorgnette_scanner_manager_ = nullptr;
receiver_.reset();
......@@ -89,4 +118,33 @@ void ScanService::OnScannerCapabilitiesReceived(
mojo::ConvertTo<mojo_ipc::ScannerCapabilitiesPtr>(capabilities.value()));
}
void ScanService::OnPageReceived(std::string scanned_image) {
// TODO(jschettler): Add page number to filename.
base::Time::Exploded time;
base::Time::Now().UTCExplode(&time);
const std::string filename = base::StringPrintf(
"scan_%02d%02d%02d-%02d%02d%02d.png", time.year, time.month,
time.day_of_month, time.hour, time.minute, time.second);
const auto file_path = root_dir_.Append(kMyFilesPath).Append(filename);
if (!base::WriteFile(file_path, scanned_image)) {
LOG(ERROR) << "Failed to save scanned image: " << file_path.value().c_str();
save_failed_ = true;
}
}
void ScanService::OnScanCompleted(ScanCallback callback, bool success) {
std::move(callback).Run(success && !save_failed_);
}
std::string ScanService::GetScannerName(
const base::UnguessableToken& scanner_id) {
const auto it = scanner_names_.find(scanner_id);
if (it == scanner_names_.end()) {
LOG(ERROR) << "Failed to find scanner name using the given scanner id.";
return "";
}
return it->second;
}
} // namespace chromeos
......@@ -9,6 +9,7 @@
#include <vector>
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/unguessable_token.h"
......@@ -37,11 +38,17 @@ class ScanService : public scanning::mojom::ScanService, public KeyedService {
void GetScanners(GetScannersCallback callback) override;
void GetScannerCapabilities(const base::UnguessableToken& scanner_id,
GetScannerCapabilitiesCallback callback) override;
void Scan(const base::UnguessableToken& scanner_id,
scanning::mojom::ScanSettingsPtr settings,
ScanCallback callback) override;
// Binds receiver_ by consuming |pending_receiver|.
void BindInterface(
mojo::PendingReceiver<scanning::mojom::ScanService> pending_receiver);
// Sets the root directory to use when saving scanned images for tests.
void SetRootDirForTesting(const base::FilePath& root_dir);
private:
// KeyedService:
void Shutdown() override;
......@@ -56,6 +63,17 @@ class ScanService : public scanning::mojom::ScanService, public KeyedService {
GetScannerCapabilitiesCallback callback,
const base::Optional<lorgnette::ScannerCapabilities>& capabilities);
// Processes each |scanned_image| received after calling
// LorgnetteScannerManager::Scan().
void OnPageReceived(std::string scanned_image);
// Processes the final result of calling LorgnetteScannerManager::Scan().
void OnScanCompleted(ScanCallback callback, bool success);
// Returns the scanner name corresponding to the given |scanner_id| or an
// empty string if the name cannot be found.
std::string GetScannerName(const base::UnguessableToken& scanner_id);
// Map of scanner IDs to display names. Used to pass the correct display name
// to LorgnetteScannerManager when clients provide an ID.
base::flat_map<base::UnguessableToken, std::string> scanner_names_;
......@@ -67,6 +85,13 @@ class ScanService : public scanning::mojom::ScanService, public KeyedService {
// Unowned. Used to get scanner information and perform scans.
LorgnetteScannerManager* lorgnette_scanner_manager_;
// The root directory where scanned images are saved. Allows tests to set a
// different root.
base::FilePath root_dir_ = base::FilePath("/");
// Indicates whether there was a failure to save scanned images.
bool save_failed_;
base::WeakPtrFactory<ScanService> weak_ptr_factory_{this};
};
......
......@@ -6,6 +6,8 @@
#include <vector>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/optional.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/task_environment.h"
......@@ -23,6 +25,9 @@ namespace {
namespace mojo_ipc = scanning::mojom;
// Relative path where scanned images are saved, relative to the root directory.
constexpr char kMyFilesPath[] = "home/chronos/user/MyFiles";
// Scanner names used for tests.
constexpr char kFirstTestScannerName[] = "Test Scanner 1";
constexpr char kSecondTestScannerName[] = "Test Scanner 2";
......@@ -59,6 +64,10 @@ class ScanServiceTest : public testing::Test {
ScanServiceTest() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ASSERT_TRUE(
base::CreateDirectory(temp_dir_.GetPath().Append(kMyFilesPath)));
scan_service_.SetRootDirForTesting(temp_dir_.GetPath());
scan_service_.BindInterface(
scan_service_remote_.BindNewPipeAndPassReceiver());
}
......@@ -82,11 +91,22 @@ class ScanServiceTest : public testing::Test {
return caps;
}
// Performs a scan with the scanner identified by |scanner_id| with the given
// |settings| by calling ScanService::Scan() via the mojo::Remote.
bool Scan(const base::UnguessableToken& scanner_id,
mojo_ipc::ScanSettingsPtr settings) {
bool success;
mojo_ipc::ScanServiceAsyncWaiter(scan_service_remote_.get())
.Scan(scanner_id, std::move(settings), &success);
return success;
}
protected:
FakeLorgnetteScannerManager fake_lorgnette_scanner_manager_;
private:
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
ScanService scan_service_{&fake_lorgnette_scanner_manager_};
mojo::Remote<mojo_ipc::ScanService> scan_service_remote_;
};
......@@ -164,4 +184,21 @@ TEST_F(ScanServiceTest, GetScannerCapabilities) {
EXPECT_EQ(caps->resolutions[1], kSecondResolution);
}
// Test that attempting to scan with a scanner ID that doesn't correspond to a
// scanner results in a failed scan.
TEST_F(ScanServiceTest, ScanWithBadScannerId) {
EXPECT_FALSE(
Scan(base::UnguessableToken::Create(), mojo_ipc::ScanSettings::New()));
}
// Test that a scan can be performed successfully.
TEST_F(ScanServiceTest, Scan) {
fake_lorgnette_scanner_manager_.SetGetScannerNamesResponse(
{kFirstTestScannerName});
fake_lorgnette_scanner_manager_.SetScanResponse("TestData");
auto scanners = GetScanners();
ASSERT_EQ(scanners.size(), 1u);
EXPECT_TRUE(Scan(scanners[0]->id, mojo_ipc::ScanSettings::New()));
}
} // namespace chromeos
......@@ -45,6 +45,16 @@ struct ScannerCapabilities {
array<uint32> resolutions;
};
// Settings used to perform a scan.
struct ScanSettings {
// The SANE name of the ScanSource from which to scan.
string source_name;
// The color mode with which to scan.
ColorMode color_mode;
// The resolution with which to scan in DPI.
uint32 resolution_dpi;
};
// Represents a connected scanner.
struct Scanner {
// The scanner's unique identifier.
......@@ -67,4 +77,9 @@ interface ScanService {
// were obtained via a secure protocol.
GetScannerCapabilities(mojo_base.mojom.UnguessableToken scanner_id)
=> (ScannerCapabilities capabilities);
// Performs a scan using the provided |settings|.
// TODO(jschettler): Send a ScanJobObserver to get scan job updates.
Scan(mojo_base.mojom.UnguessableToken scanner_id, ScanSettings settings)
=> (bool success);
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment