use crate::*; use std::time::SystemTime; #[must_use] pub fn copy(source_path: impl AsRef, destination_path: impl AsRef) -> WriteResult<()> { let source = get_entry(&source_path)?; let destination = get_optional_entry(&destination_path)?; let destination_type = destination.and_then(|e| Some(e.entry_type)); match (source.entry_type, destination_type) { (EntryType::File, Some(EntryType::File)) => { copy_file(source_path, destination_path)?; } (EntryType::File, Some(EntryType::Directory)) => { let destination_path = destination_path.as_ref().join(source.name); copy_file(source_path, destination_path)?; } (EntryType::File, None) => { make_parent_directory(&destination_path)?; copy_file(&source_path, &destination_path)?; } (EntryType::Directory, Some(EntryType::File)) => { // The file is replaced by a copy of the source directory remove_file(&destination_path)?; copy_directory(&source_path, &destination_path)?; } (EntryType::Directory, Some(EntryType::Directory)) => { // The destination is filled with the contents of the source directory for entry in list_directory(source_path)? { let destination_path = destination_path.as_ref().join(&entry.name); copy(entry.path, destination_path)?; } } (EntryType::Directory, None) => { // The destination is created as a copy of the source directory copy_directory(&source_path, &destination_path)?; } } Ok(()) } #[must_use] fn copy_file(source_path: impl AsRef, destination_path: impl AsRef) -> WriteResult<()> { // Only copy file if size or last modified time is different to file at destination. if let Comparison::Different(last_modified) = compare_files(&source_path, &destination_path) { let copy_result = std::fs::copy(&source_path, &destination_path); io_result_to_write_result(copy_result, &destination_path.as_ref())?; if let Some(time) = last_modified { if let Ok(dest) = std::fs::File::open(&destination_path) { let _ = dest.set_modified(time); } } } Ok(()) } #[must_use] fn copy_directory(source_path: impl AsRef, destination_path: impl AsRef) -> WriteResult<()> { for entry in list_directory(&source_path)? { let destination_path = destination_path.as_ref().join(entry.name); copy(entry.path, &destination_path)?; } Ok(()) } enum Comparison { Same, Different(Option), } fn compare_files(source_path: impl AsRef, destination_path: impl AsRef) -> Comparison { let Ok(source) = std::fs::File::open(&source_path) else { return Comparison::Different(None) }; let Ok(source_metadata) = source.metadata() else { return Comparison::Different(None) }; let Ok(source_modified) = source_metadata.modified() else { return Comparison::Different(None) }; let Ok(dest) = std::fs::File::open(&destination_path) else { return Comparison::Different(Some(source_modified)) }; let Ok(dest_metadata) = dest.metadata() else { return Comparison::Different(Some(source_modified)) }; let Ok(dest_modified) = dest_metadata.modified() else { return Comparison::Different(Some(source_modified)) }; match source_modified == dest_modified && source_metadata.len() == dest_metadata.len() { true => Comparison::Same, false => Comparison::Different(Some(source_modified)), } }