1use cosmic::{Task, iced::Subscription, widget};
2use std::{
3 collections::BTreeMap,
4 fmt,
5 path::PathBuf,
6 sync::{Arc, LazyLock},
7};
8use tokio::sync::mpsc;
9
10#[cfg(feature = "russh")]
11use crate::russh::client::RemoteFile;
12use crate::{config::IconSizes, config::TBConfig, tab};
13
14#[cfg(feature = "russh")]
15mod client;
16
17#[cfg(feature = "russh")]
18pub fn same_uri(a: &str, b: &str) -> bool {
19 let Ok(a_file) = a.parse::<RemoteFile>() else {
20 return false;
21 };
22 let Ok(b_file) = b.parse::<RemoteFile>() else {
23 return false;
24 };
25
26 a_file.host == b_file.host && a_file.username == b_file.username && a_file.port == b_file.port
27}
28
29#[cfg(not(feature = "russh"))]
30pub fn same_uri(_a: &str, _b: &str) -> bool {
31 false
32}
33
34#[derive(Clone)]
35pub struct ClientAuth {
36 pub message: String,
37 pub username_opt: Option<String>,
38 pub domain_opt: Option<String>,
39 pub password_opt: Option<String>,
40 pub remember_opt: Option<bool>,
41 pub anonymous_opt: Option<bool>,
42}
43
44impl fmt::Debug for ClientAuth {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.debug_struct("ClientAuth")
48 .field("username_opt", &self.username_opt)
49 .field("domain_opt", &self.domain_opt)
50 .field(
51 "password_opt",
52 if self.password_opt.is_some() {
53 &"Some(*)"
54 } else {
55 &"None"
56 },
57 )
58 .field("remember_opt", &self.remember_opt)
59 .field("anonymous_opt", &self.anonymous_opt)
60 .finish()
61 }
62}
63
64#[derive(Clone, Debug)]
65#[allow(clippy::large_enum_variant)]
66pub enum ClientItem {
67 #[cfg(feature = "russh")]
68 Russh(client::Item),
69 #[allow(dead_code)]
70 None,
71}
72
73impl ClientItem {
74 pub fn name(&self) -> String {
75 match self {
76 #[cfg(feature = "russh")]
77 Self::Russh(item) => item.name(),
78 Self::None => unreachable!(),
79 }
80 }
81
82 pub fn uri(&self) -> String {
83 match self {
84 #[cfg(feature = "russh")]
85 Self::Russh(item) => item.uri(),
86 Self::None => unreachable!(),
87 }
88 }
89
90 pub fn host(&self) -> String {
91 match self {
92 #[cfg(feature = "russh")]
93 Self::Russh(item) => item.host(),
94 Self::None => unreachable!(),
95 }
96 }
97
98 pub fn is_connected(&self) -> bool {
99 match self {
100 #[cfg(feature = "russh")]
101 Self::Russh(item) => item.is_connected(),
102 Self::None => unreachable!(),
103 }
104 }
105
106 pub fn icon(&self, symbolic: bool) -> Option<widget::icon::Handle> {
107 match self {
108 #[cfg(feature = "russh")]
109 Self::Russh(item) => item.icon(symbolic),
110 Self::None => unreachable!(),
111 }
112 }
113
114 pub fn path(&self) -> Option<PathBuf> {
115 match self {
116 #[cfg(feature = "russh")]
117 Self::Russh(item) => item.path(),
118 Self::None => unreachable!(),
119 }
120 }
121}
122
123pub type ClientItems = Vec<ClientItem>;
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
126pub struct SlurmJobId {
127 pub array_id: usize,
128 pub tasks: usize,
129 pub running_tasks: usize,
130}
131
132#[derive(Clone, Debug)]
133pub enum ClientMessage {
134 Items(ClientItems),
135 ClientResult(ClientItem, Result<bool, String>),
136 RemoteAuth(String, ClientAuth, mpsc::Sender<ClientAuth>),
137 RemoteResult(String, Result<bool, String>),
138 RunTbProfilerResult(String, Result<SlurmJobId, String>),
139 DeleteRemoteFilesResult(String, Result<String, String>),
140 JobStatusUpdate(String, usize, usize),
141}
142
143#[derive(Clone, Debug)]
145pub enum DownloadEvent {
146 FileCompleted,
148 Complete(Result<(), String>),
150}
151
152pub trait Connector: Send + Sync {
153 fn items(&self, sizes: IconSizes) -> Option<ClientItems>;
154 fn connect(&self, item: ClientItem) -> Task<()>;
155 fn remote_drive(&self, uri: String) -> Task<()>;
156 fn remote_scan(
157 &self,
158 uri: &str,
159 sizes: IconSizes,
160 ) -> Option<Result<Vec<tab::Item>, String>>;
161 fn remote_parent_item(&self, uri: &str, sizes: IconSizes) -> Option<Result<tab::Item, String>>;
162 fn dir_info(&self, uri: &str) -> Option<(String, String, Option<PathBuf>)>;
163 fn disconnect(&self, item: ClientItem) -> Task<()>;
164 fn download_file(&self, paths: Box<[PathBuf]>, uris: Vec<String>, to: PathBuf, zip_output: Option<PathBuf>) -> Task<DownloadEvent>;
165 fn run_tb_profiler(
166 &self,
167 paths: Box<[PathBuf]>,
168 uris: Vec<String>,
169 tb_config: TBConfig,
170 ) -> Task<()>;
171 fn delete_remote_files(&self, paths: Box<[PathBuf]>, uris: Vec<String>) -> Task<()>;
172 fn delete_tb_profiler_results(&self, uri: String, tb_config: TBConfig) -> Task<()>;
173 fn poll_job_status(&self, job_id: usize, uri: String) -> Task<()>;
174 fn subscription(&self) -> Subscription<ClientMessage>;
175}
176
177#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
178pub struct ClientKey(pub &'static str);
179pub type ClientMap = BTreeMap<ClientKey, Box<dyn Connector>>;
180pub type Clients = Arc<ClientMap>;
181
182pub fn clients() -> Clients {
183 #[allow(unused_mut)]
184 let mut clients = ClientMap::new();
185
186 #[cfg(feature = "russh")]
187 {
188 clients.insert(ClientKey("russh"), Box::new(client::Russh::new()));
189 }
190
191 Clients::new(clients)
192}
193
194pub static CLIENTS: LazyLock<Clients> = LazyLock::new(clients);