use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh}; use crate::commands::shared::{ find_file_by_name, find_folder, find_laboratory, find_subfolder_by_name, nfc, parse_remote_path, }; use crate::error::response_error; use anyhow::bail; pub async fn cp(src_path: &str, dest_path: &str, recursive: bool) -> Result<(), anyhow::Error> { let (s_remote, s_lab, s_path) = parse_remote_path(src_path)?; let dest_ends_with_slash = dest_path.ends_with('/'); let (d_remote, d_lab, d_path) = parse_remote_path(dest_path)?; if s_remote != d_remote { bail!("Remote host mismatched."); } if s_lab != d_lab { bail!("Laboratory mismatched."); } let cache = load_cache_with_token_refresh(&s_remote).await?; let conn = create_authenticated_conn(&s_remote, &cache)?; let lab = find_laboratory(&conn, Some(&cache), &s_lab).await?; let lab_id = lab.id; // Split source path into parent directory and target name let (s_dirname, s_basename_raw) = split_path(&s_path); let s_basename = nfc(&s_basename_raw); // If dest ends with '/', treat it as a directory and preserve src basename let (d_dirname, d_basename_raw) = if dest_ends_with_slash { (d_path.clone(), s_basename.clone()) } else { split_path(&d_path) }; let d_basename = nfc(&d_basename_raw); let s_parent_folder = find_folder(&conn, lab_id, &s_dirname, None).await?; let s_parent_files = conn.list_all_files(&s_parent_folder.id).await?; let d_parent_folder = find_folder(&conn, lab_id, &d_dirname, None).await?; let d_parent_files = conn.list_all_files(&d_parent_folder.id).await?; // Try source as a file first if let Some(src_file) = find_file_by_name(&s_parent_files, &s_basename) { let src_file_id = src_file.id.clone(); if find_file_by_name(&d_parent_files, &d_basename).is_some() { bail!("File `{}` already exists.", d_basename); } if find_subfolder_by_name(&d_parent_folder.sub_folders, &d_basename).is_some() { bail!( "Cannot overwrite non-folder `{}` with folder `{}`.", d_basename, d_path ); } // No-op if source and destination are identical if s_parent_folder.id == d_parent_folder.id && d_basename == s_basename { return Ok(()); } let body = serde_json::json!({"folder": d_parent_folder.id, "name": d_basename}); let resp = conn .post_json(&format!("v3/files/{}/copy/", src_file_id), &body) .await?; if !resp.status().is_success() { return Err(response_error("Copy failed", resp).await); } return Ok(()); } // Try source as a folder let src_folder = match find_subfolder_by_name(&s_parent_folder.sub_folders, &s_basename) { Some(f) => f, None => bail!("File or folder `{}` not found.", s_basename), }; if !recursive { bail!("Cannot copy `{}`: Is a folder.", s_path); } let src_folder_id = src_folder.id.clone(); if find_file_by_name(&d_parent_files, &d_basename).is_some() { bail!( "Cannot overwrite non-folder `{}` with folder `{}`.", d_basename, s_path ); } if let Some(d_folder) = find_subfolder_by_name(&d_parent_folder.sub_folders, &d_basename) { if d_folder.id == src_folder_id { bail!("`{}` and `{}` are the same folder.", s_path, s_path); } bail!( "Cannot move `{}` to `{}`: Folder not empty.", s_path, d_path ); } // No-op if source and destination are identical if s_parent_folder.id == d_parent_folder.id && s_basename == d_basename { return Ok(()); } let body = serde_json::json!({"parent": d_parent_folder.id, "name": d_basename}); let resp = conn .post_json(&format!("v3/folders/{}/copy/", src_folder_id), &body) .await?; if !resp.status().is_success() { return Err(response_error("Copy failed", resp).await); } Ok(()) } /// Split a path into (parent_dir, basename). /// e.g. "/path/to/item" -> ("/path/to", "item") fn split_path(path: &str) -> (String, String) { let path = path.trim_end_matches('/'); if let Some(pos) = path.rfind('/') { let d = if pos == 0 { "/" } else { &path[..pos] }; (d.to_string(), path[pos + 1..].to_string()) } else { ("/".to_string(), path.to_string()) } }