use std::error::Error;
use std::fmt;
use crate::opds::{OpdsClient, Link};
use image;
use image::imageops::FilterType;
use image::DynamicImage;
fn to_owned(borrowed: &Option<&str>) -> Option<String> {
match borrowed {
Some(val) => Some(String::from(val.to_owned())),
None => None
}
}
fn find_link<'a>(links: &'a Vec<Link>, filter_rel: Option<&str>, filter_type: Option<&str>) -> Result<&'a Link, LinkNotFoundError> {
for link in links.iter() {
if filter_rel.is_some() && link.rel != filter_rel.clone().unwrap() {
continue;
}
if filter_type.is_some() &&
( link.link_type.is_none() || link.link_type.clone().unwrap() != filter_type.clone().unwrap() ) {
continue;
}
return Ok(link);
}
Err(LinkNotFoundError {
filter_rel: to_owned(&filter_rel),
filter_type: to_owned(&filter_type),
candidates: links.clone()
})
}
pub struct MangoClient {
opds_client: OpdsClient
}
impl MangoClient {
pub fn new(opds_client: OpdsClient) -> MangoClient {
MangoClient { opds_client }
}
pub async fn library(&self) -> Result<Library, Box<dyn Error>> {
let result = self.opds_client.get("/opds").await?;
let mut library_entries: Vec<LibraryEntry> = Vec::new();
for entry in result.entries.unwrap().iter() {
library_entries.push(LibraryEntry {
title: entry.title.clone(),
book_path: find_link(&entry.links, Some("subsection"), None)?.href.clone()
});
}
Ok(Library {
title: result.title,
entries: library_entries
})
}
pub async fn book(&self, library_entry: &LibraryEntry) -> Result<Book, Box<dyn Error>> {
let result = self.opds_client.get(&library_entry.book_path).await?;
let mut chapters: Vec<BookChapter> = Vec::new();
for entry in result.entries.unwrap().iter() {
chapters.push(BookChapter {
title: entry.title.clone(),
thumbnail_url: format!("{}{}", self.opds_client.base_url(), find_link(&entry.links,
Some("http://opds-spec.org/image/thumbnail"),
None)?.href),
download_url: format!("{}{}", self.opds_client.base_url(), find_link(&entry.links,
Some("http://opds-spec.org/acquisition"),
Some("application/vnd.comicbook+zip"))?.href),
})
}
Ok(Book {
title: result.title.clone(),
chapters
})
}
pub async fn download_raw_thumbnail(&self, chapter: &BookChapter) -> Result<Vec<u8>, Box<dyn Error>> {
self.opds_client.get_resource(&chapter.thumbnail_url).await
}
pub async fn download_thumbnail(&self, chapter: &BookChapter, max_size: Option<(u16, u16)>) -> Result<DynamicImage, Box<dyn Error>> {
let data = self.download_raw_thumbnail(&chapter).await?;
let mut img = image::load_from_memory(&data)?;
if let Some(max_size) = max_size {
img = img.resize(max_size.0 as u32, max_size.1 as u32, FilterType::CatmullRom);
}
Ok(img)
}
pub async fn download_chapter(&self, chapter: &BookChapter) -> Result<Vec<u8>, Box<dyn Error>> {
self.opds_client.get_resource(&chapter.download_url).await
}
}
#[derive(Debug, Clone)]
pub struct Library {
pub title: String,
pub entries: Vec<LibraryEntry>
}
#[derive(Debug, Clone)]
pub struct LibraryEntry {
pub title: String,
book_path: String,
}
#[derive(Debug, Clone)]
pub struct Book {
pub title: String,
pub chapters: Vec<BookChapter>
}
#[derive(Debug, Clone)]
pub struct BookChapter {
pub title: String,
pub thumbnail_url: String,
pub download_url: String
}
#[derive(Debug)]
pub struct LinkNotFoundError {
pub filter_type: Option<String>,
pub filter_rel: Option<String>,
pub candidates: Vec<Link>
}
impl Error for LinkNotFoundError { }
impl fmt::Display for LinkNotFoundError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:#?}", self)
}
}