use crate::ebook::archive::Archive;
use crate::ebook::element::Href;
use crate::ebook::epub::write::writer::{EpubWriter, EpubWriterContext};
use crate::ebook::resource::{Resource, ResourceKey};
use crate::util::uri;
use crate::writer::WriterResult;
use crate::writer::zip::ZipWriter;
use std::borrow::Cow;
use std::collections::HashSet;
use std::io::Write;
struct ResourceWriter<'ebook, W: Write> {
ctx: &'ebook EpubWriterContext<'ebook>,
zip: &'ebook mut ZipWriter<W>,
resources: HashSet<Cow<'ebook, ResourceKey<'ebook>>>,
}
impl<'ebook, W: Write> ResourceWriter<'ebook, W> {
fn new(
ctx: &'ebook EpubWriterContext<'ebook>,
zip: &'ebook mut ZipWriter<W>,
resources: HashSet<Cow<'ebook, ResourceKey<'ebook>>>,
) -> Self {
Self {
ctx,
zip,
resources,
}
}
fn remove_resource(&mut self, resource: impl Into<ResourceKey<'ebook>>) {
self.resources.remove(&Cow::Owned(resource.into()));
}
fn write_resources(mut self) -> WriterResult<()> {
const CONTAINER_FILE: &str = "/META-INF/container.xml";
const MIMETYPE: &str = "/mimetype";
let package_file = uri::decode(&self.ctx.epub.package.location);
self.remove_resource(package_file);
self.remove_resource(CONTAINER_FILE);
self.remove_resource(MIMETYPE);
self.write_manifest_resources()?;
self.write_orphaned_resources()
}
fn write_manifest_resources(&mut self) -> WriterResult<()> {
let archive = &self.ctx.epub.archive;
let skip_id = |id| {
let ncx = self.ctx.toc.epub2_ncx.as_ref();
let nav = self.ctx.toc.epub3_nav.as_ref();
self.ctx.config.generate_toc
&& (ncx.is_some_and(|ncx| !ncx.is_generated && id == ncx.id)
|| nav.is_some_and(|nav| !nav.is_generated && id == nav.id))
};
for (id, entry) in &self.ctx.epub.manifest.entries {
let decoded = uri::decode(&entry.href);
let path = &*decoded;
if !skip_id(id.as_str()) {
self.zip.start_file(path)?;
archive.copy_resource_decoded(&Resource::from(path), &mut self.zip)?;
}
self.resources
.remove(&Cow::Owned(ResourceKey::from(decoded)));
}
Ok(())
}
fn write_orphaned_resources(&mut self) -> WriterResult<()> {
const META_INF_DIRECTORY: &str = "/META-INF/";
let archive = &self.ctx.epub.archive;
let retain = |path| match &self.ctx.config.keep_orphans {
Some(filter) => filter.filter(Href::new(path)),
None => path.starts_with(META_INF_DIRECTORY),
};
for resource in &self.resources {
let Some(path) = resource.value() else {
continue;
};
if archive.is_overlay_resource(path) || retain(path) {
self.zip.start_file(path)?;
archive.copy_resource_decoded(&path.into(), &mut self.zip)?;
}
}
Ok(())
}
}
impl<W: Write> EpubWriter<'_, W> {
pub(super) fn write_resources(&mut self) -> WriterResult<()> {
let resources = self.ctx.epub.archive.resources()?;
ResourceWriter::new(&self.ctx, &mut self.zip, resources).write_resources()
}
}