atsh_lib/
lib.rs

1use std::io::Error;
2use std::path::{Path, PathBuf};
3use std::sync::{LazyLock, OnceLock};
4
5mod config;
6mod db;
7mod ssh;
8
9static WORK_DIR: OnceLock<PathBuf> = OnceLock::new();
10pub(crate) static WORK_DIR_FILE: LazyLock<fn(&str) -> PathBuf> = LazyLock::new(|| {
11    |n| {
12        if cfg!(test) {
13            let work_dir =
14                std::env::current_exe().expect("failed to get current execute directory");
15            work_dir.with_file_name("test.atsh.d")
16        } else {
17            let work_dir = WORK_DIR.get().expect("WORK_DIR not initialized");
18            work_dir.join(n)
19        }
20    }
21});
22
23fn setup_logging(work_dir: &Path) -> Result<(), Error> {
24    use tracing_appender::rolling::{RollingFileAppender, Rotation};
25    use tracing_subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt};
26
27    let log_dir = work_dir.join("logs");
28    if !log_dir.is_dir() {
29        std::fs::create_dir_all(&log_dir)?;
30    }
31    // let log_file = log_dir.join(format!("{}.log", Local::now().format("%Y-%m-%d")));
32    // let file_appender = RollingFileAppender::new(Rotation::DAILY, log_dir, log_file);
33
34    let subscriber = tracing_subscriber::registry()
35        .with(EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")))
36        .with(
37            fmt::Layer::new()
38                .with_writer(std::io::stderr)
39                .with_target(true)
40                .with_line_number(true),
41        )
42        .with(
43            fmt::Layer::new().json().with_writer(
44                RollingFileAppender::builder()
45                    .rotation(Rotation::DAILY)
46                    // .filename_prefix("atsh.log")
47                    .filename_suffix("json")
48                    .build(log_dir)
49                    .expect("Failed to create log file"),
50            ),
51        );
52
53    subscriber.init();
54    tracing::debug!(
55        "Logging initialized (RUST_LOG={})",
56        std::env::var("RUST_LOG").unwrap_or_default()
57    );
58    Ok(())
59}
60
61pub mod atsh {
62    use crate::ssh::remote::Remotes;
63    use crate::{WORK_DIR, setup_logging};
64    use std::io::{Error, ErrorKind};
65    use std::path::Path;
66    use tracing::debug;
67
68    type Result<T> = std::result::Result<T, Error>;
69
70    pub fn initialize(work_dir: Option<&Path>) -> Result<()> {
71        let work_dir = work_dir.map(|p| p.to_path_buf()).unwrap_or({
72            let work_dir =
73                std::env::current_exe().expect("failed to get current execute directory");
74            // work_dir.pop();
75            // work_dir
76            work_dir.with_file_name(".atsh.d")
77        });
78        debug!("work_dir: {}", work_dir.display());
79
80        if !work_dir.exists() {
81            std::fs::create_dir_all(&work_dir)?;
82        }
83        setup_logging(&work_dir)?;
84
85        WORK_DIR
86            .set(work_dir)
87            .expect("WORK_DIR already initialized");
88
89        // info!(work=?WORK_DIR.get(), "success initialize");
90        Ok(())
91    }
92
93    pub fn add(
94        user: &str,
95        password: &str,
96        ip: &str,
97        port: u16,
98        name: &Option<impl AsRef<str>>,
99        note: &Option<impl AsRef<str>>,
100    ) -> Result<usize> {
101        Remotes::add(user, password, ip, port, name, note)
102    }
103
104    pub fn remove(index: &Vec<usize>) -> Result<usize> {
105        Remotes::delete(index)
106    }
107
108    pub fn list(all: bool) -> Result<()> {
109        if all {
110            Remotes::list_all()
111        } else {
112            Remotes::list()
113        }
114    }
115
116    // auth params means try auth against the server
117    pub fn login(index: usize, auth: bool) -> Result<()> {
118        let remote = Remotes::get(index)?;
119        remote.login(auth)
120    }
121
122    pub fn copy(index: usize, path: &str) -> Result<()> {
123        let paths = path.split('=').collect::<Vec<&str>>();
124        if paths.len() != 2 {
125            return Err(Error::new(
126                ErrorKind::InvalidInput,
127                "path format error, like `from=to`",
128            ));
129        }
130        let remote = Remotes::get(index)?;
131        if std::path::PathBuf::from(paths[0]).exists() {
132            remote.upload(paths[0], paths[1])
133        } else {
134            remote.download(paths[0], paths[1])
135        }
136    }
137
138    pub fn upload(index: usize, path: &Vec<impl AsRef<str>>) -> Result<()> {
139        if path.len() != 2 {
140            return Err(Error::new(
141                ErrorKind::InvalidInput,
142                "path format error, like `upload -p /local/path /remote/path`",
143            ));
144        }
145        let (local, remote) = (path[0].as_ref(), path[1].as_ref());
146        if !Path::new(local).exists() {
147            return Err(Error::new(ErrorKind::NotFound, "the upload file not found"));
148        }
149
150        Remotes::get(index)?.upload(local, remote)
151    }
152
153    pub fn download(index: usize, path: &Vec<impl AsRef<str>>) -> Result<()> {
154        if path.len() != 2 {
155            return Err(Error::new(
156                ErrorKind::InvalidInput,
157                "path format error, like `upload -p /remote/path /local/path`",
158            ));
159        }
160        let (remote, local) = (path[0].as_ref(), path[1].as_ref());
161
162        Remotes::get(index)?.download(remote, local)
163    }
164}
165
166// #[cfg(test)]
167// mod tests {
168
169//     use super::*;
170
171//     #[test]
172//     fn test_lib() {
173//     }
174// }