use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RemoteFileItem {
pub name: String,
pub is_dir: bool,
pub size_bytes: u64,
pub permissions: String,
pub modified: String,
}
#[tauri::command]
pub async fn list_remote_directory(
server: String,
path: String,
state: tauri::State<'_, crate::vault::VaultState>,
) -> Result<Vec<RemoteFileItem>, String> {
crate::vault::ensure_unlocked(&state)?;
let sb_ssh = which::which("sb-ssh")
.or_else(|_| {
dirs::home_dir().map(|h| h.join(".cargo").join("bin").join(if cfg!(windows) { "sb-ssh.exe" } else { "sb-ssh" }))
.filter(|p| p.exists())
.ok_or("Nicht gefunden")
})
.map_err(|_| "sb-ssh Binary nicht gefunden".to_string())?;
let cmd = format!("ls -la --time-style=\"+%Y-%m-%d %H:%M\" \"{}\"", path);
let output = tokio::process::Command::new(&sb_ssh)
.args([&server, &cmd])
.output()
.await
.map_err(|e| format!("Fehler beim Abrufen der Verzeichnisliste: {}", e))?;
if !output.status.success() {
let err = String::from_utf8_lossy(&output.stderr);
return Err(format!("Remote-Fehler: {}", err));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut items = Vec::new();
for line in stdout.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 8 || parts[0].starts_with("total") {
continue;
}
let perms = parts[0];
let is_dir = perms.starts_with('d');
let size_bytes = parts[4].parse::<u64>().unwrap_or(0);
let date = parts[5];
let time = parts[6];
let name = parts[7..].join(" ");
if name == "." || name == ".." {
continue;
}
items.push(RemoteFileItem {
name,
is_dir,
size_bytes,
permissions: perms.to_string(),
modified: format!("{} {}", date, time),
});
}
items.sort_by(|a, b| {
if a.is_dir == b.is_dir {
a.name.to_lowercase().cmp(&b.name.to_lowercase())
} else if a.is_dir {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Greater
}
});
Ok(items)
}
#[tauri::command]
pub async fn sftp_upload_file(
server: String,
local_path: String,
remote_path: String,
state: tauri::State<'_, crate::vault::VaultState>,
) -> Result<(), String> {
crate::vault::ensure_unlocked(&state)?;
let sb_ssh = which::which("sb-ssh")
.or_else(|_| {
dirs::home_dir().map(|h| h.join(".cargo").join("bin").join(if cfg!(windows) { "sb-ssh.exe" } else { "sb-ssh" }))
.filter(|p| p.exists())
.ok_or("Nicht gefunden")
})
.map_err(|_| "sb-ssh Binary nicht gefunden".to_string())?;
let status = tokio::process::Command::new(&sb_ssh)
.args(["push", &server, &local_path, &remote_path])
.status()
.await
.map_err(|e| format!("SFTP Upload fehlgeschlagen: {}", e))?;
if status.success() {
Ok(())
} else {
Err(format!("SFTP Upload beendet mit Code {:?}", status.code()))
}
}
#[tauri::command]
pub async fn sftp_download_file(
server: String,
remote_path: String,
local_path: String,
state: tauri::State<'_, crate::vault::VaultState>,
) -> Result<(), String> {
crate::vault::ensure_unlocked(&state)?;
let sb_ssh = which::which("sb-ssh")
.or_else(|_| {
dirs::home_dir().map(|h| h.join(".cargo").join("bin").join(if cfg!(windows) { "sb-ssh.exe" } else { "sb-ssh" }))
.filter(|p| p.exists())
.ok_or("Nicht gefunden")
})
.map_err(|_| "sb-ssh Binary nicht gefunden".to_string())?;
let status = tokio::process::Command::new(&sb_ssh)
.args(["pull", &server, &remote_path, &local_path])
.status()
.await
.map_err(|e| format!("SFTP Download fehlgeschlagen: {}", e))?;
if status.success() {
Ok(())
} else {
Err(format!("SFTP Download beendet mit Code {:?}", status.code()))
}
}