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