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)
}
}