use std::fs;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::config::StateConfig;
use crate::error::{Result, StorageError};
use crate::transport::ticket::Ticket;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PeerProfile {
pub name: String,
pub ticket: Ticket,
}
fn ensure_peers_dir(state: &StateConfig) -> Result<PathBuf> {
let path = state.root().join("peers");
if !path.exists() {
fs::create_dir_all(&path).map_err(|source| StorageError::DirectoryCreate {
path: path.clone(),
source,
})?;
}
Ok(path)
}
fn peer_path(state: &StateConfig, name: &str) -> PathBuf {
state.root().join("peers").join(format!("{}.json", name))
}
pub fn save_peer(state: &StateConfig, profile: &PeerProfile) -> Result<()> {
ensure_peers_dir(state)?;
if profile.name.contains('/') || profile.name.contains('\\') || profile.name == ".." {
return Err(StorageError::PeerNameInvalid {
name: profile.name.clone(),
}
.into());
}
let path = peer_path(state, &profile.name);
let json = serde_json::to_vec_pretty(profile)
.map_err(|source| StorageError::PeerProfileSerialize { source })?;
crate::storage::utils::atomic_write_secure(&path, &json)?;
Ok(())
}
pub fn get_peer(state: &StateConfig, name: &str) -> Result<Option<PeerProfile>> {
let path = peer_path(state, name);
if !path.exists() {
return Ok(None);
}
let json = fs::read_to_string(&path).map_err(|source| StorageError::FileRead {
path: path.clone(),
source,
})?;
let profile =
serde_json::from_str(&json).map_err(|source| StorageError::PeerProfileParse { source })?;
Ok(Some(profile))
}
pub fn list_peers(state: &StateConfig) -> Result<Vec<PeerProfile>> {
let dir = ensure_peers_dir(state)?;
let mut profiles = Vec::new();
let entries = fs::read_dir(&dir).map_err(|source| StorageError::DirectoryRead {
path: dir.clone(),
source,
})?;
for entry in entries {
let entry = entry.map_err(|source| StorageError::DirectoryEntryRead {
path: dir.clone(),
source,
})?;
let path = entry.path();
if path.is_file() && path.extension().is_some_and(|ext| ext == "json") {
let json = fs::read_to_string(&path).map_err(|source| StorageError::FileRead {
path: path.clone(),
source,
})?;
if let Ok(profile) = serde_json::from_str::<PeerProfile>(&json) {
profiles.push(profile);
}
}
}
Ok(profiles)
}
pub fn delete_peer(state: &StateConfig, name: &str) -> Result<bool> {
let path = peer_path(state, name);
match fs::remove_file(&path) {
Ok(()) => Ok(true),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(source) => Err(StorageError::FileDelete { path, source }.into()),
}
}