From 4b6912435867a5831e29798cd9adb976d4bba6e6 Mon Sep 17 00:00:00 2001 From: Renovate Date: Mon, 13 Oct 2025 17:30:12 +1100 Subject: [PATCH 1/4] fix(deps): update rust crate lopdf to 0.38.0 --- Cargo.lock | 265 +++++++++++++++++++++++++++++++++++++++++++++++------ Cargo.toml | 2 +- 2 files changed, 236 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f260a51..76dc8f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,7 +145,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 7.1.3", "num-rational", "v_frame", ] @@ -167,9 +167,9 @@ checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" [[package]] name = "bitstream-io" @@ -186,6 +186,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + [[package]] name = "built" version = "0.7.4" @@ -231,6 +240,15 @@ dependencies = [ "libbz2-rs-sys", ] +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + [[package]] name = "cbz2pdf" version = "0.1.0" @@ -499,6 +517,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ecb" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" +dependencies = [ + "cipher", +] + [[package]] name = "either" version = "1.13.0" @@ -805,6 +832,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ + "block-padding", "generic-array", ] @@ -851,6 +879,35 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3590fea8e9e22d449600c9bbd481a8163bef223e4ff938e5f55899f8cf1adb93" +dependencies = [ + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", + "windows-sys 0.59.0", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + [[package]] name = "jobserver" version = "0.1.32" @@ -907,12 +964,6 @@ dependencies = [ "zlib-rs", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -952,20 +1003,33 @@ dependencies = [ [[package]] name = "lopdf" -version = "0.32.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e775e4ee264e8a87d50a9efef7b67b4aa988cf94e75630859875fc347e6c872b" +checksum = "c7184fdea2bc3cd272a1acec4030c321a8f9875e877b3f92a53f2f6033fdc289" dependencies = [ + "aes", + "bitflags", + "cbc", "chrono", + "ecb", "encoding_rs", "flate2", + "getrandom 0.3.3", + "indexmap", "itoa", - "linked-hash-map", + "jiff", "log", - "md5", - "nom", + "md-5", + "nom 8.0.0", + "nom_locate", + "rand 0.9.2", + "rangemap", "rayon", + "sha2", + "stringprep", + "thiserror 2.0.17", "time", + "ttf-parser", "weezl", ] @@ -990,10 +1054,14 @@ dependencies = [ ] [[package]] -name = "md5" -version = "0.7.0" +name = "md-5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] [[package]] name = "memchr" @@ -1052,6 +1120,26 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nom_locate" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d" +dependencies = [ + "bytecount", + "memchr", + "nom 8.0.0", +] + [[package]] name = "noop_proc_macro" version = "0.3.0" @@ -1180,9 +1268,18 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "powerfmt" @@ -1239,9 +1336,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.87" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1311,8 +1408,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1322,7 +1429,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1334,6 +1451,21 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "rangemap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" + [[package]] name = "rav1e" version = "0.7.1" @@ -1360,11 +1492,11 @@ dependencies = [ "once_cell", "paste", "profiling", - "rand", - "rand_chacha", + "rand 0.8.5", + "rand_chacha 0.3.1", "simd_helpers", "system-deps", - "thiserror", + "thiserror 1.0.64", "v_frame", "wasm-bindgen", ] @@ -1566,6 +1698,17 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1580,9 +1723,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -1669,7 +1812,16 @@ version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.64", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", ] [[package]] @@ -1683,6 +1835,17 @@ dependencies = [ "syn", ] +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tiff" version = "0.10.3" @@ -1728,6 +1891,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml" version = "0.8.19" @@ -1762,18 +1940,45 @@ dependencies = [ "winnow", ] +[[package]] +name = "ttf-parser" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-width" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 473f8c4..bbebc9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ rayon = "1.10.0" tabled = "0.20.0" walkdir = "2.5.0" zip = "6.0.0" -lopdf = "0.32.0" +lopdf = "0.38.0" [dev-dependencies] tempfile = "3.12.0" From 06d4f8351069837049c6212e3d8d9c88009d9f26 Mon Sep 17 00:00:00 2001 From: Renovate Date: Tue, 14 Oct 2025 03:30:18 +1100 Subject: [PATCH 2/4] fix(deps): update rust crate clap to v4.5.49 --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 76dc8f6..a99a0c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,9 +319,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae" +checksum = "f4512b90fa68d3a9932cea5184017c5d200f5921df706d45e853537dea51508f" dependencies = [ "clap_builder", "clap_derive", @@ -329,9 +329,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.48" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9" +checksum = "0025e98baa12e766c67ba13ff4695a887a1eba19569aad00a472546795bd6730" dependencies = [ "anstream", "anstyle", @@ -341,9 +341,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.47" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", From a16ec085b1207b619b435a1e0565b018ca77acf9 Mon Sep 17 00:00:00 2001 From: Renovate Date: Wed, 22 Oct 2025 04:00:23 +1100 Subject: [PATCH 3/4] fix(deps): update rust crate indicatif to v0.18.1 --- Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a99a0c5..5022923 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -573,7 +573,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -815,9 +815,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.18.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" +checksum = "e2e0ddd45fe8e09ee1a607920b12271f8a5528a41ecaf6e1d1440d6493315b6b" dependencies = [ "console", "portable-atomic", @@ -1581,7 +1581,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] @@ -1785,7 +1785,7 @@ dependencies = [ "getrandom 0.3.3", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.52.0", ] [[package]] From 6379e8a56b2eaac256cf859dcf61fd49158e8936 Mon Sep 17 00:00:00 2001 From: Marc Plano-Lesay Date: Sun, 26 Oct 2025 19:14:22 +1100 Subject: [PATCH 4/4] feat: support cbr reading --- .gitignore | 1 + Cargo.lock | 52 ++++++++++++++++++++++++ Cargo.toml | 3 +- src/formats/cbr.rs | 75 +++++++++++++++++++++++++++++++++++ src/formats/cbx.rs | 30 ++++++++++++++ src/formats/cbz.rs | 28 +++++-------- src/formats/mod.rs | 9 +++++ src/job.rs | 1 + tests/cbr_reader_tests.rs | 23 +++++++++++ tests/job_and_format_tests.rs | 14 ++++++- 10 files changed, 216 insertions(+), 20 deletions(-) create mode 100644 src/formats/cbr.rs create mode 100644 src/formats/cbx.rs create mode 100644 tests/cbr_reader_tests.rs diff --git a/.gitignore b/.gitignore index 3f4b997..f950e3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.DS_Store .direnv/ .idea/ /target diff --git a/Cargo.lock b/Cargo.lock index 5022923..52f0115 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,7 @@ dependencies = [ "rayon", "tabled", "tempfile", + "unrar", "walkdir", "zip", ] @@ -1991,6 +1992,29 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" +[[package]] +name = "unrar" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ec61343a630d2b50d13216dea5125e157d3fc180a7d3f447d22fe146b648fc" +dependencies = [ + "bitflags", + "regex", + "unrar_sys", + "widestring", +] + +[[package]] +name = "unrar_sys" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b77675b883cfbe6bf41e6b7a5cd6008e0a83ba497de3d96e41a064bbeead765" +dependencies = [ + "cc", + "libc", + "winapi", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -2125,6 +2149,28 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -2134,6 +2180,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index bbebc9e..6faf9b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ tabled = "0.20.0" walkdir = "2.5.0" zip = "6.0.0" lopdf = "0.38.0" +unrar = "0.5.8" +tempfile = "3.12.0" [dev-dependencies] -tempfile = "3.12.0" diff --git a/src/formats/cbr.rs b/src/formats/cbr.rs new file mode 100644 index 0000000..9650d7f --- /dev/null +++ b/src/formats/cbr.rs @@ -0,0 +1,75 @@ +use std::ffi::OsStr; +use std::fs; +use std::io::Read; +use std::path::{Path, PathBuf}; + +use anyhow::{anyhow, Result}; +use walkdir::WalkDir; + +use crate::model::Document; + +use super::{CbxReader, FormatReader}; + +pub struct CbrReader; + +impl CbxReader for CbrReader { + fn extract_images(&self, input: &Path) -> Result)>> { + let tempdir = tempfile::tempdir()?; + let dest = tempdir.path().to_path_buf(); + { + use std::env; + use unrar::Archive; + + let cwd = env::current_dir()?; + let input_str = input.to_string_lossy().to_string(); + env::set_current_dir(&dest)?; + let mut archive = Archive::new(&input_str) + .open_for_processing() + .map_err(|e| anyhow!("Failed to open RAR for processing: {e}"))?; + + loop { + match archive.read_header() { + Ok(Some(header)) => { + archive = if header.entry().is_file() { + header + .extract() + .map_err(|e| anyhow!("Failed to extract entry: {e}"))? + } else { + header + .skip() + .map_err(|e| anyhow!("Failed to skip entry: {e}"))? + }; + } + Ok(None) => break, + Err(e) => { + let _ = env::set_current_dir(cwd); + return Err(anyhow!("Failed to read RAR header: {e}")); + } + } + } + env::set_current_dir(cwd)?; + } + + let mut files: Vec<(String, Vec)> = Vec::new(); + for entry in WalkDir::new(&dest).into_iter().filter_map(Result::ok) { + let path: PathBuf = entry.path().to_path_buf(); + if path.is_file() && path.extension() == Some(OsStr::new("jpg")) { + let mut data = Vec::new(); + fs::File::open(&path)?.read_to_end(&mut data)?; + let name = path + .file_name() + .and_then(OsStr::to_str) + .map(|s| s.to_string()) + .unwrap_or_else(|| path.display().to_string()); + files.push((name, data)); + } + } + Ok(files) + } +} + +impl FormatReader for CbrReader { + fn read(&self, input: &Path) -> Result { + self.read_cbx(input) + } +} diff --git a/src/formats/cbx.rs b/src/formats/cbx.rs new file mode 100644 index 0000000..5457bef --- /dev/null +++ b/src/formats/cbx.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use std::path::Path; + +use crate::model::Document; + +// Shared reader logic for CBx (CBZ/CBR) formats +pub trait CbxReader: Send + Sync { + // Implementors should return a list of (file_name, jpeg_bytes) + fn extract_images(&self, input: &Path) -> Result)>>; + + // Build a Document from extracted JPEG bytes + fn read_cbx(&self, input: &Path) -> Result { + let files = self.extract_images(input)?; + let mut pages: Vec = Vec::new(); + { + use rayon::prelude::*; + files + .par_iter() + .map(|(name, data)| crate::model::ImagePage { + name: name.clone(), + image: image::load_from_memory(data).expect("Failed to decode image"), + jpeg_dct: Some(data.clone()), + }) + .collect_into_vec(&mut pages); + + pages.par_sort_by_key(|p| p.name.clone()); + } + Ok(Document::new(pages)) + } +} diff --git a/src/formats/cbz.rs b/src/formats/cbz.rs index 8319496..fa52f3a 100644 --- a/src/formats/cbz.rs +++ b/src/formats/cbz.rs @@ -4,17 +4,16 @@ use std::io::{Read, Write}; use std::path::Path; use anyhow::Result; -use rayon::prelude::*; use zip::ZipArchive; -use crate::model::{Document, ImagePage}; +use crate::model::Document; -use super::{FormatReader, FormatWriter}; +use super::{CbxReader, FormatReader, FormatWriter}; pub struct CbzReader; -impl FormatReader for CbzReader { - fn read(&self, input: &Path) -> Result { +impl CbxReader for CbzReader { + fn extract_images(&self, input: &Path) -> Result)>> { let mut zip = ZipArchive::new(File::open(input)?)?; let mut files: Vec<(String, Vec)> = Vec::new(); for i in 0..zip.len() { @@ -35,20 +34,13 @@ impl FormatReader for CbzReader { )); } } + Ok(files) + } +} - let mut pages: Vec = Vec::new(); - files - .par_iter() - .map(|(name, data)| ImagePage { - name: name.clone(), - image: image::load_from_memory(data).expect("Failed to decode image"), - jpeg_dct: Some(data.clone()), - }) - .collect_into_vec(&mut pages); - - pages.par_sort_by_key(|p| p.name.clone()); - - Ok(Document::new(pages)) +impl FormatReader for CbzReader { + fn read(&self, input: &Path) -> Result { + self.read_cbx(input) } } diff --git a/src/formats/mod.rs b/src/formats/mod.rs index 0bac2ce..69c4cdd 100644 --- a/src/formats/mod.rs +++ b/src/formats/mod.rs @@ -5,9 +5,14 @@ use anyhow::Result; use crate::model::Document; +pub mod cbr; +pub mod cbx; pub mod cbz; pub mod pdf; +pub use cbx::CbxReader; + +use cbr::CbrReader; use cbz::{CbzReader, CbzWriter}; use pdf::{PdfReader, PdfWriter}; @@ -15,6 +20,7 @@ use pdf::{PdfReader, PdfWriter}; pub enum FormatId { Cbz, Pdf, + Cbr, } impl FormatId { @@ -32,6 +38,7 @@ impl FormatId { match path.extension().and_then(OsStr::to_str) { Some("cbz") => Some(FormatId::Cbz), Some("pdf") => Some(FormatId::Pdf), + Some("cbr") => Some(FormatId::Cbr), _ => None, } } @@ -49,6 +56,7 @@ pub fn get_reader(format: FormatId) -> Option> { match format { FormatId::Cbz => Some(Box::new(CbzReader)), FormatId::Pdf => Some(Box::new(PdfReader)), + FormatId::Cbr => Some(Box::new(CbrReader)), } } @@ -56,5 +64,6 @@ pub fn get_writer(format: FormatId) -> Option> { match format { FormatId::Pdf => Some(Box::new(PdfWriter)), FormatId::Cbz => Some(Box::new(CbzWriter)), + FormatId::Cbr => None, } } diff --git a/src/job.rs b/src/job.rs index 7f2043d..49db051 100644 --- a/src/job.rs +++ b/src/job.rs @@ -21,6 +21,7 @@ impl Job { match to { FormatId::Pdf => output_path.set_extension("pdf"), FormatId::Cbz => output_path.set_extension("cbz"), + FormatId::Cbr => output_path.set_extension("cbr"), }; Self { diff --git a/tests/cbr_reader_tests.rs b/tests/cbr_reader_tests.rs new file mode 100644 index 0000000..c7289d9 --- /dev/null +++ b/tests/cbr_reader_tests.rs @@ -0,0 +1,23 @@ +use std::fs::File; +use std::io::Write; + +use cbz2pdf::formats::cbr::CbrReader; +use cbz2pdf::formats::FormatReader; + +// We cannot reliably create a RAR archive in tests (tools cannot create .rar), +// Instead, verify that the reader fails gracefully (returns an error) when given an invalid .cbr +// file. +#[test] +fn cbr_reader_errors_on_invalid_archive() { + let temp_dir = tempfile::tempdir().expect("create temp dir"); + let cbr_path = temp_dir.path().join("invalid.cbr"); + + // Write some junk that is definitely not a RAR archive + let mut f = File::create(&cbr_path).expect("create cbr"); + f.write_all(b"this is not a rar archive").unwrap(); + + let reader = CbrReader; + let res = reader.read(&cbr_path); + + assert!(res.is_err(), "CbrReader should error on invalid archives"); +} diff --git a/tests/job_and_format_tests.rs b/tests/job_and_format_tests.rs index 55c9742..7f572fc 100644 --- a/tests/job_and_format_tests.rs +++ b/tests/job_and_format_tests.rs @@ -7,8 +7,10 @@ use cbz2pdf::job::Job; fn detect_from_path_recognizes_extensions() { let cbz = PathBuf::from("/tmp/book.cbz"); let pdf = PathBuf::from("/tmp/book.pdf"); + let cbr = PathBuf::from("/tmp/book.cbr"); assert_eq!(FormatId::detect_from_path(&cbz), Some(FormatId::Cbz)); assert_eq!(FormatId::detect_from_path(&pdf), Some(FormatId::Pdf)); + assert_eq!(FormatId::detect_from_path(&cbr), Some(FormatId::Cbr)); assert_eq!( FormatId::detect_from_path(&PathBuf::from("/tmp/book.txt")), None @@ -24,11 +26,19 @@ fn job_new_sets_output_extension() { let job2 = Job::new( PathBuf::from("/tmp/book.pdf"), - outdir, + outdir.clone(), FormatId::Pdf, FormatId::Cbz, ); assert!(job2.output_path.ends_with("book.cbz")); + + let job3 = Job::new( + PathBuf::from("/tmp/book.cbz"), + outdir, + FormatId::Cbz, + FormatId::Cbr, + ); + assert!(job3.output_path.ends_with("book.cbr")); } #[test] @@ -37,4 +47,6 @@ fn format_capabilities_consistent() { assert!(FormatId::Cbz.can_write()); assert!(FormatId::Pdf.can_write()); assert!(FormatId::Pdf.can_read()); + assert!(FormatId::Cbr.can_read()); + assert!(!FormatId::Cbr.can_write()); }