Skip to content

Commit

Permalink
Add basic scanner auto-discovery
Browse files Browse the repository at this point in the history
So far, we can list scanners on the network but not make direct use
of them. The IP/hostname still needs to be manually carried over into
--host. (Guess what's up next!)
  • Loading branch information
tanuva committed Nov 25, 2023
1 parent a971a9a commit 32836c3
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 0 deletions.
33 changes: 33 additions & 0 deletions escl-scan-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ extern crate scan;

use clap::{Args, Parser};
use scan::scanner::Scanner;
use scan::scannerfinder::ScannerFinder;
use std::path::Path;
use std::process::exit;

Expand Down Expand Up @@ -39,6 +40,33 @@ struct DeviceArgs {
/// Print information about the scanner identified by device name
#[arg(short, long)]
info: Option<String>,

/// List available scanners
#[arg(short, long)]
list: bool,
}

fn list_scanners() {
let mut finder = ScannerFinder::new();
let scanners = match finder.find(None) {
Ok(scanners) => scanners,
Err(err) => {
eprintln!("Failed to discover scanners: {err}");
exit(1);
}
};

if scanners.len() == 0 {
println!("No scanners found");
} else if scanners.len() == 1 {
println!("Found 1 scanner:");
} else {
println!("Found {} scanners:", scanners.len());
}

for scanner in scanners {
println!("- {}: {}", scanner.device_name, scanner.base_url);
}
}

fn get_scanner(cli: &Cli) -> Scanner {
Expand All @@ -53,6 +81,11 @@ fn main() {
env_logger::init();
let args = Cli::parse();

if args.device.list {
list_scanners();
exit(0);
}

// TODO This is just a band-aid for testing
if let Some(name) = args.device.info {
exit(0);
Expand Down
1 change: 1 addition & 0 deletions escl-scan/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ log = "0.4.*"
reqwest = { version = "0.11.16", features = ["blocking"] }
serde = { version = "1.0.160", features = ["derive"] }
serde-xml-rs = "0.6.0"
zeroconf = "0.12.*"
3 changes: 3 additions & 0 deletions escl-scan/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

extern crate zeroconf;

pub mod scanner;
pub mod scannererror;
pub mod scannerfinder;
pub mod structs;
1 change: 1 addition & 0 deletions escl-scan/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
use reqwest::blocking::Response;
use std::fs::File;

#[derive(Clone, Debug)]
pub struct Scanner {
pub base_url: String,
pub device_name: String,
Expand Down
13 changes: 13 additions & 0 deletions escl-scan/src/scannererror.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use core::fmt;
pub enum ErrorCode {
FilesystemError,
NetworkError,
NoScannerFound,
ProtocolError,
ScannerNotReady,
}
Expand All @@ -19,6 +20,9 @@ impl fmt::Display for ScannerError {
let msg = match self.code {
ErrorCode::FilesystemError => format!("File System Error: {}", self.message),
ErrorCode::NetworkError => format!("Network Error: {}", self.message),
ErrorCode::NoScannerFound => {
format!("No scanner found where name contains {}", self.message)
}
ErrorCode::ProtocolError => format!("eSCL Protocol Error: {}", self.message),
ErrorCode::ScannerNotReady => "The scanner is not ready to scan".to_string(),
};
Expand Down Expand Up @@ -53,3 +57,12 @@ impl From<std::io::Error> for ScannerError {
}
}
}

impl From<zeroconf::error::Error> for ScannerError {
fn from(error: zeroconf::error::Error) -> Self {
ScannerError {
code: ErrorCode::NetworkError,
message: error.to_string(),
}
}
}
135 changes: 135 additions & 0 deletions escl-scan/src/scannerfinder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use std::{
any::Any,
sync::{Arc, Mutex},
time::{Duration, Instant},
};

use zeroconf::{
browser::TMdnsBrowser, event_loop::TEventLoop, txt_record::TTxtRecord, MdnsBrowser,
ServiceDiscovery, ServiceType, TxtRecord,
};

use crate::{
scanner::Scanner,
scannererror::{ErrorCode, ScannerError},
};

pub struct ScannerFinder {
scanners: Arc<Mutex<Vec<Scanner>>>,
}

impl ScannerFinder {
pub fn new() -> ScannerFinder {
//ScannerFinder { scanners: vec![] }
ScannerFinder {
scanners: Arc::new(Mutex::new(vec![])),
}
}

fn scanner_found(&self, name: &str) -> bool {
let scanners = self.scanners.lock().unwrap();
for scanner in scanners.iter() {
// TODO Search hostname and device name
if scanner.base_url.contains(name) {
return true;
}
}

return false;
}

pub fn find(&mut self, name: Option<&str>) -> Result<Vec<Scanner>, ScannerError> {
let service_type =
ServiceType::with_sub_types(&"uscan", &"tcp", vec![]).expect("invalid service type");
log::info!("Looking for scanners with {service_type:?}");

let mut browser = MdnsBrowser::new(service_type);
browser.set_service_discovered_callback(Box::new(Self::on_service_discovered));

//let scanners: Arc<Mutex<Vec<Scanner>>> = Arc::new(Mutex::new(vec![]));
browser.set_context(Box::new(Arc::clone(&self.scanners)));

let event_loop = match browser.browse_services() {
Ok(event_loop) => event_loop,
Err(err) => return Err(err.into()),
};

let timeout = Duration::from_secs(5);
let end_time = Instant::now() + timeout;
while Instant::now() < end_time {
event_loop.poll(end_time - Instant::now()).unwrap();

if let Some(name) = name {
if self.scanner_found(name) {
log::info!("Found scanner for name {name}");
return Ok(self.scanners.lock().unwrap().clone());
}
}
}

if let Some(name) = name {
log::info!("No scanner found for name {name}");
return Err(ScannerError {
code: ErrorCode::NoScannerFound,
message: name.to_string(),
});
} else {
let scanners = self.scanners.lock().unwrap();
log::info!("Found {} scanners on the network", scanners.len());
return Ok(scanners.clone());
};
}

fn on_service_discovered(
result: zeroconf::Result<ServiceDiscovery>,
context: Option<Arc<dyn Any>>,
) {
let service = match result {
Ok(service) => service,
Err(err) => {
log::info!("Error during scanner discovery (continuing): {err}");
return;
}
};

log::info!("Service discovered: {service:?}",);
//let mut context = context.expect("We provided a scanner list as context");
let scanners = context
.as_ref()
.unwrap()
.downcast_ref::<Arc<Mutex<Vec<Scanner>>>>()
.unwrap();
let mut scanners = scanners.lock().unwrap();

let txt: &TxtRecord = match service.txt() {
Some(txt) => txt,
None => {
log::warn!("Failed to get txt record for {service:?}");
return;
}
};

let url_root = txt.get("rs");
let device_name = match txt.get("ty") {
Some(name) => name,
None => {
log::warn!("Service has no human-readable device name: {service:?}");
return;
}
};

let scanner = Scanner::new(
device_name.as_str(),
service.host_name(),
url_root.as_ref().map(|s| s.as_str()),
);
log::info!("{:?}", scanner);
scanners.push(scanner);
}
}

0 comments on commit 32836c3

Please sign in to comment.