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;


///
/// Some semi-accurate implementation of opds.
/// Works for Mango (https://github.com/hkalexling/Mango) and may work
/// for other opds supported services, but is not guaranteed.
#[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();

        // Remove trailing slash
        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>> {
        // Get response
        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()));
        }

        // Parse xml
        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 {
    /*fn cause(&self) -> Option<&dyn Error> {
        // TODO: Get this to work
        Some(&self.source_error)
    }*/
}

impl fmt::Display for OpdsEntryParsingError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:#?}", self)
    }
}