|
2 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
|
3 | 3 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
4 | 4 |
|
5 |
| -extern crate reqwest; |
6 |
| -extern crate serde; |
7 |
| -extern crate serde_xml_rs; |
8 |
| - |
9 | 5 | pub mod scanner;
|
10 | 6 | pub mod scannererror;
|
11 | 7 | 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 |
| -} |
0 commit comments