sb-vault 0.1.0

S&B Vault // Zero-Trust Desktop Suite & Secret Engine
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()))
    }
}