74 lines
2.4 KiB
Rust
74 lines
2.4 KiB
Rust
//! Saved SSH hosts. Persisted to `%APPDATA%\com.megaproxy.tiletopia\hosts.json`
|
|
//! alongside `workspace.json`. The frontend owns the in-memory state and the
|
|
//! add/edit/delete UX; the backend just reads/writes the whole list.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use anyhow::{Context, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use tauri::{AppHandle, Manager};
|
|
|
|
const HOSTS_FILE: &str = "hosts.json";
|
|
|
|
/// One saved host. Fields beyond `hostname` are optional; ssh.exe will fall
|
|
/// back to `~/.ssh/config` and its own defaults for anything we don't pass.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SshHost {
|
|
pub id: String,
|
|
pub label: String,
|
|
pub hostname: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub user: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub port: Option<u16>,
|
|
#[serde(
|
|
default,
|
|
rename = "identityFile",
|
|
skip_serializing_if = "Option::is_none"
|
|
)]
|
|
pub identity_file: Option<String>,
|
|
#[serde(
|
|
default,
|
|
rename = "jumpHost",
|
|
skip_serializing_if = "Option::is_none"
|
|
)]
|
|
pub jump_host: Option<String>,
|
|
#[serde(
|
|
default,
|
|
rename = "extraArgs",
|
|
skip_serializing_if = "Option::is_none"
|
|
)]
|
|
pub extra_args: Option<Vec<String>>,
|
|
}
|
|
|
|
fn hosts_path(app: &AppHandle) -> Result<PathBuf> {
|
|
let dir = app
|
|
.path()
|
|
.app_config_dir()
|
|
.map_err(|e| anyhow::anyhow!("app_config_dir: {e}"))?;
|
|
Ok(dir.join(HOSTS_FILE))
|
|
}
|
|
|
|
pub fn load(app: &AppHandle) -> Result<Vec<SshHost>> {
|
|
let path = hosts_path(app)?;
|
|
if !path.exists() {
|
|
return Ok(Vec::new());
|
|
}
|
|
let raw = std::fs::read_to_string(&path).context("read hosts.json")?;
|
|
let hosts: Vec<SshHost> = serde_json::from_str(&raw).context("parse hosts.json")?;
|
|
Ok(hosts)
|
|
}
|
|
|
|
pub fn save(app: &AppHandle, hosts: &[SshHost]) -> Result<()> {
|
|
let path = hosts_path(app)?;
|
|
if let Some(dir) = path.parent() {
|
|
std::fs::create_dir_all(dir).context("create_dir_all")?;
|
|
}
|
|
let tmp = path.with_extension("json.tmp");
|
|
let json = serde_json::to_string_pretty(hosts).context("serialize hosts")?;
|
|
std::fs::write(&tmp, json.as_bytes()).context("write tmp hosts.json")?;
|
|
// `std::fs::rename` is atomic on Unix and uses MoveFileEx with
|
|
// REPLACE_EXISTING on Windows — same pattern as save_workspace.
|
|
std::fs::rename(&tmp, &path).context("rename hosts.json")?;
|
|
Ok(())
|
|
}
|