sync_rs/
config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::io::{self, Write};
4
5#[derive(Debug, Serialize, Deserialize, Clone)]
6pub struct RemoteEntry {
7    pub name: String,
8    pub remote_host: String,
9    pub remote_dir: String,
10    #[serde(default)]
11    pub override_paths: Vec<String>,
12    #[serde(default)]
13    pub post_sync_command: Option<String>,
14    #[serde(default)]
15    pub preferred: bool,
16    #[serde(default)]
17    pub ignore_patterns: Vec<String>,
18}
19
20pub fn prompt_remote_info() -> Result<(String, String)> {
21    let mut remote_host = String::new();
22    let mut remote_dir = String::new();
23
24    print!("Enter remote host (e.g., user@host): ");
25    io::stdout().flush()?;
26    io::stdin().read_line(&mut remote_host)?;
27
28    print!("Enter remote directory (relative to remote home): ");
29    io::stdout().flush()?;
30    io::stdin().read_line(&mut remote_dir)?;
31
32    Ok((
33        remote_host.trim().to_string(),
34        remote_dir.trim().to_string(),
35    ))
36}
37
38pub fn select_remote(entries: &[RemoteEntry]) -> Result<String> {
39    println!("Multiple remote configurations found. Please select one:");
40
41    for (i, entry) in entries.iter().enumerate() {
42        println!(
43            "{}: {} ({}:{})",
44            i + 1,
45            entry.name,
46            entry.remote_host,
47            entry.remote_dir
48        );
49    }
50
51    let mut selection = String::new();
52    print!("Enter selection (1-{}): ", entries.len());
53    io::stdout().flush()?;
54    io::stdin().read_line(&mut selection)?;
55
56    let index = selection
57        .trim()
58        .parse::<usize>()
59        .context("Invalid selection")?
60        - 1;
61
62    if index >= entries.len() {
63        anyhow::bail!("Selection out of range");
64    }
65
66    Ok(entries[index].name.clone())
67}
68
69pub fn list_remotes(cache: &crate::cache::RemoteMap, current_dir: &str) -> Result<()> {
70    let empty_vec: Vec<RemoteEntry> = Vec::new();
71    let entries = cache.get(current_dir).unwrap_or(&empty_vec);
72
73    if entries.is_empty() {
74        println!("No remote configurations found for this directory.");
75        return Ok(());
76    }
77
78    println!("Remote configurations for this directory:");
79    for (i, entry) in entries.iter().enumerate() {
80        let preferred = if entry.preferred { " (preferred)" } else { "" };
81        println!(
82            "{}: {}{} ({}:{})",
83            i + 1,
84            entry.name,
85            preferred,
86            entry.remote_host,
87            entry.remote_dir
88        );
89    }
90
91    Ok(())
92}
93
94pub fn remove_remote(
95    cache: &mut crate::cache::RemoteMap,
96    current_dir: &str,
97    name: &str,
98) -> Result<()> {
99    let entries = cache
100        .get_mut(current_dir)
101        .context("No remotes found for this directory")?;
102
103    let initial_len = entries.len();
104    entries.retain(|e| e.name != name);
105
106    if entries.len() == initial_len {
107        anyhow::bail!("Remote with name '{}' not found", name);
108    }
109
110    println!("Removed remote configuration '{}'", name);
111    Ok(())
112}
113
114// Generate a unique name based on the host name
115pub fn generate_unique_name(
116    host: &str,
117    cache: &crate::cache::RemoteMap,
118    current_dir: &str,
119) -> String {
120    // Extract username@hostname or just hostname
121    let base_name = host.split(':').next().unwrap_or(host);
122
123    // If there are no entries for this directory or no entries with this base name, use it as is
124    if !cache.contains_key(current_dir) || !cache[current_dir].iter().any(|e| e.name == base_name) {
125        return base_name.to_string();
126    }
127
128    // Find the highest index used for this base name
129    let mut highest_index = 0;
130    for entry in &cache[current_dir] {
131        if entry.name == base_name {
132            highest_index = 1;
133        } else if entry.name.starts_with(&format!("{}_", base_name)) {
134            if let Ok(index) = entry.name[base_name.len() + 1..].parse::<usize>() {
135                highest_index = highest_index.max(index + 1);
136            }
137        }
138    }
139
140    // Return the base name with the next available index
141    format!("{}_{}", base_name, highest_index)
142}