From 6782269bb52a349e9568a28ed6c1f43438b7bb24 Mon Sep 17 00:00:00 2001 From: A Farzat Date: Tue, 3 Mar 2026 14:16:45 +0300 Subject: Add container.xml to zip Instead of writing it to file and then reading it again, we write container.xml straight to zip, just like we did with mimetype. --- src/epub.rs | 35 +++++++++++++++++++++-------------- src/main.rs | 9 +-------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/epub.rs b/src/epub.rs index e71e3ca..e993c31 100644 --- a/src/epub.rs +++ b/src/epub.rs @@ -1,5 +1,5 @@ use crate::models::FileEntry; -use anyhow::Result; +use anyhow::{Context, Result}; use reqwest::Client; use std::{io::Write, path::Path}; use tokio::{ @@ -9,15 +9,7 @@ use tokio::{ use zip::{CompressionMethod, ZipWriter, write::FileOptions}; /// Creates and writes container.xml. -pub async fn write_container_xml(dest_root: &Path, opf_full_path: &str) -> Result<()> { - // Create destination directory. - let dest_dir = dest_root.join("META-INF"); - fs::create_dir_all(&dest_dir).await?; - - // Create distination file. - let dest_path = dest_dir.join("container.xml"); - let mut file = File::create(dest_path).await?; - +fn write_container_xml_to_zip(zip: &mut ZipWriter, opf_full_path: &str) -> Result<()> { // Prepare file contents. let contents = format!( r#" @@ -30,7 +22,10 @@ pub async fn write_container_xml(dest_root: &Path, opf_full_path: &str) -> Resul ); // Write down the file. - file.write_all(contents.as_bytes()).await?; + let options: FileOptions<()> = + FileOptions::default().compression_method(CompressionMethod::Deflated); + zip.start_file("META-INF/container.xml", options)?; + zip.write_all(contents.as_bytes())?; Ok(()) } @@ -61,14 +56,26 @@ pub async fn download_all_files( } /// Creates the EPUB archive (creates zip and includes all files in it). -pub fn create_epub_archive(epub_root: &Path, output_epub: &Path) -> Result<()> { +pub fn create_epub_archive( + epub_root: &Path, + output_epub: &Path, + file_entries: &[FileEntry], +) -> Result<()> { let out_file = std::fs::File::create(output_epub)?; let mut zip = ZipWriter::new(out_file); - let mimetype_options: FileOptions<()> = + // Write mimetype to zip first. It must be uncompressed. + let options: FileOptions<()> = FileOptions::default().compression_method(CompressionMethod::Stored); - zip.start_file("mimetype", mimetype_options)?; + zip.start_file("mimetype", options)?; zip.write_all(b"application/epub+zip")?; + // Find the OPF file entry to reference it in container.xml + let opf_entry = file_entries + .iter() + .find(|f| f.filename_ext == ".opf" && f.media_type == "application/oebps-package+xml") + .context("No OPF file with the correct MIME type was found.")?; + write_container_xml_to_zip(&mut zip, &opf_entry.full_path)?; + Ok(()) } diff --git a/src/main.rs b/src/main.rs index db1fdd4..8c5c712 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod models; use std::collections::HashMap; use std::path::Path; -use crate::epub::{download_all_files, write_container_xml}; +use crate::epub::{download_all_files,}; use crate::http_client::build_authenticated_client; use crate::models::{Chapter, EpubResponse, FileEntry, Paginated, SpineItem, TocNode}; use anyhow::{Context, Result, ensure}; @@ -112,16 +112,9 @@ async fn main() -> Result<()> { let spine_items: Vec = fetch_all_pages(&client, epub_data.spine.clone()).await?; let toc_vec: Vec = fetch_direct_array(&client, &epub_data.table_of_contents).await?; - // Find the OPF file entry to reference it in container.xml - let opf_entry = file_entries - .iter() - .find(|f| f.filename_ext == ".opf" && f.media_type == "application/oebps-package+xml") - .context("No OPF file with the correct MIME type was found.")?; - let dest_root = format!("Books/{}/epub_root", args.bookid); let dest_root = Path::new(&dest_root); download_all_files(&client, &file_entries, dest_root).await?; - write_container_xml(dest_root, &opf_entry.full_path).await?; // Sanity check: Every entry in spine exists in chapters. let chapters: HashMap = -- cgit v1.2.3-70-g09d2