refactor: moved get_short_path to utils and use it in decompress

This commit is contained in:
dinhlongviolin1 2025-09-09 22:24:15 -07:00 committed by Akarshan
parent 6067ffe107
commit e2e572ccab
No known key found for this signature in database
GPG Key ID: D75C9634A870665F
6 changed files with 137 additions and 118 deletions

2
src-tauri/Cargo.lock generated
View File

@ -2325,6 +2325,7 @@ dependencies = [
"tokio", "tokio",
"tokio-util", "tokio-util",
"url", "url",
"windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -5188,7 +5189,6 @@ dependencies = [
"tauri-plugin", "tauri-plugin",
"thiserror 2.0.12", "thiserror 2.0.12",
"tokio", "tokio",
"windows-sys 0.60.2",
] ]
[[package]] [[package]]

View File

@ -25,10 +25,6 @@ thiserror = "2.0.12"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json", "blocking", "stream"] } reqwest = { version = "0.11", features = ["json", "blocking", "stream"] }
# Windows-specific dependencies
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.60.2", features = ["Win32_Storage_FileSystem"] }
# Unix-specific dependencies # Unix-specific dependencies
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { version = "=0.30.1", features = ["signal", "process"] } nix = { version = "=0.30.1", features = ["signal", "process"] }

View File

