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
114pub fn generate_unique_name(
116 host: &str,
117 cache: &crate::cache::RemoteMap,
118 current_dir: &str,
119) -> String {
120 let base_name = host.split(':').next().unwrap_or(host);
122
123 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 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 format!("{}_{}", base_name, highest_index)
142}