diff --git a/Cargo.lock b/Cargo.lock index 4cf885a..367dcb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,6 +150,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.5.0" @@ -181,18 +187,6 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" -[[package]] -name = "bytemuck" -version = "1.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - [[package]] name = "bytes" version = "1.6.0" @@ -245,9 +239,9 @@ dependencies = [ [[package]] name = "chrono-tz" -version = "0.9.0" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb" +checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" dependencies = [ "chrono", "chrono-tz-build", @@ -256,9 +250,9 @@ dependencies = [ [[package]] name = "chrono-tz-build" -version = "0.3.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c088aee841df9c3041febbb73934cfc39708749bf96dc827e3359cd39ef11b1" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" dependencies = [ "parse-zoneinfo", "phf", @@ -267,9 +261,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -277,9 +271,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -289,9 +283,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", @@ -453,12 +447,6 @@ dependencies = [ "syn", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "equivalent" version = "1.0.1" @@ -613,11 +601,11 @@ dependencies = [ [[package]] name = "globwalk" -version = "0.9.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "ignore", "walkdir", ] @@ -844,20 +832,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "image" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" -dependencies = [ - "bytemuck", - "byteorder", - "num-traits", - "rayon", - "zune-core", - "zune-jpeg", -] - [[package]] name = "indexmap" version = "1.9.3" @@ -938,12 +912,11 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "little-hesinde" -version = "0.3.0" +version = "0.2.3" dependencies = [ "calibre-db", "clap", "ignore", - "image", "once_cell", "poem", "quick-xml", @@ -951,7 +924,6 @@ dependencies = [ "serde", "serde_json", "serde_with", - "sha2", "tera", "thiserror", "time", @@ -1033,7 +1005,7 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags", + "bitflags 2.5.0", "cfg-if", "cfg_aliases", "libc", @@ -1236,9 +1208,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "poem" -version = "3.0.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88b6912ed1e8833d7c22c9c986c517f4518d7d37e3c04566d917c789aaea591" +checksum = "8b735eaaaa6bc7ed2dcbcab1d5373afe1f6d03a37d8695ba3c42101f733a8455" dependencies = [ "bytes", "futures-util", @@ -1384,33 +1356,13 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "redox_syscall" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags", + "bitflags 2.5.0", ] [[package]] @@ -1457,7 +1409,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ - "bitflags", + "bitflags 2.5.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1468,9 +1420,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.4.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19549741604902eb99a7ed0ee177a0663ee1eda51a29f71401f166e47e77806a" +checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -1479,9 +1431,9 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.4.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9f96e283ec64401f30d3df8ee2aaeb2561f34c824381efa24a35f79bf40ee4" +checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" dependencies = [ "proc-macro2", "quote", @@ -1492,9 +1444,9 @@ dependencies = [ [[package]] name = "rust-embed-utils" -version = "8.4.0" +version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c74a686185620830701348de757fd36bef4aa9680fd23c49fc539ddcc1af32" +checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" dependencies = [ "sha2", "walkdir", @@ -1512,7 +1464,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1551,18 +1503,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.200" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" dependencies = [ "proc-macro2", "quote", @@ -1571,9 +1523,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -1755,9 +1707,9 @@ dependencies = [ [[package]] name = "tera" -version = "1.20.0" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" dependencies = [ "chrono", "chrono-tz", @@ -1838,9 +1790,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.38.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1856,9 +1808,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -2046,9 +1998,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.9.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", "rand", @@ -2386,18 +2338,3 @@ dependencies = [ "once_cell", "simd-adler32", ] - -[[package]] -name = "zune-core" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" - -[[package]] -name = "zune-jpeg" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" -dependencies = [ - "zune-core", -] diff --git a/Cargo.toml b/Cargo.toml index 44a1952..dcf3448 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ members = [ ] [workspace.dependencies] -serde = "1.0.203" -thiserror = "1.0.61" +serde = "1.0.200" +thiserror = "1.0.59" time = { version = "0.3.36", features = ["macros", "serde", "formatting", "parsing" ] } [workspace.package] diff --git a/Containerfile b/Containerfile index d82e1ec..ae22d10 100644 --- a/Containerfile +++ b/Containerfile @@ -1,8 +1,5 @@ FROM docker.io/rust:1-alpine3.20 AS builder -RUN mkdir /tmp/tmp -RUN echo "hesinde:x:2222:2222:Linux User,,,:/:/app" > /passwd - RUN apk --no-cache add \ musl-dev @@ -16,16 +13,12 @@ COPY . . RUN cargo build --release --target=$(arch)-unknown-linux-musl RUN cp "./target/$(arch)-unknown-linux-musl/release/little-hesinde" /app + FROM scratch -COPY --from=builder /passwd /etc/passwd COPY --from=builder /app /app -COPY --from=builder --chown=2222: /tmp/tmp /tmp +CMD ["/app", "--listen-address", "[::]:3000", "--", "/library"] -USER hesinde - -CMD ["/app", "--listen-address", "[::]:3000", "--cache-path", "/tmp/cache", "--", "/library"] - -ENV TMPDIR=/tmp -VOLUME ["/library", "/tmp"] +ENV TMPDIR=/ +VOLUME ["/library"] EXPOSE 3000 diff --git a/README.md b/README.md index 92316fc..26ba9f5 100644 --- a/README.md +++ b/README.md @@ -37,17 +37,12 @@ From there on `cargo run` and `cargo build` and so on can be used. Usage: little-hesinde [OPTIONS] -- Arguments: - Calibre library path [env: LIBRARY_PATH=] + Calibre library path Options: - -l, --listen-address - Address to listen on [env: LISTEN_ADDRESS=] [default: [::1]:3000] - -c, --cache-path - Cache path ($TMP cascades through $XDG_CACHE_HOME, $TMPDIR and /tmp) [env: CACHE_PATH=] [default: $TMP/little-hesinde] - -h, --help - Print help - -V, --version - Print version + -l, --listen-address Address to listen on [default: [::1]:3000] + -h, --help Print help + -V, --version Print version ``` Example: `little-hesinde -l [::]4000 -- ~/Documents/library/` diff --git a/calibre-db/src/calibre.rs b/calibre-db/src/calibre.rs index 1dd1164..94bfd11 100644 --- a/calibre-db/src/calibre.rs +++ b/calibre-db/src/calibre.rs @@ -1,16 +1,15 @@ //! Bundle all functions together. -use std::path::{Path, PathBuf}; +use std::path::Path; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; -use tempfile::NamedTempFile; use crate::{ data::{ author::Author, book::Book, error::DataStoreError, pagination::SortOrder, series::Series, }, - search::search, + search::{self, search}, }; /// Top level calibre functions, bundling all sub functions in one place and providing secure access to @@ -18,7 +17,6 @@ use crate::{ #[derive(Debug, Clone)] pub struct Calibre { pool: Pool, - search_db_path: PathBuf, } impl Calibre { @@ -29,20 +27,16 @@ impl Calibre { let manager = SqliteConnectionManager::file(path); let pool = r2d2::Pool::new(manager)?; - let tmpfile = NamedTempFile::new()?; - let (_, search_db_path) = tmpfile.keep()?; + search::attach(&pool)?; - Ok(Self { - pool, - search_db_path, - }) + Ok(Self { pool }) } /// Full text search with a query. /// /// See https://www.sqlite.org/fts5.html#full_text_query_syntax for syntax. pub fn search(&self, query: &str) -> Result, DataStoreError> { - search(query, &self.pool, &self.search_db_path) + search(query, &self.pool) } /// Fetch book data from calibre, starting at `cursor`, fetching up to an amount of `limit` and diff --git a/calibre-db/src/search.rs b/calibre-db/src/search.rs index 8c6ae9a..0d61d51 100644 --- a/calibre-db/src/search.rs +++ b/calibre-db/src/search.rs @@ -5,11 +5,10 @@ //! virtual table leveraging fts5 (https://www.sqlite.org/fts5.html). Full-text search is run on //! that virtual table. -use std::path::Path; - use r2d2::{Pool, PooledConnection}; use r2d2_sqlite::SqliteConnectionManager; use rusqlite::named_params; +use tempfile::NamedTempFile; use crate::data::{book::Book, error::DataStoreError}; @@ -33,53 +32,28 @@ const SEARCH_INIT_QUERY: &str = "INSERT INTO search.fts(book_id, data) LEFT JOIN main.series AS s ON b2s.series = s.id GROUP BY b.id"; -/// Ensure the search database is attached to the connection and -/// initializes the data if needed. -fn ensure_search_db( - conn: &PooledConnection, - db_path: &Path, -) -> Result<(), DataStoreError> { - let mut stmt = - conn.prepare("SELECT COUNT() FROM pragma_database_list WHERE name = 'search'")?; - let count: u64 = stmt.query_row([], |x| x.get(0))?; - let need_attachment = count == 0; - - if need_attachment { - attach(conn, db_path)?; - init(conn)?; - } - - Ok(()) -} - /// Attach the fts temporary database to the read-only calibre database. -fn attach( - conn: &PooledConnection, - db_path: &Path, -) -> Result<(), DataStoreError> { +pub(crate) fn attach(pool: &Pool) -> Result<(), DataStoreError> { + let conn = pool.get()?; + let tmpfile = NamedTempFile::new()?; + let (_, path) = tmpfile.keep()?; + conn.execute( - &format!("ATTACH DATABASE '{}' AS search", db_path.to_string_lossy()), + &format!("ATTACH DATABASE '{}' AS search", path.to_string_lossy()), [], )?; - init(conn)?; + init(&conn)?; Ok(()) } /// Initialise the fts virtual table. fn init(conn: &PooledConnection) -> Result<(), DataStoreError> { - let mut stmt = conn - .prepare("SELECT COUNT() FROM search.sqlite_master WHERE type='table' AND name = 'fts'")?; - let count: u64 = stmt.query_row([], |x| x.get(0))?; - let need_init = count == 0; - - if need_init { - conn.execute( - "CREATE VIRTUAL TABLE search.fts USING fts5(book_id, data)", - [], - )?; - conn.execute(SEARCH_INIT_QUERY, [])?; - } + conn.execute( + "CREATE VIRTUAL TABLE search.fts USING fts5(book_id, data)", + [], + )?; + conn.execute(SEARCH_INIT_QUERY, [])?; Ok(()) } @@ -88,10 +62,8 @@ fn init(conn: &PooledConnection) -> Result<(), DataStor pub(crate) fn search( query: &str, pool: &Pool, - search_db_path: &Path, ) -> Result, DataStoreError> { let conn = pool.get()?; - ensure_search_db(&conn, search_db_path)?; let mut stmt = conn.prepare("SELECT book_id FROM search.fts WHERE data MATCH (:query) ORDER BY rank")?; diff --git a/little-hesinde/Cargo.toml b/little-hesinde/Cargo.toml index 06a5985..417ee5a 100644 --- a/little-hesinde/Cargo.toml +++ b/little-hesinde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "little-hesinde" -version = "0.3.0" +version = "0.2.3" edition = "2021" license = { workspace = true } authors = { workspace = true } @@ -9,23 +9,21 @@ description = "A very simple ebook server for a calibre library, providing a htm [dependencies] calibre-db = { path = "../calibre-db/", version = "0.1.0" } -clap = { version = "4.5.7", features = ["derive", "env"] } -image = { version = "0.25.1", default-features = false, features = ["jpeg", "rayon"] } +clap = { version = "4.5.4", features = ["derive"] } once_cell = "1.19.0" -poem = { version = "3.0.1", features = ["embed", "static-files"] } -rust-embed = "8.4.0" -sha2 = "0.10.8" +poem = { version = "3.0.0", features = ["embed", "static-files"] } +rust-embed = "8.3.0" serde = { workspace = true } -serde_json = "1.0.118" +serde_json = "1.0.116" serde_with = "3.8.1" -tera = "1.20.0" +tera = "1.19.1" thiserror = { workspace = true } time = { workspace = true } -tokio = { version = "1.38.0", features = ["signal", "rt-multi-thread", "macros"] } +tokio = { version = "1.37.0", features = ["signal", "rt-multi-thread", "macros"] } tokio-util = "0.7.11" tracing = "0.1.40" tracing-subscriber = "0.3.18" -uuid = { version = "1.9.1", features = ["v4", "fast-rng"] } +uuid = { version = "1.8.0", features = ["v4", "fast-rng"] } quick-xml = { version = "0.34.0", features = ["serialize"] } [build-dependencies] diff --git a/little-hesinde/src/cache.rs b/little-hesinde/src/cache.rs deleted file mode 100644 index ddf01e2..0000000 --- a/little-hesinde/src/cache.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Handle caching of files, specifically book covers. - -use std::{ - fs::{self, File}, - path::{Path, PathBuf}, -}; - -use sha2::{ - digest::{generic_array::GenericArray, typenum::U32}, - Digest, Sha256, -}; -use std::fmt::Write; -use thiserror::Error; -use tracing::debug; - -/// Errors from dealing with file caching. -#[derive(Error, Debug)] -pub enum CacheError { - /// Error converting a hash to its string representation. - #[error("failed to access thumbnail")] - HashError(#[from] std::fmt::Error), - /// Error creating a thumbnail for an image.. - #[error("failed to create thumbnail")] - ImageError(#[from] image::ImageError), - /// Error accessing a thumbnail. - #[error("failed to access thumbnail")] - ThumbnailAccessError(#[from] std::io::Error), - /// Error accessing thumbnail directories. - #[error("failed to access thumbnail directory")] - ThumbnailPathError(PathBuf), -} - -/// Convert a hash into its path representation inside the cache directory. -/// -/// First hash character is the top folder, second character the second level folder and the rest -/// is the filename. -fn hash_to_path(hash: GenericArray, cache_path: &Path) -> Result { - let mut hash_string = String::new(); - for byte in hash { - write!(&mut hash_string, "{:02x}", byte)?; - } - let hash = hash_string; - - let first_segment = &hash[0..1]; - let second_segment = &hash[1..2]; - let remaining_segment = &hash[2..]; - - Ok(PathBuf::from(cache_path) - .join(first_segment) - .join(second_segment) - .join(remaining_segment)) -} - -fn create_thumbnail(cover_path: &Path, thumbnail_path: &Path) -> Result<(), CacheError> { - debug!("creating thumbnail for {}", cover_path.to_string_lossy()); - let folders = thumbnail_path - .parent() - .ok_or_else(|| CacheError::ThumbnailPathError(thumbnail_path.to_path_buf()))?; - fs::create_dir_all(folders)?; - - const THUMBNAIL_SIZE: u32 = 512; - let img = image::open(cover_path)?; - let thumbnail = img.thumbnail(THUMBNAIL_SIZE, THUMBNAIL_SIZE); - thumbnail.save_with_format(thumbnail_path, image::ImageFormat::Jpeg)?; - debug!("saved thumbnail to {}", thumbnail_path.to_string_lossy()); - - Ok(()) -} - -/// Get the thumbnail for a book cover. -/// -/// If a thumbnail does not yet exist, create it. -pub fn get_thumbnail(cover_path: &Path, cache_path: &Path) -> Result { - let path_str = cover_path.to_string_lossy(); - let mut hasher = Sha256::new(); - hasher.update(path_str.as_bytes()); - let hash = hasher.finalize(); - - let thumbnail_path = hash_to_path(hash, cache_path)?; - - if !thumbnail_path.exists() { - create_thumbnail(cover_path, &thumbnail_path)?; - } - - Ok(File::open(thumbnail_path)?) -} diff --git a/little-hesinde/src/cli.rs b/little-hesinde/src/cli.rs index 924f3c1..8002b29 100644 --- a/little-hesinde/src/cli.rs +++ b/little-hesinde/src/cli.rs @@ -7,12 +7,9 @@ use clap::Parser; #[command(version, about, long_about = None)] pub struct Cli { /// Address to listen on - #[arg(short, long, env, default_value = "[::1]:3000")] + #[arg(short, long, default_value = "[::1]:3000")] pub listen_address: String, - /// Cache path ($TMP cascades through $XDG_CACHE_HOME, $TMPDIR and /tmp) - #[arg(short, long, env, default_value = "$TMP/little-hesinde")] - pub cache_path: String, /// Calibre library path - #[arg(env, last = true)] + #[arg(last = true)] pub library_path: String, } diff --git a/little-hesinde/src/config.rs b/little-hesinde/src/config.rs index 19f94a3..546d833 100644 --- a/little-hesinde/src/config.rs +++ b/little-hesinde/src/config.rs @@ -1,14 +1,12 @@ //! Configuration data. use std::{ - env, fs, io, net::SocketAddr, net::ToSocketAddrs, path::{Path, PathBuf}, }; use thiserror::Error; -use tracing::info; use crate::cli::Cli; @@ -24,9 +22,6 @@ pub enum ConfigError { /// Error converting a string to a listening address. #[error("failed to convert into listening address")] ListeningAddressError(String), - /// Error accessing the configured cache path. - #[error("failed to access cache path")] - CachePathError(#[from] io::Error), } /// Application configuration. @@ -38,8 +33,6 @@ pub struct Config { pub metadata_path: PathBuf, /// Address to listen on. pub listen_address: SocketAddr, - /// Path to data like thumbnails. - pub cache_path: PathBuf, } impl Config { @@ -76,21 +69,10 @@ impl Config { args.listen_address.clone(), ))?; - let cache_path = if args.cache_path.starts_with("$TMP") { - let cache_base = env::var("XDG_CACHE_HOME") - .unwrap_or_else(|_| env::var("TMPDIR").unwrap_or("/tmp/".to_string())); - PathBuf::from(&cache_base).join("little-hesinde") - } else { - PathBuf::from(&args.cache_path) - }; - fs::create_dir_all(&cache_path)?; - info!("Using {} for cache", cache_path.to_string_lossy()); - Ok(Self { library_path, metadata_path, listen_address, - cache_path, }) } } diff --git a/little-hesinde/src/handlers/cover.rs b/little-hesinde/src/handlers/cover.rs index 53c7c0c..a97514b 100644 --- a/little-hesinde/src/handlers/cover.rs +++ b/little-hesinde/src/handlers/cover.rs @@ -1,74 +1,24 @@ //! Handle requests for cover images. -use std::{fs::File, path::Path as FilePath, sync::Arc}; +use std::sync::Arc; -use crate::{ - app_state::AppState, - cache::{self, CacheError}, - config::Config, - handlers::error::HandlerError, -}; -use calibre_db::calibre::Calibre; +use crate::{app_state::AppState, handlers::error::HandlerError}; use poem::{ error::NotFoundError, handler, web::{headers::ContentType, Data, Path}, Response, }; -use thiserror::Error; -use tokio::fs::File as AsyncFile; - -/// Errors from fetching cover images. -#[derive(Error, Debug)] -pub enum CoverError { - /// Error fetching a cover thumbnail. - #[error("failed to access thumbnail")] - ThumbnailError(#[from] CacheError), - /// Error fetching a full cover. - #[error("failed access cover")] - FullCoverError(#[from] std::io::Error), -} - -/// Handle a request for the cover thumbnail of book with id `id`. -#[handler] -pub async fn handler_thumbnail( - id: Path, - state: Data<&Arc>, -) -> Result { - cover( - &state.calibre, - &state.config, - *id, - |cover_path, cache_path| Ok(cache::get_thumbnail(cover_path, cache_path)?), - ) - .await -} +use tokio::fs::File; /// Handle a request for the cover image of book with id `id`. #[handler] -pub async fn handler_full( - id: Path, - state: Data<&Arc>, -) -> Result { - cover(&state.calibre, &state.config, *id, |cover_path, _| { - Ok(File::open(cover_path)?) - }) - .await -} - -async fn cover( - calibre: &Calibre, - config: &Config, - id: u64, - f: F, -) -> Result -where - F: Fn(&FilePath, &FilePath) -> Result, -{ - let book = calibre.scalar_book(id).map_err(HandlerError::DataError)?; - let cover_path = config.library_path.join(book.path).join("cover.jpg"); - - let cover = f(&cover_path, &config.cache_path).map_err(|_| NotFoundError)?; - let cover = AsyncFile::from_std(cover); +pub async fn handler(id: Path, state: Data<&Arc>) -> Result { + let book = state + .calibre + .scalar_book(*id) + .map_err(HandlerError::DataError)?; + let cover_path = state.config.library_path.join(book.path).join("cover.jpg"); + let mut cover = File::open(cover_path).await.map_err(|_| NotFoundError)?; crate::handlers::download::handler("cover.jpg", cover, &ContentType::jpeg().to_string()).await } diff --git a/little-hesinde/src/lib.rs b/little-hesinde/src/lib.rs index 3bdc615..95b1ebc 100644 --- a/little-hesinde/src/lib.rs +++ b/little-hesinde/src/lib.rs @@ -16,7 +16,6 @@ use tokio::signal; use tracing::info; pub mod app_state; -pub mod cache; pub mod cli; pub mod config; /// Data structs and their functions. @@ -77,7 +76,7 @@ pub mod opds { pub mod templates; pub const APP_NAME: &str = "little-hesinde"; -pub const VERSION: &str = "0.3.0"; +pub const VERSION: &str = "0.2.3"; /// Internal marker data in lieu of a proper `Accept` header. #[derive(Debug, Clone, Copy)] @@ -117,11 +116,7 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> { "/authors/:cursor/:sort_order", get(handlers::authors::handler), ) - .at("/cover/:id", get(handlers::cover::handler_full)) - .at( - "/cover/:id/thumbnail", - get(handlers::cover::handler_thumbnail), - ) + .at("/cover/:id", get(handlers::cover::handler)) .at("/book/:id/:format", get(handlers::books::handler_download)) .at("/archive", get(handlers::source_archive::handler)) .at("/search", get(handlers::search::handler)) @@ -147,7 +142,7 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> { .with(Tracing); let server = Server::new(TcpListener::bind(config.listen_address)) - .name("little-hesinde") + .name("cops-web") .run(app); tokio::select! { diff --git a/little-hesinde/templates/book_card.html b/little-hesinde/templates/book_card.html index 1549cb4..db491eb 100644 --- a/little-hesinde/templates/book_card.html +++ b/little-hesinde/templates/book_card.html @@ -14,7 +14,7 @@ {% endif %} - book cover + book cover