fix: use PathBuf::join for path construction in download command

On Windows, std::fs::canonicalize returns an extended-length path
with the \?\ prefix, which does not support forward slashes.
Using format!("{}/{}", ...) to join paths then caused os error 123
(ERROR_INVALID_NAME).

Replace all string-based path concatenation with PathBuf::join so
that the OS-appropriate separator is used on every platform.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-20 11:51:10 +09:00
parent f0671c06ad
commit 1d81216c97
+18 -19
View File
@@ -4,7 +4,7 @@ use crate::commands::shared::{
};
use crate::connection::MDRSConnection;
use futures::stream::{FuturesUnordered, StreamExt};
use std::path::Path;
use std::path::PathBuf;
use std::sync::Arc;
pub async fn download(
@@ -26,7 +26,6 @@ pub async fn download(
if !local_real.is_dir() {
return Err(format!("Local directory `{}` not found.", local_path).into());
}
let local_dir_base = local_real.to_string_lossy().to_string();
// Split r_path into the parent directory path and the target basename.
// Trailing slashes are already stripped by parse_remote_path, so this is safe.
@@ -49,20 +48,20 @@ pub async fn download(
return Ok(());
}
// Python always places the downloaded file inside the local directory.
let dest = format!("{}/{}", local_dir_base, file.name);
let dest = local_real.join(&file.name);
if skip_if_exists {
if Path::new(&dest).exists() {
if dest.exists() {
if let Ok(meta) = std::fs::metadata(&dest) {
if meta.len() == file.size {
println!("{}", dest);
println!("{}", dest.display());
return Ok(());
}
}
}
}
let url = make_absolute_url(&conn, &file.download_url);
conn.download_file(&url, &dest).await?;
println!("{}", dest);
conn.download_file(&url, &dest.to_string_lossy()).await?;
println!("{}", dest.display());
return Ok(());
}
@@ -77,10 +76,10 @@ pub async fn download(
}
// Python downloads into local_path/<remote_folder_name>/ (not directly into local_path).
// We create that subdirectory first, then recurse into it.
let top_local = format!("{}/{}", local_dir_base, sub.name);
let top_local = local_real.join(&sub.name);
// Iterative DFS: each entry is (remote_folder_id, local_dir)
let mut stack: Vec<(String, String)> = vec![(sub.id.clone(), top_local)];
let mut stack: Vec<(String, PathBuf)> = vec![(sub.id.clone(), top_local)];
while let Some((folder_id, local_dir)) = stack.pop() {
let folder = conn.retrieve_folder(&folder_id).await?;
@@ -90,7 +89,7 @@ pub async fn download(
}
tokio::fs::create_dir_all(&local_dir).await?;
println!("{}", local_dir);
println!("{}", local_dir.display());
let dir_files = conn.list_all_files(&folder_id).await?;
@@ -101,12 +100,12 @@ pub async fn download(
if is_excluded(&excludes, &lab.name, &folder.path, Some(&f.name)) {
continue;
}
let dest_path = format!("{}/{}", local_dir, f.name);
let dest_path = local_dir.join(&f.name);
if skip_if_exists {
if Path::new(&dest_path).exists() {
if dest_path.exists() {
if let Ok(meta) = std::fs::metadata(&dest_path) {
if meta.len() == f.size {
println!("{}", dest_path);
println!("{}", dest_path.display());
continue;
}
}
@@ -114,13 +113,13 @@ pub async fn download(
}
let url = make_absolute_url(&conn, &f.download_url);
let conn = conn.clone();
let _fname = f.name.clone();
futs.push(tokio::spawn(async move {
match conn.download_file(&url, &dest_path).await {
Ok(_) => println!("{}", dest_path),
let dest_str = dest_path.to_string_lossy().to_string();
match conn.download_file(&url, &dest_str).await {
Ok(_) => println!("{}", dest_path.display()),
Err(_) => {
eprintln!("Failed: {}", dest_path);
if Path::new(&dest_path).is_file() {
eprintln!("Failed: {}", dest_path.display());
if dest_path.is_file() {
let _ = std::fs::remove_file(&dest_path);
}
}
@@ -144,7 +143,7 @@ pub async fn download(
None => continue,
}
}
let sub_local = format!("{}/{}", local_dir, sf.name);
let sub_local = local_dir.join(&sf.name);
stack.push((sf.id.clone(), sub_local));
}
}