use crate::tools::tasks::TaskManager;
use crate::vfs::local::LocalFs;
use crate::vfs::smb::{SmbClient, SmbParams};
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tempfile::NamedTempFile;
pub fn generate_unique_destination_path(target: &Path) -> PathBuf {
if !target.exists() {
return target.to_path_buf();
}
let parent = target.parent().unwrap_or_else(|| Path::new(""));
let is_dir = target.is_dir();
let (stem, ext) = if is_dir {
(target.file_name().and_then(|s| s.to_str()).unwrap_or("folder"), None)
} else {
let stem = target.file_stem().and_then(|s| s.to_str()).unwrap_or("file");
let ext = target.extension().and_then(|e| e.to_str());
(stem, ext)
};
let mut counter = 1;
loop {
let new_name = match ext {
Some(e) if !e.is_empty() => format!("{} ({}).{}", stem, counter, e),
_ => format!("{} ({})", stem, counter),
};
let candidate = parent.join(&new_name);
if !candidate.exists() {
return candidate;
}
counter += 1;
if counter > 10000 {
let unique_suffix = format!("{}_{}", stem, std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_millis()).unwrap_or(0));
return parent.join(match ext {
Some(e) if !e.is_empty() => format!("{}.{}", unique_suffix, e),
_ => unique_suffix,
});
}
}
}
pub struct VfsTransfer;
impl VfsTransfer {
pub fn copy_smb_dir_to_local(
src_params: &SmbParams,
dest_local_dir: &Path,
task_manager: &TaskManager,
task_id: &str,
) -> Result<(), String> {
fs::create_dir_all(dest_local_dir).map_err(|e| format!("Failed to create local directory: {}", e))?;
let listing = SmbClient::list_dir(src_params)?;
for entry in listing.entries {
let child_subpath = if src_params.subpath.is_empty() {
entry.name.clone()
} else {
format!("{}/{}", src_params.subpath, entry.name)
};
let mut child_params = src_params.clone();
child_params.subpath = child_subpath;
let child_dest = dest_local_dir.join(&entry.name);
if entry.is_dir {
Self::copy_smb_dir_to_local(&child_params, &child_dest, task_manager, task_id)?;
} else {
SmbClient::download_to_file(&child_params, &child_dest)?;
}
}
Ok(())
}
pub fn copy_local_dir_to_smb(
src_local_dir: &Path,
dest_params: &SmbParams,
task_manager: &TaskManager,
task_id: &str,
) -> Result<(), String> {
let _ = SmbClient::mkdir(dest_params);
let read_dir = fs::read_dir(src_local_dir).map_err(|e| format!("Failed to read local dir: {}", e))?;
for entry_res in read_dir {
if let Ok(entry) = entry_res {
let name = entry.file_name().to_string_lossy().to_string();
let child_subpath = if dest_params.subpath.is_empty() {
name.clone()
} else {
format!("{}/{}", dest_params.subpath, name)
};
let mut child_params = dest_params.clone();
child_params.subpath = child_subpath;
let path = entry.path();
if path.is_dir() {
Self::copy_local_dir_to_smb(&path, &child_params, task_manager, task_id)?;
} else {
SmbClient::upload_from_file(&child_params, &path)?;
}
}
}
Ok(())
}
pub fn transfer_single_item(
src: &str,
dest_dir: &str,
is_move: bool,
paranoid: bool,
conflict_resolution: Option<&str>,
task_manager: &TaskManager,
task_id: &str,
) -> Result<Option<String>, String> {
Self::transfer_single_item_with_metrics(
src,
dest_dir,
is_move,
paranoid,
conflict_resolution,
task_manager,
task_id,
0,
1,
0,
std::time::Instant::now(),
)
}
pub fn transfer_single_item_with_metrics(
src: &str,
dest_dir: &str,
is_move: bool,
paranoid: bool,
conflict_resolution: Option<&str>,
task_manager: &TaskManager,
task_id: &str,
files_done_before: u64,
total_files: u64,
bytes_done_before: u64,
start_time: std::time::Instant,
) -> Result<Option<String>, String> {
let is_src_smb = src.starts_with("smb://");
let is_dest_smb = dest_dir.starts_with("smb://");
let is_src_sftp = src.starts_with("sftp://");
let is_dest_sftp = dest_dir.starts_with("sftp://");
let is_src_nfs = src.starts_with("nfs://");
let is_dest_nfs = dest_dir.starts_with("nfs://");
let is_src_archive = src.starts_with("archive://");
let mut verified_hash: Option<String> = None;
if is_src_archive {
let rest = src.strip_prefix("archive://").unwrap();
let (archive_file, subpath) = match rest.split_once('#') {
Some((a, s)) => (a, s),
None => (rest, ""),
};
let file_res = crate::vfs::archive::ArchiveHandler::read_archive_entry(archive_file, subpath, 0)
.map_err(|e| format!("Failed to read archive entry: {}", e))?;
use base64::Engine;
let file_bytes = if file_res.is_binary {
base64::engine::general_purpose::STANDARD.decode(&file_res.content).unwrap_or_default()
} else {
file_res.content.into_bytes()
};
let dest_path = Path::new(dest_dir);
let raw_target = if dest_path.is_dir() {
dest_path.join(&file_res.name)
} else {
dest_path.to_path_buf()
};
let target = match conflict_resolution {
Some("skip") if raw_target.exists() => return Ok(None),
Some("rename") if raw_target.exists() => generate_unique_destination_path(&raw_target),
_ => raw_target,
};
if let Some(parent) = target.parent() {
let _ = fs::create_dir_all(parent);
}
fs::write(&target, &file_bytes).map_err(|e| format!("Failed to write extracted file: {}", e))?;
if let Ok(h) = crate::vfs::checksum::calculate_sha256(&target) {
verified_hash = Some(format!("Extracted SHA-256: {}", h));
}
return Ok(verified_hash);
} else if is_src_sftp && !is_dest_sftp {
let src_params = crate::vfs::sftp::SftpClient::parse_uri(src, None, None)?;
let file_name = src_params.remote_path.rsplit('/').next().unwrap_or(&src_params.remote_path).to_string();
let dest_path = Path::new(dest_dir);
let raw_target = if dest_path.is_dir() {
dest_path.join(&file_name)
} else {
dest_path.to_path_buf()
};
let target = match conflict_resolution {
Some("skip") if raw_target.exists() => return Ok(None),
Some("rename") if raw_target.exists() => generate_unique_destination_path(&raw_target),
_ => raw_target,
};
crate::vfs::sftp::SftpClient::download_to_file(&src_params, &target)?;
if target.is_file() {
if let Ok(h) = crate::vfs::checksum::calculate_sha256(&target) {
verified_hash = Some(format!("Dest SHA-256: {}", h));
}
}
if is_move {
let _ = crate::vfs::sftp::SftpClient::delete(&src_params, false);
}
} else if !is_src_sftp && is_dest_sftp {
let src_path = Path::new(src);
if !src_path.exists() {
return Err(format!("Local source path not found: {}", src));
}
let file_name = src_path.file_name().unwrap_or_default().to_string_lossy().to_string();
let dest_params = crate::vfs::sftp::SftpClient::parse_uri(dest_dir, None, None)?;
let target_remote_path = if dest_params.remote_path.is_empty() || dest_params.remote_path == "/" {
format!("/{}", file_name)
} else {
format!("{}/{}", dest_params.remote_path.trim_end_matches('/'), file_name)
};
let mut target_params = dest_params.clone();
target_params.remote_path = target_remote_path;
if let Ok(h) = crate::vfs::checksum::calculate_sha256(src_path) {
verified_hash = Some(format!("Src SHA-256: {}", h));
}
crate::vfs::sftp::SftpClient::upload_from_file(&target_params, src_path)?;
if is_move {
let _ = LocalFs::delete_entry(src, false, None);
}
} else if is_src_nfs || is_dest_nfs {
let local_src = if is_src_nfs {
let params = crate::vfs::nfs::NfsClient::parse_uri(src)?;
let mount = crate::vfs::nfs::NfsClient::ensure_mounted(¶ms)?;
mount.join(params.subpath.trim_start_matches('/'))
} else {
Path::new(src).to_path_buf()
};
let local_dest = if is_dest_nfs {
let params = crate::vfs::nfs::NfsClient::parse_uri(dest_dir)?;
let mount = crate::vfs::nfs::NfsClient::ensure_mounted(¶ms)?;
mount.join(params.subpath.trim_start_matches('/'))
} else {
Path::new(dest_dir).to_path_buf()
};
let raw_target = if local_dest.is_dir() {
local_dest.join(local_src.file_name().unwrap_or_default())
} else {
local_dest
};
let target = match conflict_resolution {
Some("skip") if raw_target.exists() => return Ok(None),
Some("rename") if raw_target.exists() => generate_unique_destination_path(&raw_target),
_ => raw_target,
};
LocalFs::copy_file_paranoid(&local_src.to_string_lossy(), &target.to_string_lossy(), paranoid).map_err(|e| e.to_string())?;
if target.is_file() {
if let Ok(h) = crate::vfs::checksum::calculate_sha256(&target) {
verified_hash = Some(format!("SHA-256 Match: {}", h));
}
}
if is_move {
let _ = LocalFs::delete_entry(&local_src.to_string_lossy(), false, None);
}
} else if is_src_smb && !is_dest_smb {
let src_params = SmbClient::parse_uri(src, None, None)?;
let file_name = src_params.subpath.rsplit('/').next().unwrap_or(&src_params.subpath).to_string();
let dest_path = Path::new(dest_dir);
let raw_target = if dest_path.is_dir() {
dest_path.join(&file_name)
} else {
dest_path.to_path_buf()
};
let target = match conflict_resolution {
Some("skip") if raw_target.exists() => return Ok(None),
Some("rename") if raw_target.exists() => generate_unique_destination_path(&raw_target),
_ => raw_target,
};
match SmbClient::download_to_file(&src_params, &target) {
Ok(_) => {
if target.is_file() {
if let Ok(h) = crate::vfs::checksum::calculate_sha256(&target) {
verified_hash = Some(format!("Dest SHA-256: {}", h));
}
}
},
Err(e) => {
if let Ok(_listing) = SmbClient::list_dir(&src_params) {
Self::copy_smb_dir_to_local(&src_params, &target, task_manager, task_id)?;
} else {
return Err(format!("SMB download failed: {}", e));
}
}
}
if is_move {
let _ = SmbClient::delete(&src_params, false);
}
} else if !is_src_smb && is_dest_smb {
let src_path = Path::new(src);
if !src_path.exists() {
return Err(format!("Local source path not found: {}", src));
}
let file_name = src_path.file_name().unwrap_or_default().to_string_lossy().to_string();
let dest_params = SmbClient::parse_uri(dest_dir, None, None)?;
let target_subpath = if dest_params.subpath.is_empty() {
file_name
} else {
format!("{}/{}", dest_params.subpath, file_name)
};
let mut target_params = dest_params.clone();
target_params.subpath = target_subpath;
if src_path.is_dir() {
Self::copy_local_dir_to_smb(src_path, &target_params, task_manager, task_id)?;
} else {
if let Ok(h) = crate::vfs::checksum::calculate_sha256(src_path) {
verified_hash = Some(format!("Src SHA-256: {}", h));
}
SmbClient::upload_from_file(&target_params, src_path)?;
}
if is_move {
let _ = LocalFs::delete_entry(src, false, None);
}
} else if is_src_smb && is_dest_smb {
let src_params = SmbClient::parse_uri(src, None, None)?;
let file_name = src_params.subpath.rsplit('/').next().unwrap_or(&src_params.subpath).to_string();
let dest_params = SmbClient::parse_uri(dest_dir, None, None)?;
let target_subpath = if dest_params.subpath.is_empty() {
file_name
} else {
format!("{}/{}", dest_params.subpath, file_name)
};
let mut target_params = dest_params.clone();
target_params.subpath = target_subpath;
let tmp = NamedTempFile::new().map_err(|e| format!("Temp file error: {}", e))?;
SmbClient::download_to_file(&src_params, tmp.path())?;
if let Ok(h) = crate::vfs::checksum::calculate_sha256(tmp.path()) {
verified_hash = Some(format!("Stream SHA-256: {}", h));
}
SmbClient::upload_from_file(&target_params, tmp.path())?;
if is_move {
let _ = SmbClient::delete(&src_params, false);
}
} else {
let src_path = Path::new(src);
let dest_path = Path::new(dest_dir);
if !src_path.exists() {
return Err(format!("Source path not found: {}", src));
}
let file_name = src_path.file_name().unwrap_or_default();
let raw_target = if dest_path.is_dir() {
dest_path.join(file_name)
} else {
dest_path.to_path_buf()
};
if let (Ok(can_src), Ok(can_target)) = (src_path.canonicalize(), raw_target.canonicalize()) {
if can_src == can_target {
if is_move {
return Ok(None); } else {
return Ok(None);
}
}
}
let target = match conflict_resolution {
Some("skip") if raw_target.exists() => return Ok(None),
Some("rename") if raw_target.exists() => generate_unique_destination_path(&raw_target),
_ => raw_target,
};
if src_path.is_dir() {
if let Ok(can_src) = src_path.canonicalize() {
let dest_check = if target.exists() {
target.canonicalize().unwrap_or_else(|_| target.clone())
} else if let Some(parent) = target.parent() {
parent.canonicalize().map(|p| p.join(target.file_name().unwrap_or(file_name))).unwrap_or_else(|_| target.clone())
} else {
target.clone()
};
if dest_check == can_src || dest_check.starts_with(&can_src) {
return Err(format!(
"Cannot copy directory '{}' into itself or a subdirectory of itself '{}'",
src,
target.display()
));
}
}
}
let tm = task_manager.clone();
let tid = task_id.to_string();
let iname = file_name.to_string_lossy().to_string();
let mut last_progress_time = std::time::Instant::now();
let mut dir_bytes_done = 0u64;
if is_move {
if std::fs::rename(src, &target).is_err() {
if src_path.is_file() {
let hash_opt = LocalFs::copy_single_file_streaming(src_path, &target, paranoid, |_, cur_file_bytes, cur_file_total| {
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
while tm.sync_is_paused(&tid) {
std::thread::sleep(std::time::Duration::from_millis(100));
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
}
let now = std::time::Instant::now();
if now.duration_since(last_progress_time).as_millis() >= 35 || cur_file_bytes == cur_file_total {
last_progress_time = now;
let elapsed = start_time.elapsed().as_secs_f64();
let total_bytes_now = bytes_done_before + cur_file_bytes;
let current_speed = if elapsed > 0.05 {
(total_bytes_now as f64 / elapsed) as u64
} else {
0
};
tm.sync_update_stream_progress(
&tid,
Some(&iname),
cur_file_bytes,
cur_file_total,
files_done_before,
total_files,
total_bytes_now,
current_speed,
);
}
Ok(())
}).map_err(|e| e.to_string())?;
if let Some(h) = hash_opt {
verified_hash = Some(format!("SHA-256 Match: {}", h));
}
} else {
LocalFs::copy_file_paranoid_with_progress(src, &target.to_string_lossy(), paranoid, |cur_file_path, chunk_bytes, cur_bytes, cur_total| {
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
while tm.sync_is_paused(&tid) {
std::thread::sleep(std::time::Duration::from_millis(100));
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
}
dir_bytes_done += chunk_bytes;
let now = std::time::Instant::now();
if now.duration_since(last_progress_time).as_millis() >= 35 || cur_bytes == cur_total {
last_progress_time = now;
let elapsed = start_time.elapsed().as_secs_f64();
let total_bytes_now = bytes_done_before + dir_bytes_done;
let current_speed = if elapsed > 0.05 {
(total_bytes_now as f64 / elapsed) as u64
} else {
0
};
let cur_name = cur_file_path.file_name().unwrap_or_default().to_string_lossy();
tm.sync_update_stream_progress(
&tid,
Some(&cur_name),
cur_bytes,
cur_total,
files_done_before,
total_files,
total_bytes_now,
current_speed,
);
}
Ok(())
}).map_err(|e| e.to_string())?;
}
let _ = LocalFs::delete_entry(src, false, None);
} else if paranoid && target.is_file() {
if let Ok(h) = crate::vfs::checksum::calculate_sha256(&target) {
verified_hash = Some(format!("SHA-256 Match: {}", h));
}
}
} else {
if src_path.is_file() {
let hash_opt = LocalFs::copy_single_file_streaming(src_path, &target, paranoid, |_, cur_file_bytes, cur_file_total| {
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
while tm.sync_is_paused(&tid) {
std::thread::sleep(std::time::Duration::from_millis(100));
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
}
let now = std::time::Instant::now();
if now.duration_since(last_progress_time).as_millis() >= 35 || cur_file_bytes == cur_file_total {
last_progress_time = now;
let elapsed = start_time.elapsed().as_secs_f64();
let total_bytes_now = bytes_done_before + cur_file_bytes;
let current_speed = if elapsed > 0.05 {
(total_bytes_now as f64 / elapsed) as u64
} else {
0
};
tm.sync_update_stream_progress(
&tid,
Some(&iname),
cur_file_bytes,
cur_file_total,
files_done_before,
total_files,
total_bytes_now,
current_speed,
);
}
Ok(())
}).map_err(|e| e.to_string())?;
if let Some(h) = hash_opt {
verified_hash = Some(format!("SHA-256 Match: {}", h));
}
} else {
LocalFs::copy_file_paranoid_with_progress(src, &target.to_string_lossy(), paranoid, |cur_file_path, chunk_bytes, cur_bytes, cur_total| {
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
while tm.sync_is_paused(&tid) {
std::thread::sleep(std::time::Duration::from_millis(100));
if tm.sync_is_cancelled(&tid) {
return Err(std::io::Error::new(std::io::ErrorKind::Interrupted, "Transfer cancelled by user"));
}
}
dir_bytes_done += chunk_bytes;
let now = std::time::Instant::now();
if now.duration_since(last_progress_time).as_millis() >= 35 || cur_bytes == cur_total {
last_progress_time = now;
let elapsed = start_time.elapsed().as_secs_f64();
let total_bytes_now = bytes_done_before + dir_bytes_done;
let current_speed = if elapsed > 0.05 {
(total_bytes_now as f64 / elapsed) as u64
} else {
0
};
let cur_name = cur_file_path.file_name().unwrap_or_default().to_string_lossy();
tm.sync_update_stream_progress(
&tid,
Some(&cur_name),
cur_bytes,
cur_total,
files_done_before,
total_files,
total_bytes_now,
current_speed,
);
}
Ok(())
}).map_err(|e| e.to_string())?;
if paranoid && target.is_file() {
if let Ok(h) = crate::vfs::checksum::calculate_sha256(&target) {
verified_hash = Some(format!("SHA-256 Match: {}", h));
}
}
}
}
}
Ok(verified_hash)
}
pub async fn execute_batch_transfer(
task_manager: Arc<TaskManager>,
task_id: String,
sources: Vec<String>,
destination: String,
is_move: bool,
paranoid: bool,
conflict_resolution: Option<String>,
) {
let mut total_files = 0u64;
let mut total_bytes = 0u64;
for s in &sources {
if !s.starts_with("smb://") && !s.starts_with("sftp://") && !s.starts_with("nfs://") {
let p = Path::new(s);
if p.is_dir() {
let (count, bytes) = walkdir::WalkDir::new(p)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.file_type().is_file())
.fold((0u64, 0u64), |(c, b), e| {
let len = e.metadata().map(|m| m.len()).unwrap_or(0);
(c + 1, b + len)
});
total_files += if count > 0 { count } else { 1 };
total_bytes += bytes;
} else if let Ok(meta) = p.metadata() {
total_bytes += meta.len();
total_files += 1;
} else {
total_files += 1;
}
} else {
total_files += 1;
}
}
if total_files == 0 {
total_files = sources.len() as u64;
}
task_manager.set_task_totals(&task_id, total_files, total_bytes).await;
task_manager.set_paranoid(&task_id, paranoid).await;
if paranoid {
task_manager.add_log_entry(&task_id, "🛡️ TeraCopy Paranoid Integrity: ACTIVE (Full SHA-256 Hash Verification)").await;
}
let mut files_done = 0u64;
let mut verified = 0u64;
let mut bytes_done = 0u64;
let start_time = std::time::Instant::now();
for (idx, src_str) in sources.iter().enumerate() {
if task_manager.is_cancelled(&task_id).await {
task_manager.add_log_entry(&task_id, "Transfer cancelled by user").await;
return;
}
while task_manager.is_paused(&task_id).await {
tokio::time::sleep(tokio::time::Duration::from_millis(150)).await;
if task_manager.is_cancelled(&task_id).await {
return;
}
}
let item_name = src_str.rsplit('/').next().unwrap_or(src_str);
let elapsed = start_time.elapsed().as_secs_f64();
let current_speed = if elapsed > 0.05 {
(bytes_done as f64 / elapsed) as u64
} else {
0
};
task_manager.update_task_details(
&task_id,
Some(item_name),
0,
0,
files_done,
total_files,
bytes_done,
current_speed,
Some(verified),
None,
Some(&format!("Transferring item {}/{}: {}", idx + 1, sources.len(), item_name)),
).await;
match Self::transfer_single_item_with_metrics(
src_str,
&destination,
is_move,
paranoid,
conflict_resolution.as_deref(),
&task_manager,
&task_id,
files_done,
total_files,
bytes_done,
start_time,
) {
Ok(hash_opt) => {
let (item_files, item_bytes) = if Path::new(src_str).is_dir() {
let count = walkdir::WalkDir::new(src_str).into_iter().filter_map(|e| e.ok()).filter(|e| e.file_type().is_file()).count() as u64;
let bytes = walkdir::WalkDir::new(src_str).into_iter().filter_map(|e| e.ok()).filter(|e| e.file_type().is_file()).map(|e| e.metadata().map(|m| m.len()).unwrap_or(0)).sum::<u64>();
(if count > 0 { count } else { 1 }, bytes)
} else {
(1, Path::new(src_str).metadata().map(|m| m.len()).unwrap_or(0))
};
files_done += item_files;
bytes_done += item_bytes;
if hash_opt.is_some() {
verified += 1;
}
let hash_str = hash_opt.as_deref().unwrap_or("");
let log_msg = if !hash_str.is_empty() {
format!("✓ Transferred {} | {}", item_name, hash_str)
} else {
format!("✓ Transferred {}", item_name)
};
let post_elapsed = start_time.elapsed().as_secs_f64();
let post_speed = if post_elapsed > 0.05 {
(bytes_done as f64 / post_elapsed) as u64
} else {
0
};
task_manager.update_task_details(
&task_id,
Some(item_name),
1,
1,
files_done,
total_files,
bytes_done,
post_speed,
Some(verified),
hash_opt.as_deref(),
Some(&log_msg),
).await;
}
Err(e) => {
task_manager.fail_task(&task_id, &e).await;
return;
}
}
}
if paranoid && verified > 0 {
task_manager.add_log_entry(&task_id, &format!("🛡️ Paranoid Verification Complete: {}/{} files verified with SHA-256 match", verified, total_files)).await;
}
task_manager.complete_task(&task_id).await;
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn test_generate_unique_destination_path() {
let dir = tempdir().unwrap();
let file1 = dir.path().join("report.pdf");
fs::write(&file1, b"original").unwrap();
let unique1 = generate_unique_destination_path(&file1);
assert_eq!(unique1.file_name().unwrap(), "report (1).pdf");
fs::write(&unique1, b"copy 1").unwrap();
let unique2 = generate_unique_destination_path(&file1);
assert_eq!(unique2.file_name().unwrap(), "report (2).pdf");
}
#[tokio::test]
async fn test_transfer_single_item_conflict_modes() {
let dir = tempdir().unwrap();
let src_dir = dir.path().join("src");
let dest_dir = dir.path().join("dest");
fs::create_dir_all(&src_dir).unwrap();
fs::create_dir_all(&dest_dir).unwrap();
let src_file = src_dir.join("test.txt");
let dest_file = dest_dir.join("test.txt");
fs::write(&src_file, b"source content").unwrap();
fs::write(&dest_file, b"existing dest content").unwrap();
let tm = TaskManager::new();
let res = VfsTransfer::transfer_single_item(
src_file.to_str().unwrap(),
dest_dir.to_str().unwrap(),
false,
false,
Some("skip"),
&tm,
"test_task_1",
);
assert!(res.is_ok());
assert_eq!(fs::read(&dest_file).unwrap(), b"existing dest content");
let res2 = VfsTransfer::transfer_single_item(
src_file.to_str().unwrap(),
dest_dir.to_str().unwrap(),
false,
false,
Some("rename"),
&tm,
"test_task_2",
);
assert!(res2.is_ok());
let renamed = dest_dir.join("test (1).txt");
assert!(renamed.exists());
assert_eq!(fs::read(&renamed).unwrap(), b"source content");
assert_eq!(fs::read(&dest_file).unwrap(), b"existing dest content");
let res3 = VfsTransfer::transfer_single_item(
src_file.to_str().unwrap(),
dest_dir.to_str().unwrap(),
false,
false,
Some("overwrite"),
&tm,
"test_task_3",
);
assert!(res3.is_ok());
assert_eq!(fs::read(&dest_file).unwrap(), b"source content");
}
}