| // Copyright 2020 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 "chrome/browser/ash/scanning/lorgnette_scanner_manager.h" |
| |
| #include <array> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/check.h" |
| #include "base/containers/flat_map.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/ash/scanning/lorgnette_scanner_manager_util.h" |
| #include "chrome/browser/ash/scanning/zeroconf_scanner_detector.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/lorgnette/lorgnette_service.pb.h" |
| #include "chromeos/dbus/lorgnette_manager/lorgnette_manager_client.h" |
| #include "chromeos/scanning/scanner.h" |
| #include "net/base/ip_address.h" |
| #include "third_party/re2/src/re2/re2.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // A prioritized list of scan protocols. Protocols that appear earlier in the |
| // list are preferred over those that appear later in the list when |
| // communicating with a connected scanner. |
| constexpr std::array<chromeos::ScanProtocol, 4> kPrioritizedProtocols = { |
| chromeos::ScanProtocol::kEscls, chromeos::ScanProtocol::kEscl, |
| chromeos::ScanProtocol::kLegacyNetwork, chromeos::ScanProtocol::kLegacyUsb}; |
| |
| // Returns a pointer to LorgnetteManagerClient, which is used to detect and |
| // interact with scanners via the lorgnette D-Bus service. |
| chromeos::LorgnetteManagerClient* GetLorgnetteManagerClient() { |
| DCHECK(DBusThreadManager::IsInitialized()); |
| return DBusThreadManager::Get()->GetLorgnetteManagerClient(); |
| } |
| |
| // Creates a base name by concatenating the manufacturer and model, if the |
| // model doesn't already include the manufacturer. Appends "(USB)" for USB |
| // scanners. |
| std::string CreateBaseName(const lorgnette::ScannerInfo& lorgnette_scanner, |
| const bool is_usb_scanner) { |
| const std::string model = lorgnette_scanner.model(); |
| const std::string manufacturer = lorgnette_scanner.manufacturer(); |
| |
| // It's assumed that, if present, the manufacturer would be the first word in |
| // the model. |
| const std::string maybe_manufacturer = |
| RE2::PartialMatch(model.c_str(), base::StringPrintf("(?i)\\A%s\\b", |
| manufacturer.c_str())) |
| ? "" |
| : manufacturer + " "; |
| |
| return base::StringPrintf("%s%s%s", maybe_manufacturer.c_str(), model.c_str(), |
| is_usb_scanner ? " (USB)" : ""); |
| } |
| |
| class LorgnetteScannerManagerImpl final : public LorgnetteScannerManager { |
| public: |
| LorgnetteScannerManagerImpl( |
| std::unique_ptr<ZeroconfScannerDetector> zeroconf_scanner_detector) |
| : zeroconf_scanner_detector_(std::move(zeroconf_scanner_detector)) { |
| zeroconf_scanner_detector_->RegisterScannersDetectedCallback( |
| base::BindRepeating(&LorgnetteScannerManagerImpl::OnScannersDetected, |
| weak_ptr_factory_.GetWeakPtr())); |
| OnScannersDetected(zeroconf_scanner_detector_->GetScanners()); |
| } |
| |
| ~LorgnetteScannerManagerImpl() override = default; |
| |
| // KeyedService: |
| void Shutdown() override { weak_ptr_factory_.InvalidateWeakPtrs(); } |
| |
| // LorgnetteScannerManager: |
| void GetScannerNames(GetScannerNamesCallback callback) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| GetLorgnetteManagerClient()->ListScanners( |
| base::BindOnce(&LorgnetteScannerManagerImpl::OnListScannersResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| // LorgnetteScannerManager: |
| void GetScannerCapabilities( |
| const std::string& scanner_name, |
| GetScannerCapabilitiesCallback callback) override { |
| std::string device_name; |
| chromeos::ScanProtocol protocol; |
| if (!GetUsableDeviceNameAndProtocol(scanner_name, device_name, protocol)) { |
| std::move(callback).Run(base::nullopt); |
| return; |
| } |
| |
| GetLorgnetteManagerClient()->GetScannerCapabilities( |
| device_name, |
| base::BindOnce( |
| &LorgnetteScannerManagerImpl::OnScannerCapabilitiesResponse, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback), scanner_name, |
| device_name, protocol)); |
| } |
| |
| // LorgnetteScannerManager: |
| void Scan(const std::string& scanner_name, |
| const lorgnette::ScanSettings& settings, |
| ProgressCallback progress_callback, |
| PageCallback page_callback, |
| CompletionCallback completion_callback) override { |
| std::string device_name; |
| chromeos::ScanProtocol protocol; // Unused. |
| if (!GetUsableDeviceNameAndProtocol(scanner_name, device_name, protocol)) { |
| std::move(completion_callback).Run(lorgnette::SCAN_FAILURE_MODE_UNKNOWN); |
| return; |
| } |
| |
| GetLorgnetteManagerClient()->StartScan( |
| device_name, settings, std::move(completion_callback), |
| std::move(page_callback), std::move(progress_callback)); |
| } |
| |
| // LorgnetteScannerManager: |
| void CancelScan(CancelCallback cancel_callback) override { |
| GetLorgnetteManagerClient()->CancelScan(std::move(cancel_callback)); |
| } |
| |
| private: |
| // Called when scanners are detected by a ScannerDetector. |
| void OnScannersDetected(std::vector<chromeos::Scanner> scanners) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| zeroconf_scanners_ = scanners; |
| } |
| |
| // Handles the result of calling LorgnetteManagerClient::ListScanners(). |
| void OnListScannersResponse( |
| GetScannerNamesCallback callback, |
| base::Optional<lorgnette::ListScannersResponse> response) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_); |
| RebuildDedupedScanners(response); |
| std::vector<std::string> scanner_names; |
| scanner_names.reserve(deduped_scanners_.size()); |
| for (const auto& entry : deduped_scanners_) |
| scanner_names.push_back(entry.first); |
| |
| std::move(callback).Run(std::move(scanner_names)); |
| } |
| |
| // Handles the result of calling |
| // LorgnetteManagerClient::GetScannerCapabilities(). If getting the scanner |
| // capabilities fails, |scanner_name|, |device_name|, and |protocol| are used |
| // to mark the device name that was used as unusable and retry the operation |
| // with the next available device name. This pattern of trying each device |
| // name cannot be used when performing a scan since the backend used to obtain |
| // the capabilities must be the same backend used to perform the scan. |
| void OnScannerCapabilitiesResponse( |
| GetScannerCapabilitiesCallback callback, |
| const std::string& scanner_name, |
| const std::string& device_name, |
| const chromeos::ScanProtocol protocol, |
| base::Optional<lorgnette::ScannerCapabilities> capabilities) { |
| if (!capabilities) { |
| LOG(WARNING) << "Failed to get scanner capabilities using device name: " |
| << device_name; |
| MarkDeviceNameUnusable(scanner_name, device_name, protocol); |
| GetScannerCapabilities(scanner_name, std::move(callback)); |
| return; |
| } |
| |
| std::move(callback).Run(capabilities); |
| } |
| |
| // Uses |response| and zeroconf_scanners_ to rebuild deduped_scanners_. |
| void RebuildDedupedScanners( |
| base::Optional<lorgnette::ListScannersResponse> response) { |
| ResetDedupedScanners(); |
| if (!response || response->scanners_size() == 0) |
| return; |
| |
| // Iterate through each lorgnette scanner and add its info to an existing |
| // Scanner if it has a matching IP address. Otherwise, create a new Scanner |
| // for the lorgnette scanner. |
| base::flat_map<net::IPAddress, chromeos::Scanner*> known_ip_addresses = |
| GetKnownIpAddresses(); |
| for (const auto& lorgnette_scanner : response->scanners()) { |
| std::string ip_address_str; |
| chromeos::ScanProtocol protocol = chromeos::ScanProtocol::kUnknown; |
| ParseScannerName(lorgnette_scanner.name(), ip_address_str, protocol); |
| if (!ip_address_str.empty()) { |
| net::IPAddress ip_address; |
| if (ip_address.AssignFromIPLiteral(ip_address_str)) { |
| const auto it = known_ip_addresses.find(ip_address); |
| if (it != known_ip_addresses.end()) { |
| it->second->device_names[protocol].emplace( |
| lorgnette_scanner.name()); |
| continue; |
| } |
| } |
| } |
| |
| const bool is_usb_scanner = |
| protocol == chromeos::ScanProtocol::kLegacyUsb; |
| const std::string base_name = |
| CreateBaseName(lorgnette_scanner, is_usb_scanner); |
| const std::string display_name = CreateUniqueDisplayName(base_name); |
| |
| chromeos::Scanner scanner; |
| scanner.display_name = display_name; |
| scanner.device_names[protocol].emplace(lorgnette_scanner.name()); |
| deduped_scanners_[display_name] = scanner; |
| } |
| } |
| |
| // Resets |deduped_scanners_| by clearing it and repopulating it with |
| // zeroconf_scanners_. |
| void ResetDedupedScanners() { |
| deduped_scanners_.clear(); |
| deduped_scanners_.reserve(zeroconf_scanners_.size()); |
| for (const auto& scanner : zeroconf_scanners_) |
| deduped_scanners_[scanner.display_name] = scanner; |
| } |
| |
| // Returns a map of IP addresses to the scanners they correspond to in |
| // deduped_scanners_. This enables deduplication of network scanners by making |
| // it easy to check for and modify them using their IP addresses. |
| base::flat_map<net::IPAddress, chromeos::Scanner*> GetKnownIpAddresses() { |
| base::flat_map<net::IPAddress, chromeos::Scanner*> known_ip_addresses; |
| for (auto& entry : deduped_scanners_) { |
| for (const auto& ip_address : entry.second.ip_addresses) |
| known_ip_addresses[ip_address] = &entry.second; |
| } |
| |
| return known_ip_addresses; |
| } |
| |
| // Creates a unique display name by appending a copy number to a duplicate |
| // name (e.g. if Scanner Name already exists, the second instance will be |
| // renamed Scanner Name (1)). |
| std::string CreateUniqueDisplayName(const std::string& base_name) { |
| std::string display_name = base_name; |
| int i = 1; // The first duplicate will become "Scanner Name (1)." |
| while (deduped_scanners_.find(display_name) != deduped_scanners_.end()) { |
| display_name = base::StringPrintf("%s (%d)", base_name.c_str(), i); |
| i++; |
| } |
| |
| return display_name; |
| } |
| |
| // Gets the first usable device name corresponding to the highest priority |
| // protocol for the scanner specified by |scanner_name|. Returns true on |
| // success, false on failure. |
| bool GetUsableDeviceNameAndProtocol(const std::string& scanner_name, |
| std::string& device_name_out, |
| chromeos::ScanProtocol& protocol_out) { |
| const auto scanner_it = deduped_scanners_.find(scanner_name); |
| if (scanner_it == deduped_scanners_.end()) { |
| LOG(ERROR) << "Failed to find scanner with name " << scanner_name; |
| return false; |
| } |
| |
| for (const auto& protocol : kPrioritizedProtocols) { |
| const auto device_names_it = |
| scanner_it->second.device_names.find(protocol); |
| if (device_names_it == scanner_it->second.device_names.end()) |
| continue; |
| |
| for (const chromeos::ScannerDeviceName& name : device_names_it->second) { |
| if (name.usable) { |
| device_name_out = name.device_name; |
| protocol_out = protocol; |
| return true; |
| } |
| } |
| } |
| |
| LOG(ERROR) << "Failed to find usable device name for " << scanner_name; |
| return false; |
| } |
| |
| // Marks a device name as unusable to prevent it from being returned by future |
| // calls to GetUsableDeviceNameAndProtocol(). |
| void MarkDeviceNameUnusable(const std::string& scanner_name, |
| const std::string& device_name, |
| const chromeos::ScanProtocol protocol) { |
| auto scanner_it = deduped_scanners_.find(scanner_name); |
| if (scanner_it == deduped_scanners_.end()) |
| return; |
| |
| auto device_names_it = scanner_it->second.device_names.find(protocol); |
| if (device_names_it == scanner_it->second.device_names.end()) |
| return; |
| |
| for (chromeos::ScannerDeviceName& name : device_names_it->second) { |
| if (name.device_name == device_name) { |
| name.usable = false; |
| return; |
| } |
| } |
| } |
| |
| // Used to detect zeroconf scanners. |
| std::unique_ptr<ZeroconfScannerDetector> zeroconf_scanner_detector_; |
| |
| // The deduplicated zeroconf scanners reported by the |
| // zeroconf_scanner_detector_. |
| std::vector<chromeos::Scanner> zeroconf_scanners_; |
| |
| // Stores the deduplicated scanners from all sources in a map of display name |
| // to Scanner. Clients are given display names and can use them to interact |
| // with the corresponding scanners. |
| base::flat_map<std::string, chromeos::Scanner> deduped_scanners_; |
| |
| SEQUENCE_CHECKER(sequence_); |
| |
| base::WeakPtrFactory<LorgnetteScannerManagerImpl> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<LorgnetteScannerManager> LorgnetteScannerManager::Create( |
| std::unique_ptr<ZeroconfScannerDetector> zeroconf_scanner_detector) { |
| return std::make_unique<LorgnetteScannerManagerImpl>( |
| std::move(zeroconf_scanner_detector)); |
| } |
| |
| } // namespace ash |