diff --git a/Cargo.lock b/Cargo.lock index 970005b..a0436dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -108,6 +119,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -178,12 +198,39 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "calibre-db" version = "0.1.0" @@ -201,6 +248,11 @@ name = "cc" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] [[package]] name = "cfg-if" @@ -249,6 +301,16 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.4" @@ -289,12 +351,27 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -310,6 +387,30 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -380,6 +481,12 @@ dependencies = [ "syn", ] +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + [[package]] name = "deranged" version = "0.3.11" @@ -390,6 +497,17 @@ dependencies = [ "serde", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "deunicode" version = "1.4.4" @@ -404,6 +522,18 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -424,6 +554,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "libz-ng-sys", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -629,6 +770,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "1.1.0" @@ -787,6 +937,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -799,6 +958,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -837,12 +1005,23 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-ng-sys" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6409efc61b12687963e602df8ecf70e8ddacf95bc6576bcf16e3ac6328083c5" +dependencies = [ + "cmake", + "libc", +] + [[package]] name = "little-hesinde" -version = "0.1.0" +version = "0.1.1" dependencies = [ "calibre-db", "clap", + "ignore", "once_cell", "poem", "quick-xml", @@ -858,6 +1037,7 @@ dependencies = [ "tracing", "tracing-subscriber", "uuid", + "zip", ] [[package]] @@ -876,6 +1056,16 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "memchr" version = "2.7.2" @@ -1018,6 +1208,16 @@ dependencies = [ "regex", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1520,6 +1720,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "siphasher" version = "0.3.11" @@ -1573,6 +1779,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.60" @@ -1792,6 +2004,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.17.0" @@ -2195,3 +2413,91 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed14a57c18714abaa03196c0714ed36bab969d7457f72d12fb5c2e1ced4c24ae" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap 2.2.6", + "lzma-rs", + "pbkdf2", + "rand", + "sha1", + "thiserror", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736" +dependencies = [ + "crc32fast", + "log", + "simd-adler32", + "typed-arena", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/little-hesinde/Cargo.toml b/little-hesinde/Cargo.toml index 4ec0118..024dc28 100644 --- a/little-hesinde/Cargo.toml +++ b/little-hesinde/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "little-hesinde" -version = "0.1.0" +version = "0.1.1" edition = "2021" license = { workspace = true } authors = { workspace = true } @@ -25,3 +25,7 @@ tracing = "0.1.40" tracing-subscriber = "0.3.18" uuid = { version = "1.8.0", features = ["v4", "fast-rng"] } quick-xml = { version = "0.31.0", features = ["serialize"] } + +[build-dependencies] +ignore = "0.4.22" +zip = "1.2.2" diff --git a/little-hesinde/build.rs b/little-hesinde/build.rs new file mode 100644 index 0000000..9a8e8c7 --- /dev/null +++ b/little-hesinde/build.rs @@ -0,0 +1,46 @@ +use std::{ + env, + fs::File, + io::{Read, Write}, + path::Path, +}; + +use ignore::Walk; +use zip::{write::SimpleFileOptions, CompressionMethod}; + +fn main() -> Result<(), Box> { + let out_dir = env::var("OUT_DIR")?; + let src_dir = ".."; + + let zip_path = Path::new(&out_dir).join("archive.zip"); + let zip_file = File::create(zip_path)?; + + let walkdir = Walk::new(src_dir); + let it = walkdir.into_iter(); + + let mut zip = zip::ZipWriter::new(zip_file); + + let options = SimpleFileOptions::default() + .compression_method(CompressionMethod::Zstd) + .unix_permissions(0o755); + + for entry in it { + let entry = entry?; + let path = entry.path(); + let name = path.strip_prefix(Path::new(src_dir))?; + + if path.is_file() { + zip.start_file(name.to_str().unwrap(), options)?; + let mut f = File::open(path)?; + let mut buffer = Vec::new(); + f.read_to_end(&mut buffer)?; + zip.write_all(&buffer)?; + } else if !name.as_os_str().is_empty() { + zip.add_directory(name.to_str().unwrap(), options)?; + } + } + zip.finish()?; + println!("cargo:rerun-if-changed={}", src_dir); + + Ok(()) +} diff --git a/little-hesinde/src/handlers/opds/author.rs b/little-hesinde/src/handlers/opds/author.rs index 69fbb2a..922bd22 100644 --- a/little-hesinde/src/handlers/opds/author.rs +++ b/little-hesinde/src/handlers/opds/author.rs @@ -8,6 +8,7 @@ use crate::{ data::book::Book, handlers::error::HandlerError, opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation}, + APP_NAME, }; /// Render a single author as an OPDS entry embedded in a feed. @@ -24,7 +25,7 @@ pub async fn handler(author: Author, books: Vec) -> Result Result, poem::Error> { }; let books_entry = Entry { title: "Books".to_string(), - id: "little-hesinde:books".to_string(), + id: format!("{APP_NAME}:books"), updated: now, content: Some(Content { media_type: MediaType::Text, @@ -43,7 +44,7 @@ pub async fn handler() -> Result, poem::Error> { let authors_entry = Entry { title: "Authors".to_string(), - id: "little-hesinde:authors".to_string(), + id: format!("{APP_NAME}:authors"), updated: now, content: Some(Content { media_type: MediaType::Text, @@ -61,7 +62,7 @@ pub async fn handler() -> Result, poem::Error> { let series_entry = Entry { title: "Series".to_string(), - id: "little-hesinde:series".to_string(), + id: format!("{APP_NAME}:series"), updated: now, content: Some(Content { media_type: MediaType::Text, @@ -79,7 +80,7 @@ pub async fn handler() -> Result, poem::Error> { let recents_entry = Entry { title: "Recent Additions".to_string(), - id: "little-hesinde:recentbooks".to_string(), + id: format!("{APP_NAME}:recentbooks"), updated: now, content: Some(Content { media_type: MediaType::Text, @@ -97,7 +98,7 @@ pub async fn handler() -> Result, poem::Error> { let feed = Feed::create( now, - "little-hesinde:catalog", + &format!("{APP_NAME}:catalog"), "Little Hesinde", self_link, vec![], diff --git a/little-hesinde/src/handlers/opds/recent.rs b/little-hesinde/src/handlers/opds/recent.rs index 8e36691..9f337d6 100644 --- a/little-hesinde/src/handlers/opds/recent.rs +++ b/little-hesinde/src/handlers/opds/recent.rs @@ -7,6 +7,7 @@ use crate::{ data::book::Book, handlers::error::HandlerError, opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation}, + APP_NAME, }; /// Render recent books as OPDS entries embedded in a feed. @@ -23,7 +24,7 @@ pub async fn handler(recent_books: Vec) -> Result { }; let feed = Feed::create( now, - "little-hesinde:recentbooks", + &format!("{APP_NAME}:recentbooks"), "Recent Books", self_link, vec![], diff --git a/little-hesinde/src/handlers/opds/series.rs b/little-hesinde/src/handlers/opds/series.rs index 9aeedf2..8a90360 100644 --- a/little-hesinde/src/handlers/opds/series.rs +++ b/little-hesinde/src/handlers/opds/series.rs @@ -7,6 +7,7 @@ use time::OffsetDateTime; use crate::{ handlers::error::HandlerError, opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation}, + APP_NAME, }; /// Render all series as OPDS entries embedded in a feed. @@ -31,7 +32,7 @@ pub async fn handler( }; let feed = Feed::create( now, - "little-hesinde:series", + &format!("{APP_NAME}:series"), "All Series", self_link, vec![], diff --git a/little-hesinde/src/handlers/opds/series_single.rs b/little-hesinde/src/handlers/opds/series_single.rs index 20d68ba..42377e6 100644 --- a/little-hesinde/src/handlers/opds/series_single.rs +++ b/little-hesinde/src/handlers/opds/series_single.rs @@ -8,6 +8,7 @@ use crate::{ data::book::Book, handlers::error::HandlerError, opds::{entry::Entry, feed::Feed, link::Link, media_type::MediaType, relation::Relation}, + APP_NAME, }; /// Render a single series as an OPDS entry embedded in a feed. @@ -24,7 +25,7 @@ pub async fn handler(series: Series, books: Vec) -> Result Result { + let stream = ReaderStream::new(SOURCE_ARCHIVE); + let body = Body::from_bytes_stream(stream); + + Ok(body + .with_content_type("application/zip") + .with_header( + "Content-Disposition", + format!("filename=\"{APP_NAME}-{VERSION}.zip\""), + ) + .into_response()) +} diff --git a/little-hesinde/src/lib.rs b/little-hesinde/src/lib.rs index 951b117..4983245 100644 --- a/little-hesinde/src/lib.rs +++ b/little-hesinde/src/lib.rs @@ -55,6 +55,7 @@ pub mod handlers { pub mod recent; pub mod series; pub mod series_single; + pub mod source_archive; } /// OPDS data structs. pub mod opds { @@ -69,6 +70,9 @@ pub mod opds { } pub mod templates; +pub const APP_NAME: &str = "little-hesinde"; +pub const VERSION: &str = "0.1.1"; + /// Internal marker data in lieu of a proper `Accept` header. #[derive(Debug, Clone, Copy)] pub enum Accept { @@ -106,6 +110,7 @@ pub async fn run(config: Config) -> Result<(), std::io::Error> { ) .at("/cover/:id", get(handlers::cover::handler)) .at("/book/:id/:format", get(handlers::download::handler)) + .at("/archive", get(handlers::source_archive::handler)) .nest("/static", EmbeddedFilesEndpoint::::new()) .data(Accept::Html); diff --git a/little-hesinde/src/opds/entry.rs b/little-hesinde/src/opds/entry.rs index 6686990..1505c8f 100644 --- a/little-hesinde/src/opds/entry.rs +++ b/little-hesinde/src/opds/entry.rs @@ -4,7 +4,7 @@ use calibre_db::data::{author::Author as DbAuthor, series::Series}; use serde::Serialize; use time::OffsetDateTime; -use crate::data::book::Book; +use crate::{data::book::Book, APP_NAME}; use super::{ author::Author, content::Content, link::Link, media_type::MediaType, relation::Relation, @@ -88,7 +88,7 @@ impl From for Entry { Self { title: value.name.clone(), - id: format!("little-hesinde:authors:{}", value.id), + id: format!("{APP_NAME}:authors:{}", value.id), updated: OffsetDateTime::now_utc(), content: None, author: None, @@ -112,7 +112,7 @@ impl From for Entry { Self { title: value.name.clone(), - id: format!("little-hesinde:series:{}", value.id), + id: format!("{APP_NAME}:series:{}", value.id), updated: OffsetDateTime::now_utc(), content: None, author: None, diff --git a/little-hesinde/static/style.css b/little-hesinde/static/style.css index 6a9a15b..bcb9324 100644 --- a/little-hesinde/static/style.css +++ b/little-hesinde/static/style.css @@ -40,7 +40,3 @@ nav ul li { height: 6rem; } -footer small { - display: flex; - justify-content: space-between; -} diff --git a/little-hesinde/templates/base.html b/little-hesinde/templates/base.html index b0c38cf..105d379 100644 --- a/little-hesinde/templates/base.html +++ b/little-hesinde/templates/base.html @@ -28,9 +28,10 @@