bit of documentation
This commit is contained in:
parent
a41dcab889
commit
870f457f1b
47 changed files with 341 additions and 80 deletions
|
@ -1,3 +1,5 @@
|
|||
//! Bundle all functions together.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use r2d2::Pool;
|
||||
|
@ -7,12 +9,17 @@ use crate::data::{
|
|||
author::Author, book::Book, error::DataStoreError, pagination::SortOrder, series::Series,
|
||||
};
|
||||
|
||||
/// Top level calibre functions, bundling all sub functions in one place and providing secure access to
|
||||
/// the database.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Calibre {
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
}
|
||||
|
||||
impl Calibre {
|
||||
/// Open a connection to the calibre database.
|
||||
///
|
||||
/// Fail if the database file can not be opened or not be found.
|
||||
pub fn load(path: &Path) -> Result<Self, DataStoreError> {
|
||||
let manager = SqliteConnectionManager::file(path);
|
||||
let pool = r2d2::Pool::new(manager)?;
|
||||
|
@ -20,6 +27,8 @@ impl Calibre {
|
|||
Ok(Self { pool })
|
||||
}
|
||||
|
||||
/// Fetch book data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||
/// ordering by `sort_order`.
|
||||
pub fn books(
|
||||
&self,
|
||||
limit: u64,
|
||||
|
@ -30,6 +39,8 @@ impl Calibre {
|
|||
Book::multiple(&conn, limit, cursor, sort_order)
|
||||
}
|
||||
|
||||
/// Fetch author data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||
/// ordering by `sort_order`.
|
||||
pub fn authors(
|
||||
&self,
|
||||
limit: u64,
|
||||
|
@ -40,6 +51,8 @@ impl Calibre {
|
|||
Author::multiple(&conn, limit, cursor, sort_order)
|
||||
}
|
||||
|
||||
/// Fetch books for an author specified by `author_id`, paginate the books by starting at `cursor`,
|
||||
/// fetching up to an amount of `limit` and ordering by `sort_order`.
|
||||
pub fn author_books(
|
||||
&self,
|
||||
author_id: u64,
|
||||
|
@ -51,21 +64,26 @@ impl Calibre {
|
|||
Book::author_books(&conn, author_id, limit, cursor, sort_order)
|
||||
}
|
||||
|
||||
/// Get recent books up to a limit of `limit`.
|
||||
pub fn recent_books(&self, limit: u64) -> Result<Vec<Book>, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Book::recents(&conn, limit)
|
||||
}
|
||||
|
||||
/// Get a single book, specified `id`.
|
||||
pub fn scalar_book(&self, id: u64) -> Result<Book, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Book::scalar_book(&conn, id)
|
||||
}
|
||||
|
||||
/// Get the author to a book with id `id`.
|
||||
pub fn book_author(&self, id: u64) -> Result<Author, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Author::book_author(&conn, id)
|
||||
}
|
||||
|
||||
/// Fetch series data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||
/// ordering by `sort_order`.
|
||||
pub fn series(
|
||||
&self,
|
||||
limit: u64,
|
||||
|
@ -76,51 +94,61 @@ impl Calibre {
|
|||
Series::multiple(&conn, limit, cursor, sort_order)
|
||||
}
|
||||
|
||||
/// Get the series a book with id `id` is in, as well as the book's position within the series.
|
||||
pub fn book_series(&self, id: u64) -> Result<Option<(Series, f64)>, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Series::book_series(&conn, id)
|
||||
}
|
||||
|
||||
/// Get all books belonging to the series with id `id`.
|
||||
pub fn series_books(&self, id: u64) -> Result<Vec<Book>, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Book::series_books(&conn, id)
|
||||
}
|
||||
|
||||
/// Check if there are more authors before the specified cursor.
|
||||
pub fn has_previous_authors(&self, author_sort: &str) -> Result<bool, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Author::has_previous_authors(&conn, author_sort)
|
||||
}
|
||||
|
||||
/// Check if there are more authors after the specified cursor.
|
||||
pub fn has_more_authors(&self, author_sort: &str) -> Result<bool, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Author::has_more_authors(&conn, author_sort)
|
||||
}
|
||||
|
||||
/// Check if there are more books before the specified cursor.
|
||||
pub fn has_previous_books(&self, book_sort: &str) -> Result<bool, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Book::has_previous_books(&conn, book_sort)
|
||||
}
|
||||
|
||||
/// Check if there are more books after the specified cursor.
|
||||
pub fn has_more_books(&self, book_sort: &str) -> Result<bool, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Book::has_more_books(&conn, book_sort)
|
||||
}
|
||||
|
||||
/// Check if there are more series before the specified cursor.
|
||||
pub fn has_previous_series(&self, series_sort: &str) -> Result<bool, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Series::has_previous_series(&conn, series_sort)
|
||||
}
|
||||
|
||||
/// Check if there are more series after the specified cursor.
|
||||
pub fn has_more_series(&self, series_sort: &str) -> Result<bool, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Series::has_more_series(&conn, series_sort)
|
||||
}
|
||||
|
||||
/// Fetch a single author with id `id`.
|
||||
pub fn scalar_author(&self, id: u64) -> Result<Author, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Author::scalar_author(&conn, id)
|
||||
}
|
||||
|
||||
/// Fetch a single series with id `id`.
|
||||
pub fn scalar_series(&self, id: u64) -> Result<Series, DataStoreError> {
|
||||
let conn = self.pool.get()?;
|
||||
Series::scalar_series(&conn, id)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Author data.
|
||||
|
||||
use rusqlite::{named_params, Connection, Row};
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -6,10 +8,14 @@ use super::{
|
|||
pagination::{Pagination, SortOrder},
|
||||
};
|
||||
|
||||
/// Author in calibre.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Author {
|
||||
/// Id in database.
|
||||
pub id: u64,
|
||||
/// Full name.
|
||||
pub name: String,
|
||||
/// Full name for sorting.
|
||||
pub sort: String,
|
||||
}
|
||||
|
||||
|
@ -22,6 +28,8 @@ impl Author {
|
|||
})
|
||||
}
|
||||
|
||||
/// Fetch author data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||
/// ordering by `sort_order`.
|
||||
pub fn multiple(
|
||||
conn: &Connection,
|
||||
limit: u64,
|
||||
|
@ -37,6 +45,7 @@ impl Author {
|
|||
)
|
||||
}
|
||||
|
||||
/// Get the author to a book with id `id`.
|
||||
pub fn book_author(conn: &Connection, id: u64) -> Result<Self, DataStoreError> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT authors.id, authors.name, authors.sort FROM authors \
|
||||
|
@ -47,12 +56,14 @@ impl Author {
|
|||
Ok(stmt.query_row(params, Self::from_row)?)
|
||||
}
|
||||
|
||||
/// Fetch a single author with id `id`.
|
||||
pub fn scalar_author(conn: &Connection, id: u64) -> Result<Self, DataStoreError> {
|
||||
let mut stmt = conn.prepare("SELECT id, name, sort FROM authors WHERE id = (:id)")?;
|
||||
let params = named_params! { ":id": id };
|
||||
Ok(stmt.query_row(params, Self::from_row)?)
|
||||
}
|
||||
|
||||
/// Check if there are more authors before the specified cursor.
|
||||
pub fn has_previous_authors(
|
||||
conn: &Connection,
|
||||
sort_name: &str,
|
||||
|
@ -60,6 +71,7 @@ impl Author {
|
|||
Pagination::has_prev_or_more(conn, "authors", sort_name, &SortOrder::DESC)
|
||||
}
|
||||
|
||||
/// Check if there are more authors after the specified cursor.
|
||||
pub fn has_more_authors(conn: &Connection, sort_name: &str) -> Result<bool, DataStoreError> {
|
||||
Pagination::has_prev_or_more(conn, "authors", sort_name, &SortOrder::ASC)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Book data.
|
||||
|
||||
use rusqlite::{named_params, Connection, Row};
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
|
@ -7,14 +9,22 @@ use super::{
|
|||
pagination::{Pagination, SortOrder},
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
/// Book in calibre.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Book {
|
||||
/// Id in database.
|
||||
pub id: u64,
|
||||
/// Book title.
|
||||
pub title: String,
|
||||
/// Book title for sorting.
|
||||
pub sort: String,
|
||||
/// Folder of the book within the calibre library.
|
||||
pub path: String,
|
||||
/// Uuid of the book.
|
||||
pub uuid: String,
|
||||
/// When was the book last modified.
|
||||
pub last_modified: OffsetDateTime,
|
||||
/// Optional description.
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -31,6 +41,8 @@ impl Book {
|
|||
})
|
||||
}
|
||||
|
||||
/// Fetch book data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||
/// ordering by `sort_order`.
|
||||
pub fn multiple(
|
||||
conn: &Connection,
|
||||
limit: u64,
|
||||
|
@ -47,6 +59,8 @@ impl Book {
|
|||
)
|
||||
}
|
||||
|
||||
/// Fetch books for an author specified by `author_id`, paginate the books by starting at `cursor`,
|
||||
/// fetching up to an amount of `limit` and ordering by `sort_order`.
|
||||
pub fn author_books(
|
||||
conn: &Connection,
|
||||
author_id: u64,
|
||||
|
@ -66,6 +80,7 @@ impl Book {
|
|||
)
|
||||
}
|
||||
|
||||
/// Get all books belonging to the series with id `id`.
|
||||
pub fn series_books(conn: &Connection, id: u64) -> Result<Vec<Book>, DataStoreError> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT books.id, books.title, books.sort, books.path, books.uuid, books.last_modified, comments.text FROM series \
|
||||
|
@ -80,6 +95,7 @@ impl Book {
|
|||
Ok(iter.filter_map(Result::ok).collect())
|
||||
}
|
||||
|
||||
/// Get recent books up to a limit of `limit`.
|
||||
pub fn recents(conn: &Connection, limit: u64) -> Result<Vec<Self>, DataStoreError> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT books.id, books.title, books.sort, books.path, books.uuid, books.last_modified, comments.text \
|
||||
|
@ -90,6 +106,7 @@ impl Book {
|
|||
Ok(iter.filter_map(Result::ok).collect())
|
||||
}
|
||||
|
||||
/// Get a single book, specified `id`.
|
||||
pub fn scalar_book(conn: &Connection, id: u64) -> Result<Self, DataStoreError> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT books.id, books.title, books.sort, books.path, books.uuid, books.last_modified, comments.text \
|
||||
|
@ -99,10 +116,12 @@ impl Book {
|
|||
Ok(stmt.query_row(params, Self::from_row)?)
|
||||
}
|
||||
|
||||
/// Check if there are more books before the specified cursor.
|
||||
pub fn has_previous_books(conn: &Connection, sort_title: &str) -> Result<bool, DataStoreError> {
|
||||
Pagination::has_prev_or_more(conn, "books", sort_title, &SortOrder::DESC)
|
||||
}
|
||||
|
||||
/// Check if there are more books after the specified cursor.
|
||||
pub fn has_more_books(conn: &Connection, sort_title: &str) -> Result<bool, DataStoreError> {
|
||||
Pagination::has_prev_or_more(conn, "books", sort_title, &SortOrder::ASC)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
//! Error handling for calibre database access.
|
||||
|
||||
use thiserror::Error;
|
||||
use time::error::Parse;
|
||||
|
||||
/// Errors from accessing the calibre database.
|
||||
#[derive(Error, Debug)]
|
||||
#[error("data store error")]
|
||||
pub enum DataStoreError {
|
||||
/// Found no entries for the query.
|
||||
#[error("no results")]
|
||||
NoResults(rusqlite::Error),
|
||||
/// Error with SQLite.
|
||||
#[error("sqlite error")]
|
||||
SqliteError(rusqlite::Error),
|
||||
/// Error connecting to the database.
|
||||
#[error("connection error")]
|
||||
ConnectionError(#[from] r2d2::Error),
|
||||
/// Error wparsing a datetime from the database.
|
||||
#[error("failed to parse datetime")]
|
||||
DateTimeError(#[from] Parse),
|
||||
}
|
||||
|
||||
/// Convert an SQLite error into a proper NoResults one if the query
|
||||
/// returned no rows, return others as is.
|
||||
impl From<rusqlite::Error> for DataStoreError {
|
||||
fn from(error: rusqlite::Error) -> Self {
|
||||
match error {
|
||||
|
|
|
@ -1,22 +1,33 @@
|
|||
//! Cursor pagination handling.
|
||||
|
||||
use rusqlite::{named_params, Connection, Row, ToSql};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::error::DataStoreError;
|
||||
|
||||
/// How to sort query results. Signifying whether we are paginating forwards or backwards.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub enum SortOrder {
|
||||
/// Forwards
|
||||
ASC,
|
||||
/// Backwards
|
||||
DESC,
|
||||
}
|
||||
|
||||
/// Pagination data.
|
||||
pub struct Pagination<'a> {
|
||||
/// Sort by this column.
|
||||
pub sort_col: &'a str,
|
||||
/// Limit returned results.
|
||||
pub limit: u64,
|
||||
/// Where to start paginating.
|
||||
pub cursor: Option<&'a str>,
|
||||
/// Paginating forwards or backwards.
|
||||
pub sort_order: SortOrder,
|
||||
}
|
||||
|
||||
impl<'a> Pagination<'a> {
|
||||
/// Create a new pagination.
|
||||
pub fn new(
|
||||
sort_col: &'a str,
|
||||
cursor: Option<&'a str>,
|
||||
|
@ -40,6 +51,7 @@ impl<'a> Pagination<'a> {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
/// Check if there are more items forwards or backwards from `cursor` (direction specified by `sort_order`).
|
||||
pub fn has_prev_or_more(
|
||||
conn: &Connection,
|
||||
table: &str,
|
||||
|
@ -57,6 +69,7 @@ impl<'a> Pagination<'a> {
|
|||
Ok(count > 0)
|
||||
}
|
||||
|
||||
/// Paginate a statement.
|
||||
pub fn paginate<T, F>(
|
||||
&self,
|
||||
conn: &Connection,
|
||||
|
@ -77,7 +90,8 @@ impl<'a> Pagination<'a> {
|
|||
};
|
||||
|
||||
let sort_col = self.sort_col;
|
||||
// otherwise paginated statements with join will fail
|
||||
// otherwise paginated statements with join will fails, not happy with this but fine for
|
||||
// now
|
||||
let sort_col_wrapped = if let Some(index) = sort_col.find('.') {
|
||||
let right_part = &sort_col[index..];
|
||||
"t".to_owned() + right_part
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Series data.
|
||||
|
||||
use rusqlite::{named_params, Connection, Row};
|
||||
use serde::Serialize;
|
||||
|
||||
|
@ -6,10 +8,14 @@ use super::{
|
|||
pagination::{Pagination, SortOrder},
|
||||
};
|
||||
|
||||
/// Series in calibre.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Series {
|
||||
/// Id in database.
|
||||
pub id: u64,
|
||||
/// Series name.
|
||||
pub name: String,
|
||||
/// Series name for sorting.
|
||||
pub sort: String,
|
||||
}
|
||||
|
||||
|
@ -22,6 +28,8 @@ impl Series {
|
|||
})
|
||||
}
|
||||
|
||||
/// Fetch series data from calibre, starting at `cursor`, fetching up to an amount of `limit` and
|
||||
/// ordering by `sort_order`.
|
||||
pub fn multiple(
|
||||
conn: &Connection,
|
||||
limit: u64,
|
||||
|
@ -37,12 +45,14 @@ impl Series {
|
|||
)
|
||||
}
|
||||
|
||||
/// Fetch a single series with id `id`.
|
||||
pub fn scalar_series(conn: &Connection, id: u64) -> Result<Self, DataStoreError> {
|
||||
let mut stmt = conn.prepare("SELECT id, name, sort FROM series WHERE id = (:id)")?;
|
||||
let params = named_params! { ":id": id };
|
||||
Ok(stmt.query_row(params, Self::from_row)?)
|
||||
}
|
||||
|
||||
/// Get the series a book with id `id` is in, as well as the book's position within the series.
|
||||
pub fn book_series(
|
||||
conn: &Connection,
|
||||
book_id: u64,
|
||||
|
@ -68,10 +78,12 @@ impl Series {
|
|||
}
|
||||
}
|
||||
|
||||
/// Check if there are more series before the specified cursor.
|
||||
pub fn has_previous_series(conn: &Connection, sort_name: &str) -> Result<bool, DataStoreError> {
|
||||
Pagination::has_prev_or_more(conn, "series", sort_name, &SortOrder::DESC)
|
||||
}
|
||||
|
||||
/// Check if there are more series after the specified cursor.
|
||||
pub fn has_more_series(conn: &Connection, sort_name: &str) -> Result<bool, DataStoreError> {
|
||||
Pagination::has_prev_or_more(conn, "series", sort_name, &SortOrder::ASC)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
//! Read data from a calibre library, leveraging its SQLite metadata database.
|
||||
|
||||
pub mod calibre;
|
||||
/// Data structs for the calibre database.
|
||||
pub mod data {
|
||||
pub mod author;
|
||||
pub mod book;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue