cosmic_files/russh/
mod.rs

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
44// Custom debug for ClientAuth to hide password
45impl 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/// Events emitted during a download operation.
144#[derive(Clone, Debug)]
145pub enum DownloadEvent {
146    /// One file has finished downloading.
147    FileCompleted,
148    /// The entire download batch is done.
149    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);