Skip to content

Commit

Permalink
Merge pull request ouch-org#250 from ouch-org/extracted-file-metadata
Browse files Browse the repository at this point in the history
feat: recover last modified time when unpacking zip archives
  • Loading branch information
marcospb19 committed Feb 7, 2022
2 parents 8b1cd18 + dc931de commit 20e2044
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 2 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ tempfile = "3.2.0"
ignore = "0.4.18"
indicatif = "0.16.2"

[target.'cfg(unix)'.dependencies]
time = { version = "0.3.7", default-features = false }

[build-dependencies]
clap = { version = "3.0.4", features = ["derive", "env"] }
clap_complete = "3.0.2"
Expand Down
49 changes: 47 additions & 2 deletions src/archive/zip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ where
D: Write,
{
assert!(output_folder.read_dir().expect("dir exists").count() == 0);
let mut unpacked_files = vec![];

let mut unpacked_files = Vec::with_capacity(archive.len());

for idx in 0..archive.len() {
let mut file = archive.by_index(idx)?;
let file_path = match file.enclosed_name() {
Expand Down Expand Up @@ -67,13 +69,15 @@ where

let mut output_file = fs::File::create(&file_path)?;
io::copy(&mut file, &mut output_file)?;

#[cfg(unix)]
set_last_modified_time(&output_file, &file)?;
}
}

#[cfg(unix)]
__unix_set_permissions(&file_path, &file)?;

let file_path = fs::canonicalize(&file_path)?;
unpacked_files.push(file_path);
}

Expand Down Expand Up @@ -204,6 +208,47 @@ fn check_for_comments(file: &ZipFile) {
}
}

#[cfg(unix)]
/// Attempts to convert a [`zip::DateTime`] to a [`libc::timespec`].
fn convert_zip_date_time(date_time: zip::DateTime) -> Option<libc::timespec> {
use time::{Date, Month, PrimitiveDateTime, Time};

// Safety: time::Month is repr(u8) and goes from 1 to 12
let month: Month = unsafe { std::mem::transmute(date_time.month()) };

let date = Date::from_calendar_date(date_time.year() as _, month, date_time.day()).ok()?;

let time = Time::from_hms(date_time.hour(), date_time.minute(), date_time.second()).ok()?;

let date_time = PrimitiveDateTime::new(date, time);
let timestamp = date_time.assume_utc().unix_timestamp();

Some(libc::timespec { tv_sec: timestamp, tv_nsec: 0 })
}

#[cfg(unix)]
fn set_last_modified_time(file: &fs::File, zip_file: &ZipFile) -> crate::Result<()> {
use std::os::unix::prelude::AsRawFd;

use libc::UTIME_NOW;

let now = libc::timespec { tv_sec: 0, tv_nsec: UTIME_NOW };

let last_modified = zip_file.last_modified();
let last_modified = convert_zip_date_time(last_modified).unwrap_or(now);

// The first value is the last accessed time, which we'll set as being right now.
// The second value is the last modified time, which we'll copy over from the zip archive
let times = [now, last_modified];

let output_fd = file.as_raw_fd();

// TODO: check for -1
unsafe { libc::futimens(output_fd, &times as *const _) };

Ok(())
}

#[cfg(unix)]
fn __unix_set_permissions(file_path: &Path, file: &ZipFile) -> crate::Result<()> {
use std::{fs::Permissions, os::unix::fs::PermissionsExt};
Expand Down

0 comments on commit 20e2044

Please sign in to comment.