quick & dirty opds implementation
This commit is contained in:
parent
12341d01a6
commit
cccd3cbdc9
3 changed files with 319 additions and 18 deletions
|
@ -1,9 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use calibre_db::data::pagination::SortOrder;
|
||||
use calibre_db::data::{author::Author as DbAuthor, pagination::SortOrder};
|
||||
use poem::{
|
||||
handler,
|
||||
web::{Data, WithContentType},
|
||||
web::{Data, Path, WithContentType},
|
||||
IntoResponse,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
@ -20,6 +20,8 @@ use crate::{
|
|||
|
||||
fn create_feed(
|
||||
now: OffsetDateTime,
|
||||
id: &str,
|
||||
title: &str,
|
||||
self_link: Link,
|
||||
mut additional_links: Vec<Link>,
|
||||
entries: Vec<Entry>,
|
||||
|
@ -42,8 +44,8 @@ fn create_feed(
|
|||
links.append(&mut additional_links);
|
||||
|
||||
Feed {
|
||||
title: "rusty-library".to_string(),
|
||||
id: "rusty:catalog".to_string(),
|
||||
title: title.to_string(),
|
||||
id: id.to_string(),
|
||||
updated: now,
|
||||
icon: "favicon.ico".to_string(),
|
||||
author,
|
||||
|
@ -53,7 +55,191 @@ fn create_feed(
|
|||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn books(state: Data<&Arc<AppState>>) -> Result<WithContentType<String>, poem::Error> {
|
||||
pub async fn recents_handler(
|
||||
state: Data<&Arc<AppState>>,
|
||||
) -> Result<WithContentType<String>, poem::Error> {
|
||||
let books = state
|
||||
.calibre
|
||||
.recent_books(25)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
let books = books
|
||||
.iter()
|
||||
.filter_map(|x| Book::full_book(x, &state))
|
||||
.collect::<Vec<Book>>();
|
||||
|
||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let self_link = Link {
|
||||
href: "/opds/recent".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Myself,
|
||||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let feed = create_feed(
|
||||
now,
|
||||
"rusty:recentbooks",
|
||||
"Recent Books",
|
||||
self_link,
|
||||
vec![],
|
||||
entries,
|
||||
);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn series_single_handler(
|
||||
id: Path<u64>,
|
||||
state: Data<&Arc<AppState>>,
|
||||
) -> Result<WithContentType<String>, poem::Error> {
|
||||
let series = state
|
||||
.calibre
|
||||
.scalar_series(*id)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
let books = state
|
||||
.calibre
|
||||
.series_books(*id)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
let books = books
|
||||
.iter()
|
||||
.filter_map(|x| Book::full_book(x, &state))
|
||||
.collect::<Vec<Book>>();
|
||||
|
||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let self_link = Link {
|
||||
href: format!("/opds/series/{}", *id),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Myself,
|
||||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let feed = create_feed(
|
||||
now,
|
||||
&format!("rusty:series:{}", *id),
|
||||
&series.name,
|
||||
self_link,
|
||||
vec![],
|
||||
entries,
|
||||
);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn series_handler(
|
||||
state: Data<&Arc<AppState>>,
|
||||
) -> Result<WithContentType<String>, poem::Error> {
|
||||
let series = state
|
||||
.calibre
|
||||
.series(u32::MAX.into(), None, &SortOrder::ASC)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
|
||||
let entries: Vec<Entry> = series.into_iter().map(Entry::from).collect();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let self_link = Link {
|
||||
href: "/opds/series".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Myself,
|
||||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let feed = create_feed(
|
||||
now,
|
||||
"rusty:series",
|
||||
"All Series",
|
||||
self_link,
|
||||
vec![],
|
||||
entries,
|
||||
);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn author_handler(
|
||||
id: Path<u64>,
|
||||
state: Data<&Arc<AppState>>,
|
||||
) -> Result<WithContentType<String>, poem::Error> {
|
||||
let author = state
|
||||
.calibre
|
||||
.scalar_author(*id)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
let books = state
|
||||
.calibre
|
||||
.author_books(*id, u32::MAX.into(), None, SortOrder::ASC)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
let books = books
|
||||
.iter()
|
||||
.filter_map(|x| Book::full_book(x, &state))
|
||||
.collect::<Vec<Book>>();
|
||||
|
||||
let entries: Vec<Entry> = books.into_iter().map(Entry::from).collect();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let self_link = Link {
|
||||
href: format!("/opds/authors/{}", author.id),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Myself,
|
||||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let feed = create_feed(
|
||||
now,
|
||||
&format!("rusty:author:{}", author.id),
|
||||
&author.name,
|
||||
self_link,
|
||||
vec![],
|
||||
entries,
|
||||
);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn authors_handler(
|
||||
state: Data<&Arc<AppState>>,
|
||||
) -> Result<WithContentType<String>, poem::Error> {
|
||||
let authors: Vec<DbAuthor> = state
|
||||
.calibre
|
||||
.authors(u32::MAX.into(), None, &SortOrder::ASC)
|
||||
.map_err(HandlerError::DataError)?;
|
||||
|
||||
let entries: Vec<Entry> = authors.into_iter().map(Entry::from).collect();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let self_link = Link {
|
||||
href: "/opds/authors".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Myself,
|
||||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let feed = create_feed(
|
||||
now,
|
||||
"rusty:authors",
|
||||
"All Authors",
|
||||
self_link,
|
||||
vec![],
|
||||
entries,
|
||||
);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn books_handler(
|
||||
state: Data<&Arc<AppState>>,
|
||||
) -> Result<WithContentType<String>, poem::Error> {
|
||||
let books: Vec<Book> = state
|
||||
.calibre
|
||||
.books(u32::MAX.into(), None, &SortOrder::ASC)
|
||||
|
@ -74,14 +260,14 @@ pub async fn books(state: Data<&Arc<AppState>>) -> Result<WithContentType<String
|
|||
title: None,
|
||||
count: None,
|
||||
};
|
||||
let feed = create_feed(now, self_link, vec![], entries);
|
||||
let feed = create_feed(now, "rusty:books", "All Books", self_link, vec![], entries);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
}
|
||||
|
||||
#[handler]
|
||||
pub async fn handler(state: Data<&Arc<AppState>>) -> Result<WithContentType<String>, poem::Error> {
|
||||
pub async fn handler() -> Result<WithContentType<String>, poem::Error> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
||||
let self_link = Link {
|
||||
|
@ -95,10 +281,10 @@ pub async fn handler(state: Data<&Arc<AppState>>) -> Result<WithContentType<Stri
|
|||
title: "Books".to_string(),
|
||||
id: "rusty:books".to_string(),
|
||||
updated: now,
|
||||
content: Content {
|
||||
content: Some(Content {
|
||||
media_type: MediaType::Text,
|
||||
content: "Index of all books".to_string(),
|
||||
},
|
||||
}),
|
||||
author: None,
|
||||
links: vec![Link {
|
||||
href: "/opds/books".to_string(),
|
||||
|
@ -109,7 +295,68 @@ pub async fn handler(state: Data<&Arc<AppState>>) -> Result<WithContentType<Stri
|
|||
}],
|
||||
};
|
||||
|
||||
let feed = create_feed(now, self_link, vec![], vec![books_entry]);
|
||||
let authors_entry = Entry {
|
||||
title: "Authors".to_string(),
|
||||
id: "rusty:authors".to_string(),
|
||||
updated: now,
|
||||
content: Some(Content {
|
||||
media_type: MediaType::Text,
|
||||
content: "Index of all authors".to_string(),
|
||||
}),
|
||||
author: None,
|
||||
links: vec![Link {
|
||||
href: "/opds/authors".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Subsection,
|
||||
title: None,
|
||||
count: None,
|
||||
}],
|
||||
};
|
||||
|
||||
let series_entry = Entry {
|
||||
title: "Series".to_string(),
|
||||
id: "rusty:series".to_string(),
|
||||
updated: now,
|
||||
content: Some(Content {
|
||||
media_type: MediaType::Text,
|
||||
content: "Index of all series".to_string(),
|
||||
}),
|
||||
author: None,
|
||||
links: vec![Link {
|
||||
href: "/opds/series".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Subsection,
|
||||
title: None,
|
||||
count: None,
|
||||
}],
|
||||
};
|
||||
|
||||
let recents_entry = Entry {
|
||||
title: "Recent Additions".to_string(),
|
||||
id: "rusty:recentbooks".to_string(),
|
||||
updated: now,
|
||||
content: Some(Content {
|
||||
media_type: MediaType::Text,
|
||||
content: "Recently added books".to_string(),
|
||||
}),
|
||||
author: None,
|
||||
links: vec![Link {
|
||||
href: "/opds/recent".to_string(),
|
||||
media_type: MediaType::Navigation,
|
||||
rel: Relation::Subsection,
|
||||
title: None,
|
||||
count: None,
|
||||
}],
|
||||
};
|
||||
|
||||
let feed = create_feed(
|
||||
now,
|
||||
"rusty:catalog",
|
||||
"Rusty-Library",
|
||||
self_link,
|
||||
vec![],
|
||||
vec![authors_entry, series_entry, books_entry, recents_entry],
|
||||
);
|
||||
let xml = feed.as_xml().map_err(HandlerError::OpdsError)?;
|
||||
|
||||
Ok(xml.with_content_type("application/atom+xml"))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue