Skip to content

Commit 8ff466e

Browse files
committed
Move existing functions into struct Scanner
This gives state to the `Scanner` object and moves `scanner_base_path` into the former. Encapsulation improves and enables auto-discovery that I'll implement in a way that it returns a list of `Scanner` instances it found.
1 parent 86eefda commit 8ff466e

File tree

3 files changed

+165
-163
lines changed

3 files changed

+165
-163
lines changed

escl-scan-cli/src/main.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ extern crate clap;
66
extern crate scan;
77

88
use clap::Parser;
9+
use scan::scanner::Scanner;
910
use std::path::Path;
1011
use std::process::exit;
1112

@@ -36,14 +37,14 @@ struct Args {
3637
fn main() {
3738
env_logger::init();
3839
let args = Args::parse();
39-
let scanner_base_path = format!("http://{}:80/eSCL", args.host);
4040

4141
if !args.overwrite && Path::new(&args.output_file_name).exists() {
4242
eprintln!("Output file exists, exiting...");
4343
exit(1);
4444
}
4545

46-
match scan::get_scanner_status(&scanner_base_path) {
46+
let scanner = Scanner::new(args.host, None);
47+
match scanner.get_status() {
4748
Ok(state) => println!("Scanner state: {state}"),
4849
Err(err) => {
4950
eprintln!("Failed to get status: {err:?}");
@@ -56,7 +57,7 @@ fn main() {
5657
exit(0);
5758
}
5859

59-
if let Err(err) = scan::scan(&scanner_base_path, args.dpi, &args.output_file_name) {
60+
if let Err(err) = scanner.scan(args.dpi, &args.output_file_name) {
6061
eprintln!("Failed to scan: {err:?}");
6162
exit(1);
6263
}

escl-scan/src/lib.rs

Lines changed: 0 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -2,166 +2,6 @@
22
* License, v. 2.0. If a copy of the MPL was not distributed with this
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44

5-
extern crate reqwest;
6-
extern crate serde;
7-
extern crate serde_xml_rs;
8-
95
pub mod scanner;
106
pub mod scannererror;
117
pub mod structs;
12-
13-
use crate::scannererror::ErrorCode;
14-
use crate::scannererror::ScannerError;
15-
use reqwest::blocking::Response;
16-
use std::fs::File;
17-
use structs::ScannerState;
18-
19-
pub fn scan(
20-
scanner_base_path: &str,
21-
scan_resolution: i16,
22-
destination_file: &str,
23-
) -> Result<(), ScannerError> {
24-
log::info!("Getting scanner capabilities...");
25-
let scanner_capabilities = match get_scanner_capabilities(&scanner_base_path) {
26-
Ok(caps) => caps,
27-
Err(err) => return Err(err),
28-
};
29-
30-
let scan_settings: structs::ScanSettings = structs::ScanSettings {
31-
version: "2.6".to_string(),
32-
scan_regions: structs::ScanRegion {
33-
x_offset: 0,
34-
y_offset: 0,
35-
width: scanner_capabilities.platen.platen_input_caps.max_width,
36-
height: scanner_capabilities.platen.platen_input_caps.max_height,
37-
content_region_units: "escl:ThreeHundredthsOfInches".to_string(),
38-
},
39-
input_source: "Platen".to_string(),
40-
color_mode: "RGB24".to_string(),
41-
x_resolution: scan_resolution,
42-
y_resolution: scan_resolution,
43-
};
44-
45-
let request_body = match serde_xml_rs::to_string(&scan_settings) {
46-
Ok(body) => body,
47-
Err(err) => return Err(err.into()),
48-
};
49-
50-
log::info!("Sending scan request with DPI {}...", scan_resolution);
51-
let scan_response = match get_scan_response(scanner_base_path, request_body) {
52-
Ok(response) => response,
53-
Err(err) => return Err(err),
54-
};
55-
let location = match scan_response.headers().get("location") {
56-
Some(location) => location.to_str().expect("'location' can be a string"),
57-
None => {
58-
return Err(ScannerError {
59-
code: ErrorCode::ProtocolError,
60-
message: format!(
61-
"Failed to get 'location' header from response:\n{scan_response:?}"
62-
),
63-
});
64-
}
65-
};
66-
67-
let download_url = format!("{}/NextDocument", location);
68-
return download_scan(&download_url, destination_file);
69-
}
70-
71-
pub fn get_scanner_capabilities(
72-
scanner_base_path: &str,
73-
) -> Result<structs::ScannerCapabilities, ScannerError> {
74-
let response =
75-
match reqwest::blocking::get(&format!("{}/ScannerCapabilities", scanner_base_path)) {
76-
Ok(response) => response,
77-
Err(err) => return Err(err.into()),
78-
};
79-
80-
let response_string = response.text().expect("text is a string");
81-
let scanner_capabilities: structs::ScannerCapabilities =
82-
match serde_xml_rs::from_str(&response_string) {
83-
Ok(caps) => caps,
84-
Err(err) => return Err(err.into()),
85-
};
86-
Ok(scanner_capabilities)
87-
}
88-
89-
pub fn get_scanner_status(scanner_base_path: &str) -> Result<ScannerState, ScannerError> {
90-
log::info!("Getting scanner status");
91-
let response = match reqwest::blocking::get(&format!("{}/ScannerStatus", scanner_base_path)) {
92-
Ok(response) => response,
93-
Err(err) => return Err(err.into()),
94-
};
95-
log::debug!("ScannerStatus: {:?}", response);
96-
97-
let response_string = response.text().expect("text is a string");
98-
log::debug!("ScannerStatus: {:?}", response_string);
99-
100-
let scanner_status: structs::ScannerStatus = match serde_xml_rs::from_str(&response_string) {
101-
Ok(status) => status,
102-
Err(err) => return Err(err.into()),
103-
};
104-
105-
log::info!("Scanner state: {}", scanner_status.state);
106-
Ok(scanner_status.state)
107-
}
108-
109-
fn get_scan_response(
110-
scanner_base_path: &str,
111-
request_body: String,
112-
) -> Result<Response, ScannerError> {
113-
let client = reqwest::blocking::Client::new();
114-
let request = client
115-
.post(format!("{}/ScanJobs", &scanner_base_path).as_str())
116-
.body(format!(
117-
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>{}",
118-
request_body
119-
));
120-
let response = match request.send() {
121-
Ok(response) => response,
122-
Err(err) => return Err(err.into()),
123-
};
124-
125-
if !response.status().is_success() {
126-
return Err(ScannerError {
127-
code: ErrorCode::NetworkError,
128-
message: format!("{response:?}"),
129-
});
130-
}
131-
132-
return Ok(response);
133-
}
134-
135-
fn download_scan(download_url: &str, destination_file: &str) -> Result<(), ScannerError> {
136-
// We need to try downloadng at least once again, expecting a 404, to make
137-
// sure we got everything.
138-
// This is necessary on my Brother MFC-L2710DW to get it to idle state
139-
// again. It will wait for timeout otherwise, even if we got the scanned
140-
// page earlier.
141-
let mut page: u16 = 1;
142-
loop {
143-
log::info!("Downloading page {page} to {destination_file}");
144-
let mut response = match reqwest::blocking::get(download_url) {
145-
Ok(response) => response,
146-
Err(err) => return Err(err.into()),
147-
};
148-
149-
if response.status() == 404 {
150-
log::info!("There is no page {page}, we're done");
151-
break;
152-
}
153-
154-
let mut file = match File::create(destination_file) {
155-
Ok(file) => file,
156-
Err(err) => return Err(err.into()),
157-
};
158-
159-
if let Err(err) = response.copy_to(&mut file) {
160-
return Err(err.into());
161-
}
162-
163-
page += 1;
164-
}
165-
166-
Ok(())
167-
}

escl-scan/src/scanner.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4+
5+
extern crate reqwest;
6+
extern crate serde;
7+
extern crate serde_xml_rs;
8+
9+
use crate::{
10+
scannererror::{ErrorCode, ScannerError},
11+
structs,
12+
};
13+
use reqwest::blocking::Response;
14+
use std::fs::File;
15+
116
pub struct Scanner {
217
base_url: String,
318
}
@@ -14,4 +29,150 @@ impl Scanner {
1429
base_url: format!("http://{}:80/{}", ip_or_host, resource_root),
1530
}
1631
}
32+
33+
pub fn get_status(&self) -> Result<structs::ScannerState, ScannerError> {
34+
log::info!("Getting scanner status");
35+
let response = match reqwest::blocking::get(&format!("{}/ScannerStatus", self.base_url)) {
36+
Ok(response) => response,
37+
Err(err) => return Err(err.into()),
38+
};
39+
log::debug!("ScannerStatus: {:?}", response);
40+
41+
let response_string = response.text().expect("text is a string");
42+
log::debug!("ScannerStatus: {:?}", response_string);
43+
44+
let scanner_status: structs::ScannerStatus = match serde_xml_rs::from_str(&response_string)
45+
{
46+
Ok(status) => status,
47+
Err(err) => return Err(err.into()),
48+
};
49+
50+
log::info!("Scanner state: {}", scanner_status.state);
51+
Ok(scanner_status.state)
52+
}
53+
54+
pub fn get_capabilities(&self) -> Result<structs::ScannerCapabilities, ScannerError> {
55+
let response =
56+
match reqwest::blocking::get(&format!("{}/ScannerCapabilities", self.base_url)) {
57+
Ok(response) => response,
58+
Err(err) => return Err(err.into()),
59+
};
60+
61+
let response_string = response.text().expect("text is a string");
62+
let scanner_capabilities: structs::ScannerCapabilities =
63+
match serde_xml_rs::from_str(&response_string) {
64+
Ok(caps) => caps,
65+
Err(err) => return Err(err.into()),
66+
};
67+
Ok(scanner_capabilities)
68+
}
69+
70+
pub fn scan(&self, scan_resolution: i16, destination_file: &str) -> Result<(), ScannerError> {
71+
log::info!("Getting scanner capabilities...");
72+
let scanner_capabilities = match self.get_capabilities() {
73+
Ok(caps) => caps,
74+
Err(err) => return Err(err),
75+
};
76+
77+
let scan_settings: structs::ScanSettings = structs::ScanSettings {
78+
version: "2.6".to_string(),
79+
scan_regions: structs::ScanRegion {
80+
x_offset: 0,
81+
y_offset: 0,
82+
width: scanner_capabilities.platen.platen_input_caps.max_width,
83+
height: scanner_capabilities.platen.platen_input_caps.max_height,
84+
content_region_units: "escl:ThreeHundredthsOfInches".to_string(),
85+
},
86+
input_source: "Platen".to_string(),
87+
color_mode: "RGB24".to_string(),
88+
x_resolution: scan_resolution,
89+
y_resolution: scan_resolution,
90+
};
91+
92+
let request_body = match serde_xml_rs::to_string(&scan_settings) {
93+
Ok(body) => body,
94+
Err(err) => return Err(err.into()),
95+
};
96+
97+
log::info!("Sending scan request with DPI {}...", scan_resolution);
98+
let scan_response = match self.get_scan_response(request_body) {
99+
Ok(response) => response,
100+
Err(err) => return Err(err),
101+
};
102+
let location = match scan_response.headers().get("location") {
103+
Some(location) => location.to_str().expect("'location' can be a string"),
104+
None => {
105+
return Err(ScannerError {
106+
code: ErrorCode::ProtocolError,
107+
message: format!(
108+
"Failed to get 'location' header from response:\n{scan_response:?}"
109+
),
110+
});
111+
}
112+
};
113+
114+
let download_url = format!("{}/NextDocument", location);
115+
return self.download_page(&download_url, destination_file);
116+
}
117+
118+
fn get_scan_response(&self, request_body: String) -> Result<Response, ScannerError> {
119+
let client = reqwest::blocking::Client::new();
120+
let request = client
121+
.post(format!("{}/ScanJobs", &self.base_url).as_str())
122+
.body(format!(
123+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>{}",
124+
request_body
125+
));
126+
let response = match request.send() {
127+
Ok(response) => response,
128+
Err(err) => return Err(err.into()),
129+
};
130+
131+
if !response.status().is_success() {
132+
return Err(ScannerError {
133+
code: ErrorCode::NetworkError,
134+
message: format!("{response:?}"),
135+
});
136+
}
137+
138+
return Ok(response);
139+
}
140+
141+
fn download_page(
142+
&self,
143+
download_url: &str,
144+
destination_file: &str,
145+
) -> Result<(), ScannerError> {
146+
// We need to try downloadng at least once again, expecting a 404, to make
147+
// sure we got everything.
148+
// This is necessary on my Brother MFC-L2710DW to get it to idle state
149+
// again. It will wait for timeout otherwise, even if we got the scanned
150+
// page earlier.
151+
let mut page: u16 = 1;
152+
loop {
153+
log::info!("Downloading page {page} to {destination_file}");
154+
let mut response = match reqwest::blocking::get(download_url) {
155+
Ok(response) => response,
156+
Err(err) => return Err(err.into()),
157+
};
158+
159+
if response.status() == 404 {
160+
log::info!("There is no page {page}, we're done");
161+
break;
162+
}
163+
164+
let mut file = match File::create(destination_file) {
165+
Ok(file) => file,
166+
Err(err) => return Err(err.into()),
167+
};
168+
169+
if let Err(err) = response.copy_to(&mut file) {
170+
return Err(err.into());
171+
}
172+
173+
page += 1;
174+
}
175+
176+
Ok(())
177+
}
17178
}

0 commit comments

Comments
 (0)