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}
15
16pub fn prompt_remote_info() -> Result<(String, String)> {
17 let mut remote_host = String::new();
18 let mut remote_dir = String::new();
19
20 print!("Enter remote host (e.g., user@host): ");
21 io::stdout().flush()?;
22 io::stdin().read_line(&mut remote_host)?;
23
24 print!("Enter remote directory (relative to remote home): ");
25 io::stdout().flush()?;
26 io::stdin().read_line(&mut remote_dir)?;
27
28 Ok((
29 remote_host.trim().to_string(),
30 remote_dir.trim().to_string(),
31 ))
32}
33
34pub fn select_remote(entries: &[RemoteEntry]) -> Result<String> {
35 println!("Multiple remote configurations found. Please select one:");
36
37 for (i, entry) in entries.iter().enumerate() {
38 println!(
39 "{}: {} ({}:{})",
40 i + 1,
41 entry.name,
42 entry.remote_host,
43 entry.remote_dir
44 );
45 }
46
47 let mut selection = String::new();
48 print!("Enter selection (1-{}): ", entries.len());
49 io::stdout().flush()?;
50 io::stdin().read_line(&mut selection)?;
51
52 let index = selection
53 .trim()
54 .parse::<usize>()
55 .context("Invalid selection")?
56 - 1;
57
58 if index >= entries.len() {
59 anyhow::bail!("Selection out of range");
60 }
61
62 Ok(entries[index].name.clone())
63}
64
65pub fn list_remotes(cache: &crate::cache::RemoteMap, current_dir: &str) -> Result<()> {
66 let empty_vec: Vec<RemoteEntry> = Vec::new();
67 let entries = cache.get(current_dir).unwrap_or(&empty_vec);
68
69 if entries.is_empty() {
70 println!("No remote configurations found for this directory.");
71 return Ok(());
72 }
73
74 println!("Remote configurations for this directory:");
75 for (i, entry) in entries.iter().enumerate() {
76 println!(
77 "{}: {} ({}:{})",
78 i + 1,
79 entry.name,
80 entry.remote_host,
81 entry.remote_dir
82 );
83 }
84
85 Ok(())
86}
87
88pub fn remove_remote(
89 cache: &mut crate::cache::RemoteMap,
90 current_dir: &str,
91 name: &str,
92) -> Result<()> {
93 let entries = cache
94 .get_mut(current_dir)
95 .context("No remotes found for this directory")?;
96
97 let initial_len = entries.len();
98 entries.retain(|e| e.name != name);
99
100 if entries.len() == initial_len {
101 anyhow::bail!("Remote with name '{}' not found", name);
102 }
103
104 println!("Removed remote configuration '{}'", name);
105 Ok(())
106}
107
108pub fn generate_unique_name(
110 host: &str,
111 cache: &crate::cache::RemoteMap,
112 current_dir: &str,
113) -> String {
114 let base_name = host.split(':').next().unwrap_or(host);
116
117 if !cache.contains_key(current_dir) || !cache[current_dir].iter().any(|e| e.name == base_name) {
119 return base_name.to_string();
120 }
121
122 let mut highest_index = 0;
124 for entry in &cache[current_dir] {
125 if entry.name == base_name {
126 highest_index = 1;
127 } else if entry.name.starts_with(&format!("{}_", base_name)) {
128 if let Ok(index) = entry.name[base_name.len() + 1..].parse::<usize>() {
129 highest_index = highest_index.max(index + 1);
130 }
131 }
132 }
133
134 format!("{}_{}", base_name, highest_index)
136}