From a0489b6f867cf51874fda77f212a15398f1b47af Mon Sep 17 00:00:00 2001 From: "James MacAdie (TT sandbox)" Date: Mon, 26 Feb 2024 08:30:15 +0000 Subject: [PATCH] Add remove and update file examples Per discussion #430, it was not obvious to me how to update a file within an archive. I have added some examples of how to delete and update to make it easier for anyone in a similar situation to me in the future --- examples/delete_file_from_archive.rs | 76 +++++++++++++++++++++++++ examples/update_file_in_archive.rs | 83 ++++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 examples/delete_file_from_archive.rs create mode 100644 examples/update_file_in_archive.rs diff --git a/examples/delete_file_from_archive.rs b/examples/delete_file_from_archive.rs new file mode 100644 index 000000000..000fe87c7 --- /dev/null +++ b/examples/delete_file_from_archive.rs @@ -0,0 +1,76 @@ +// See this dicussion for further background on why it is done like this: +// https://github.com/zip-rs/zip/discussions/430 + +use zip::result::{ZipError, ZipResult}; + +fn main() { + std::process::exit(real_main()); +} + +fn real_main() -> i32 { + let args: Vec<_> = std::env::args().collect(); + if args.len() < 3 { + println!( + "Usage: {} ", + args[0] + ); + return 1; + } + let filename = &*args[1]; + let file_to_remove = &*args[2]; + match remove_file(filename, file_to_remove, false) { + Ok(_) => println!("{file_to_remove} deleted from {filename}"), + Err(e) => { + eprintln!("Error: {e:?}"); + return 1; + } + } + 0 +} + +fn remove_file(archive_filename: &str, file_to_remove: &str, in_place: bool) -> ZipResult<()> { + let fname = std::path::Path::new(archive_filename); + let zipfile = std::fs::File::open(fname)?; + + let mut archive = zip::ZipArchive::new(zipfile)?; + + // Open a new, empty archive for writing to + let new_filename = replacement_filename(archive_filename.as_ref())?; + let new_file = std::fs::File::create(&new_filename)?; + let mut new_archive = zip::ZipWriter::new(new_file); + + // Loop through the original archive: + // - Skip the target file + // - Copy everything else across as raw, which saves the bother of decoding it + // The end effect is to have a new archive, which is a clone of the original, + // save for the target file which has been omitted i.e. deleted + let target: &std::path::Path = file_to_remove.as_ref(); + for i in 0..archive.len() { + let file = archive.by_index_raw(i).unwrap(); + match file.enclosed_name() { + Some(p) if p == target => (), + _ => new_archive.raw_copy_file(file)?, + } + } + new_archive.finish()?; + + drop(archive); + drop(new_archive); + + // If we're doing this in place then overwrite the original with the new + if in_place { + std::fs::rename(new_filename, archive_filename)?; + } + + Ok(()) +} + +fn replacement_filename(source: &std::path::Path) -> ZipResult { + let mut new = std::path::PathBuf::from(source); + let mut stem = source.file_stem().ok_or(ZipError::FileNotFound)?.to_owned(); + stem.push("_updated"); + new.set_file_name(stem); + let ext = source.extension().ok_or(ZipError::FileNotFound)?; + new.set_extension(ext); + Ok(new) +} diff --git a/examples/update_file_in_archive.rs b/examples/update_file_in_archive.rs new file mode 100644 index 000000000..d9113c518 --- /dev/null +++ b/examples/update_file_in_archive.rs @@ -0,0 +1,83 @@ +// See this dicussion for further background on why it is done like this: +// https://github.com/zip-rs/zip/discussions/430 + +use std::io::prelude::*; + +use zip::result::{ZipError, ZipResult}; + +fn main() { + std::process::exit(real_main()); +} + +fn real_main() -> i32 { + let args: Vec<_> = std::env::args().collect(); + if args.len() < 3 { + println!( + "Usage: {} ", + args[0] + ); + return 1; + } + let filename = &*args[1]; + let file_to_update = &*args[2]; + match update_file(filename, file_to_update, false) { + Ok(_) => println!("{file_to_update} updated in {filename}"), + Err(e) => { + eprintln!("Error: {e:?}"); + return 1; + } + } + 0 +} + +fn update_file(archive_filename: &str, file_to_update: &str, in_place: bool) -> ZipResult<()> { + let fname = std::path::Path::new(archive_filename); + let zipfile = std::fs::File::open(fname)?; + + let mut archive = zip::ZipArchive::new(zipfile)?; + + // Open a new, empty archive for writing to + let new_filename = replacement_filename(archive_filename.as_ref())?; + let new_file = std::fs::File::create(&new_filename)?; + let mut new_archive = zip::ZipWriter::new(new_file); + + // Loop through the original archive: + // - Write the target file from some bytes + // - Copy everything else across as raw, which saves the bother of decoding it + // The end effect is to have a new archive, which is a clone of the original, + // save for the target file which has been re-written + let target: &std::path::Path = file_to_update.as_ref(); + let new = b"Lorem ipsum"; + for i in 0..archive.len() { + let file = archive.by_index_raw(i).unwrap(); + match file.enclosed_name() { + Some(p) if p == target => { + new_archive.start_file(file_to_update, zip::write::FileOptions::default())?; + new_archive.write_all(new)?; + new_archive.flush()?; + } + _ => new_archive.raw_copy_file(file)?, + } + } + new_archive.finish()?; + + drop(archive); + drop(new_archive); + + // If we're doing this in place then overwrite the original with the new + if in_place { + std::fs::rename(new_filename, archive_filename)?; + } + + Ok(()) +} + +fn replacement_filename(source: &std::path::Path) -> ZipResult { + let mut new = std::path::PathBuf::from(source); + let mut stem = source.file_stem().ok_or(ZipError::FileNotFound)?.to_owned(); + stem.push("_updated"); + new.set_file_name(stem); + let ext = source.extension().ok_or(ZipError::FileNotFound)?; + new.set_extension(ext); + Ok(new) +}