fix: preserve modified time when creating/extracting zip files
This commit is contained in:
parent
ba89d191d9
commit
7a90e62093
4 changed files with 81 additions and 13 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1461,6 +1461,7 @@ dependencies = [
|
|||
"dirs 6.0.0",
|
||||
"env_logger",
|
||||
"fastrand 2.3.0",
|
||||
"filetime",
|
||||
"flate2",
|
||||
"fork",
|
||||
"gio",
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ md-5 = "0.10.6"
|
|||
png = "0.18"
|
||||
jxl-oxide = { version = "0.12.5", features = ["image"] }
|
||||
num_cpus = "1.17.0"
|
||||
filetime = "0.2"
|
||||
|
||||
# Completion-based IO runtime to enable io_uring / IOCP file IO support.
|
||||
[dependencies.compio]
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ use crate::{
|
|||
mime_icon::mime_for_path,
|
||||
operation::{Controller, OpReader, OperationError, OperationErrorType, sync_to_disk},
|
||||
};
|
||||
use chrono::TimeZone;
|
||||
use chrono::{Datelike, Timelike};
|
||||
use cosmic::iced::futures;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
io::{self, Read, Write},
|
||||
path::{Path, PathBuf},
|
||||
time::SystemTime,
|
||||
};
|
||||
use zip::result::ZipError;
|
||||
|
||||
|
|
@ -143,6 +146,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
let mut target_dirs = HashSet::new();
|
||||
#[cfg(unix)]
|
||||
let mut files_by_unix_mode = Vec::with_capacity(total_files);
|
||||
let mut files_by_last_modified = Vec::with_capacity(total_files);
|
||||
|
||||
for i in 0..total_files {
|
||||
futures::executor::block_on(async {
|
||||
|
|
@ -158,12 +162,17 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
None => archive.by_index(i),
|
||||
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
||||
}?;
|
||||
|
||||
let filepath = file
|
||||
.enclosed_name()
|
||||
.ok_or(ZipError::InvalidArchive("Invalid file path".into()))?;
|
||||
|
||||
let outpath = directory.as_ref().join(filepath);
|
||||
|
||||
if let Some(last_modified) = file.last_modified() {
|
||||
files_by_last_modified.push((outpath.clone(), last_modified));
|
||||
}
|
||||
|
||||
if file.is_dir() {
|
||||
make_writable_dir_all(&outpath, &mut target_dirs)?;
|
||||
|
||||
|
|
@ -262,8 +271,47 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
|||
}
|
||||
}
|
||||
|
||||
for (path, last_modified) in files_by_last_modified {
|
||||
if let Some(modified) = zip_date_time_to_system_time(last_modified) {
|
||||
let file_time = filetime::FileTime::from_system_time(modified);
|
||||
filetime::set_file_mtime(&path, file_time)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush files to disk
|
||||
futures::executor::block_on(async { sync_to_disk(written_files, target_dirs).await });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn zip_date_time_to_system_time(date_time: zip::DateTime) -> Option<SystemTime> {
|
||||
let date = chrono::NaiveDate::from_ymd_opt(
|
||||
date_time.year() as i32,
|
||||
date_time.month() as u32,
|
||||
date_time.day() as u32,
|
||||
)?;
|
||||
let time = chrono::NaiveTime::from_hms_opt(
|
||||
date_time.hour() as u32,
|
||||
date_time.minute() as u32,
|
||||
date_time.second() as u32,
|
||||
)?;
|
||||
let naive = chrono::NaiveDateTime::new(date, time);
|
||||
chrono::Local
|
||||
.from_local_datetime(&naive)
|
||||
.latest()
|
||||
.map(SystemTime::from)
|
||||
}
|
||||
|
||||
pub fn system_time_to_zip_date_time(system_time: SystemTime) -> Option<zip::DateTime> {
|
||||
let date_time: chrono::DateTime<chrono::Local> = system_time.into();
|
||||
|
||||
zip::DateTime::from_date_and_time(
|
||||
date_time.year() as u16,
|
||||
date_time.month() as u8,
|
||||
date_time.day() as u8,
|
||||
date_time.hour() as u8,
|
||||
date_time.minute() as u8,
|
||||
date_time.second() as u8,
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{
|
||||
app::{ArchiveType, DialogPage, Message, REPLACE_BUTTON_ID},
|
||||
archive,
|
||||
config::IconSizes,
|
||||
fl,
|
||||
spawn_detached::spawn_detached,
|
||||
|
|
@ -184,7 +185,13 @@ async fn copy_or_move(
|
|||
let msg_tx = msg_tx.clone();
|
||||
context = context.on_replace(move |op, conflict_count| {
|
||||
let msg_tx = msg_tx.clone();
|
||||
Box::pin(handle_replace(msg_tx, op.from.clone(), op.to.clone(), true, conflict_count))
|
||||
Box::pin(handle_replace(
|
||||
msg_tx,
|
||||
op.from.clone(),
|
||||
op.to.clone(),
|
||||
true,
|
||||
conflict_count,
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -748,24 +755,35 @@ impl Operation {
|
|||
.map_err(|e| OperationError::from_err(e, &controller))?
|
||||
.to_str()
|
||||
{
|
||||
let mut file = fs::File::open(path).map_err(|e| {
|
||||
OperationError::from_err(e, &controller)
|
||||
})?;
|
||||
let metadata = file.metadata().map_err(|e| {
|
||||
OperationError::from_err(e, &controller)
|
||||
})?;
|
||||
|
||||
if let Ok(modified) = metadata.modified() {
|
||||
if let Some(last_modified) =
|
||||
archive::system_time_to_zip_date_time(modified)
|
||||
{
|
||||
zip_options =
|
||||
zip_options.last_modified_time(last_modified);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let mode = metadata.mode();
|
||||
zip_options = zip_options.unix_permissions(mode);
|
||||
}
|
||||
|
||||
if path.is_file() {
|
||||
let mut file = fs::File::open(path).map_err(|e| {
|
||||
OperationError::from_err(e, &controller)
|
||||
})?;
|
||||
let metadata = file.metadata().map_err(|e| {
|
||||
OperationError::from_err(e, &controller)
|
||||
})?;
|
||||
let total = metadata.len();
|
||||
if total >= 4 * 1024 * 1024 * 1024 {
|
||||
// The large file option must be enabled for files above 4 GiB
|
||||
zip_options = zip_options.large_file(true);
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
let mode = metadata.mode();
|
||||
zip_options = zip_options.unix_permissions(mode);
|
||||
}
|
||||
archive
|
||||
.start_file(relative_path, zip_options)
|
||||
.map_err(|e| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue