| 20 |
20 |
|
client::{Entry, Reachability},
|
| 21 |
21 |
|
};
|
| 22 |
22 |
|
|
| 23 |
|
- |
const PATH: &AsciiSet = &CONTROLS.add(b' ').add(b'#').add(b'?').add(b'"').add(b'<').add(b'>');
|
|
23 |
+ |
const PATH: &AsciiSet = &CONTROLS
|
|
24 |
+ |
.add(b' ')
|
|
25 |
+ |
.add(b'#')
|
|
26 |
+ |
.add(b'?')
|
|
27 |
+ |
.add(b'"')
|
|
28 |
+ |
.add(b'<')
|
|
29 |
+ |
.add(b'>');
|
| 24 |
30 |
|
|
| 25 |
31 |
|
pub(crate) fn probe(host: &str, port: u16, timeout: Duration) -> Reachability {
|
| 26 |
32 |
|
let url = format!("http://{host}:{port}/");
|
| 74 |
80 |
|
})?;
|
| 75 |
81 |
|
let page: PageInfo = serde_json::from_str(&json)
|
| 76 |
82 |
|
.map_err(|e| Error::Http(format!("failed to parse listing JSON: {e}")))?;
|
| 77 |
|
- |
Ok(page.file_list.into_iter().map(RawEntry::into_entry).collect())
|
|
83 |
+ |
Ok(page
|
|
84 |
+ |
.file_list
|
|
85 |
+ |
.into_iter()
|
|
86 |
+ |
.map(RawEntry::into_entry)
|
|
87 |
+ |
.collect())
|
| 78 |
88 |
|
}
|
| 79 |
89 |
|
|
| 80 |
90 |
|
fn agent(timeout: Duration) -> ureq::Agent {
|
| 146 |
156 |
|
}
|
| 147 |
157 |
|
|
| 148 |
158 |
|
mod base64_stub {
|
| 149 |
|
- |
const TABLE: &[u8; 64] =
|
| 150 |
|
- |
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
159 |
+ |
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
| 151 |
160 |
|
|
| 152 |
161 |
|
pub fn encode(input: impl AsRef<[u8]>) -> String {
|
| 153 |
162 |
|
let input = input.as_ref();
|
| 219 |
228 |
|
#[test]
|
| 220 |
229 |
|
fn url_encodes_spaces_and_hashes() {
|
| 221 |
230 |
|
let url = build_url("10.0.0.5", 8089, "/Document/My Notes/", Some("a b#c.pdf"));
|
| 222 |
|
- |
assert_eq!(url, "http://10.0.0.5:8089/Document/My%20Notes/a%20b%23c.pdf");
|
|
231 |
+ |
assert_eq!(
|
|
232 |
+ |
url,
|
|
233 |
+ |
"http://10.0.0.5:8089/Document/My%20Notes/a%20b%23c.pdf"
|
|
234 |
+ |
);
|
| 223 |
235 |
|
}
|
| 224 |
236 |
|
|
| 225 |
237 |
|
#[test]
|
| 232 |
244 |
|
|
| 233 |
245 |
|
#[test]
|
| 234 |
246 |
|
fn url_root() {
|
| 235 |
|
- |
assert_eq!(build_url("10.0.0.5", 8089, "/", None), "http://10.0.0.5:8089");
|
|
247 |
+ |
assert_eq!(
|
|
248 |
+ |
build_url("10.0.0.5", 8089, "/", None),
|
|
249 |
+ |
"http://10.0.0.5:8089"
|
|
250 |
+ |
);
|
| 236 |
251 |
|
}
|
| 237 |
252 |
|
|
| 238 |
253 |
|
#[test]
|
| 261 |
276 |
|
#[test]
|
| 262 |
277 |
|
fn extracts_embedded_json_blob() {
|
| 263 |
278 |
|
let html = "<html>...\n const json = '{\"fileList\":[]}'\n const webInfo...</html>";
|
| 264 |
|
- |
assert_eq!(extract_embedded_json(html).as_deref(), Some(r#"{"fileList":[]}"#));
|
|
279 |
+ |
assert_eq!(
|
|
280 |
+ |
extract_embedded_json(html).as_deref(),
|
|
281 |
+ |
Some(r#"{"fileList":[]}"#)
|
|
282 |
+ |
);
|
| 265 |
283 |
|
}
|
| 266 |
284 |
|
|
| 267 |
285 |
|
#[test]
|
| 277 |
295 |
|
fn parses_real_nomad_listing_shape() {
|
| 278 |
296 |
|
let json = r#"{"deviceName":"Supernote Nomad","fileList":[{"date":"2026-07-19 07:39","extension":"","isDirectory":true,"name":"Document","size":0,"uri":"/Document"},{"date":"2026-07-19 07:58","extension":".pdf","isDirectory":false,"name":"guide.pdf","size":14817,"uri":"/Document/guide.pdf"}]}"#;
|
| 279 |
297 |
|
let page: PageInfo = serde_json::from_str(json).unwrap();
|
| 280 |
|
- |
let entries: Vec<Entry> = page.file_list.into_iter().map(RawEntry::into_entry).collect();
|
|
298 |
+ |
let entries: Vec<Entry> = page
|
|
299 |
+ |
.file_list
|
|
300 |
+ |
.into_iter()
|
|
301 |
+ |
.map(RawEntry::into_entry)
|
|
302 |
+ |
.collect();
|
| 281 |
303 |
|
assert_eq!(entries.len(), 2);
|
| 282 |
304 |
|
assert_eq!(entries[0].name, "Document");
|
| 283 |
305 |
|
assert!(entries[0].is_dir);
|