1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use std::error::Error;
use strerror::prelude::*;
use reqwest;
use reqwest::header::AUTHORIZATION;
use base64;
use serde;
use serde::Deserialize;
use serde_xml_rs;
use std::fmt;
use bytes::Bytes;
#[derive(Debug)]
pub struct OpdsClient {
base_url: String,
client: reqwest::Client,
authorization_value: String
}
impl OpdsClient {
pub fn new(base_url: &str, username: &str, password: &str) -> OpdsClient {
let mut base_url_no_trailing_slash = base_url.to_owned();
if base_url_no_trailing_slash.ends_with("/") {
base_url_no_trailing_slash.pop();
}
let auth_base64 = base64::encode(format!("{}:{}", username, password).as_bytes());
let client = reqwest::ClientBuilder::new().cookie_store(true).build().expect("Failed to build client");
OpdsClient {
base_url: base_url_no_trailing_slash,
client,
authorization_value: format!("Basic {}", auth_base64)
}
}
pub fn base_url(&self) -> &str {
&self.base_url
}
pub async fn get(&self, path: &str) -> Result<OpdsEntry, Box<dyn Error>> {
let response = self.client.get(&format!("{}{}", self.base_url, path).replace("//", "/"))
.header(AUTHORIZATION, &self.authorization_value)
.send().await?.error_for_status()?;
let raw_xml = response.text().await?;
if ! path.starts_with("/") {
return Err(Box::new("Only paths starting with / are currently supported.".into_error()));
}
match serde_xml_rs::from_str(&raw_xml) {
Ok(entry) => Ok(entry),
Err(e) => Err(Box::new(OpdsEntryParsingError{
xml: raw_xml,
source_error: Box::new(e)
}))
}
}
pub async fn get_resource(&self, url: &str) -> Result<Vec<u8>, Box<dyn Error>> {
let response = self.client.get(url)
.header(AUTHORIZATION, &self.authorization_value)
.send().await?.error_for_status()?;
let data: Bytes = response.bytes().await?;
Ok(data.to_vec())
}
}
#[derive(Deserialize, Debug)]
pub struct OpdsEntry {
pub title: String,
pub id: String,
pub author: Option<Author>,
#[serde(rename = "link")]
pub links: Vec<Link>,
#[serde(rename = "entry")]
pub entries: Option<Vec<OpdsEntry>>
}
#[derive(Deserialize, Debug)]
pub struct Author {
pub name: String,
pub uri: String
}
#[derive(Deserialize, Debug, Clone)]
pub struct Link {
#[serde(rename="type")]
pub link_type: Option<String>,
pub rel: String,
pub href: String
}
#[derive(Debug)]
struct OpdsEntryParsingError {
source_error: Box<dyn Error>,
xml: String
}
impl Error for OpdsEntryParsingError {
}
impl fmt::Display for OpdsEntryParsingError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#?}", self)
}
}