@ -3,31 +3,7 @@ use std::path::PathBuf;
use crate::error::{ErrorCode, LlamacppError, ServerResult}; use crate::error::{ErrorCode, LlamacppError, ServerResult};
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::ffi::OsStrExt; use jan_utils::path::get_short_path;
#[cfg(windows)]
use std::ffi::OsStr;
#[cfg(windows)]
use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW;
/// Get Windows short path to avoid issues with spaces and special characters
#[cfg(windows)]
pub fn get_short_path<P: AsRef<std::path::Path>>(path: P) -> Option<String> {
let wide: Vec<u16> = OsStr::new(path.as_ref())
.encode_wide()
.chain(Some(0))
.collect();
let mut buffer = vec![0u16; 260];
let len = unsafe { GetShortPathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) };
if len > 0 {
Some(String::from_utf16_lossy(&buffer[..len as usize]))
} else {
None
}
}
/// Validate that a binary path exists and is accessible /// Validate that a binary path exists and is accessible
pub fn validate_binary_path(backend_path: &str) -> ServerResult<PathBuf> { pub fn validate_binary_path(backend_path: &str) -> ServerResult<PathBuf> {
@ -259,18 +235,6 @@ mod tests {
assert!(result.is_err()); assert!(result.is_err());
} }
#[cfg(windows)]
#[test]
fn test_get_short_path() {
// Test with a real path that should exist on Windows
use std::env;
if let Ok(temp_dir) = env::var("TEMP") {
let result = get_short_path(&temp_dir);
// Should return some short path or None (both are valid)
// We can't assert the exact value as it depends on the system
println!("Short path result: {:?}", result);
}
}
#[test] #[test]
fn test_validate_model_path_multiple_m_flags() { fn test_validate_model_path_multiple_m_flags() {

View File

@ -184,6 +184,17 @@ pub fn decompress(app: tauri::AppHandle, path: &str, output_dir: &str) -> Result
) )
})?; })?;
// Use short path on Windows to handle paths with spaces
#[cfg(windows)]
let file = {
if let Some(short_path) = jan_utils::path::get_short_path(&path_buf) {
fs::File::open(&short_path).map_err(|e| e.to_string())?
} else {
fs::File::open(&path_buf).map_err(|e| e.to_string())?
}
};
#[cfg(not(windows))]
let file = fs::File::open(&path_buf).map_err(|e| e.to_string())?; let file = fs::File::open(&path_buf).map_err(|e| e.to_string())?;
if path.ends_with(".tar.gz") { if path.ends_with(".tar.gz") {
let tar = flate2::read::GzDecoder::new(file); let tar = flate2::read::GzDecoder::new(file);

View File

@ -16,6 +16,9 @@ tokio = { version = "1", features = ["process", "fs", "macros", "rt"] }
tokio-util = "0.7.14" tokio-util = "0.7.14"
url = "2.5" url = "2.5"
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.60.2", features = ["Win32_Storage_FileSystem"] }
[dev-dependencies] [dev-dependencies]
tempfile = "3.0" tempfile = "3.0"

View File

@ -1,76 +1,121 @@
#[cfg(windows)] #[cfg(windows)]
use std::path::Prefix; use std::path::Prefix;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
/// Normalizes file paths by handling path components, prefixes, and resolving relative paths #[cfg(windows)]
/// Based on: https://github.com/rust-lang/cargo/blob/rust-1.67.0/crates/cargo-util/src/paths.rs#L82-L107 use std::os::windows::ffi::OsStrExt;
pub fn normalize_path(path: &Path) -> PathBuf {
let mut components = path.components().peekable(); #[cfg(windows)]
let mut ret = if let Some(c @ Component::Prefix(_prefix_component)) = components.peek().cloned() use std::ffi::OsStr;
{
#[cfg(windows)] #[cfg(windows)]
// Remove only the Verbatim prefix, but keep the drive letter (e.g., C:\) use windows_sys::Win32::Storage::FileSystem::GetShortPathNameW;
match _prefix_component.kind() {
Prefix::VerbatimDisk(disk) => { /// Normalizes file paths by handling path components, prefixes, and resolving relative paths
components.next(); // skip this prefix /// Based on: https://github.com/rust-lang/cargo/blob/rust-1.67.0/crates/cargo-util/src/paths.rs#L82-L107
// Re-add the disk prefix (e.g., C:) pub fn normalize_path(path: &Path) -> PathBuf {
let mut pb = PathBuf::new(); let mut components = path.components().peekable();
pb.push(format!("{}:", disk as char)); let mut ret = if let Some(c @ Component::Prefix(_prefix_component)) = components.peek().cloned()
pb {
} #[cfg(windows)]
Prefix::Verbatim(_) | Prefix::VerbatimUNC(_, _) => { // Remove only the Verbatim prefix, but keep the drive letter (e.g., C:\)
components.next(); // skip this prefix match _prefix_component.kind() {
PathBuf::new() Prefix::VerbatimDisk(disk) => {
} components.next(); // skip this prefix
_ => { // Re-add the disk prefix (e.g., C:)
components.next(); let mut pb = PathBuf::new();
PathBuf::from(c.as_os_str()) pb.push(format!("{}:", disk as char));
} pb
} }
#[cfg(not(windows))] Prefix::Verbatim(_) | Prefix::VerbatimUNC(_, _) => {
{ components.next(); // skip this prefix
components.next(); // skip this prefix PathBuf::new()
PathBuf::from(c.as_os_str()) }
} _ => {
} else { components.next();
PathBuf::new() PathBuf::from(c.as_os_str())
}; }
}
for component in components { #[cfg(not(windows))]
match component { {
Component::Prefix(..) => unreachable!(), components.next(); // skip this prefix
Component::RootDir => { PathBuf::from(c.as_os_str())
ret.push(component.as_os_str()); }
} } else {
Component::CurDir => {} PathBuf::new()
Component::ParentDir => { };
ret.pop();
} for component in components {
Component::Normal(c) => { match component {
ret.push(c); Component::Prefix(..) => unreachable!(),
} Component::RootDir => {
} ret.push(component.as_os_str());
} }
ret Component::CurDir => {}
} Component::ParentDir => {
ret.pop();
/// Removes file:/ and file:\ prefixes from file paths }
pub fn normalize_file_path(path: &str) -> String { Component::Normal(c) => {
path.replace("file:/", "").replace("file:\\", "") ret.push(c);
} }
}
/// Removes prefix from path string with proper formatting }
pub fn remove_prefix(path: &str, prefix: &str) -> String { ret
if !prefix.is_empty() && path.starts_with(prefix) { }
let result = path[prefix.len()..].to_string();
if result.is_empty() { /// Removes file:/ and file:\ prefixes from file paths
"/".to_string() pub fn normalize_file_path(path: &str) -> String {
} else if result.starts_with('/') { path.replace("file:/", "").replace("file:\\", "")
result }
} else {
format!("/{}", result) /// Removes prefix from path string with proper formatting
} pub fn remove_prefix(path: &str, prefix: &str) -> String {
} else { if !prefix.is_empty() && path.starts_with(prefix) {
path.to_string() let result = path[prefix.len()..].to_string();
} if result.is_empty() {
} "/".to_string()
} else if result.starts_with('/') {
result
} else {
format!("/{}", result)
}
} else {
path.to_string()
}
}
/// Get Windows short path to avoid issues with spaces and special characters
#[cfg(windows)]
pub fn get_short_path<P: AsRef<std::path::Path>>(path: P) -> Option<String> {
let wide: Vec<u16> = OsStr::new(path.as_ref())
.encode_wide()
.chain(Some(0))
.collect();
let mut buffer = vec![0u16; 260];
let len = unsafe { GetShortPathNameW(wide.as_ptr(), buffer.as_mut_ptr(), buffer.len() as u32) };
if len > 0 {
Some(String::from_utf16_lossy(&buffer[..len as usize]))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(windows)]
#[test]
fn test_get_short_path() {
// Test with a real path that should exist on Windows
use std::env;
if let Ok(temp_dir) = env::var("TEMP") {
let result = get_short_path(&temp_dir);
// Should return some short path or None (both are valid)
// We can't assert the exact value as it depends on the system
println!("Short path result: {:?}", result);
}
}
}