quick_file_transfer/
config.rs

1use anyhow::Result;
2use stderrlog::LogLevelNum;
3use transfer::{listen::ListenArgs, send::SendArgs};
4mod util;
5use util::*;
6
7pub mod compression;
8#[cfg(feature = "ssh")]
9pub mod ssh;
10pub mod transfer;
11
12pub const BIN_NAME: &str = "qft";
13
14#[cfg(feature = "evaluate-compression")]
15pub mod evaluate_compression;
16pub mod get_free_port;
17#[cfg(feature = "mdns")]
18pub mod mdns;
19pub mod misc;
20
21#[derive(Debug, Parser)]
22#[command(name = "Quick File Transfer", version, styles = misc::cli_styles())]
23#[command(bin_name = BIN_NAME)]
24pub struct Config {
25    /// Accepted subcommands, e.g. `listen`
26    #[clap(subcommand)]
27    pub command: Option<Command>,
28
29    /// Pass many times for more log output
30    ///
31    /// By default, it'll report errors, warnings and info,
32    /// `-v` enables debug messages, `-vv` for trace messages.
33    #[arg(short, long, action = ArgAction::Count, default_value_t = 0, global = true)]
34    pub verbose: u8,
35
36    /// Silence all log output, this will lead to better performance.
37    #[arg(short, long, action = ArgAction::SetTrue, conflicts_with("verbose"), global = true, env = "QFT_QUIET")]
38    pub quiet: bool,
39
40    #[arg(
41        long,
42        require_equals = true,
43        value_name = "WHEN",
44        default_value_t = clap::ColorChoice::Auto,
45        default_missing_value = "always",
46        value_enum,
47        global = true
48    )]
49    pub color: clap::ColorChoice,
50
51    /// Generate completion scripts for the specified shell.
52    /// Note: The completion script is printed to stdout
53    #[arg(
54        long = "completions",
55        value_hint = clap::ValueHint::Other,
56        value_name = "SHELL"
57    )]
58    pub completions: Option<clap_complete::Shell>,
59}
60
61impl Config {
62    pub fn init() -> Result<Self> {
63        let cfg = Self::parse();
64
65        let log_level: LogLevelNum = match cfg.verbose {
66            0 => LogLevelNum::Info,
67            1 => LogLevelNum::Debug,
68            255 => LogLevelNum::Off,
69            _ => LogLevelNum::Trace,
70        };
71
72        let log_color_when: stderrlog::ColorChoice = match cfg.color {
73            clap::ColorChoice::Auto => stderrlog::ColorChoice::Auto,
74            clap::ColorChoice::Always => stderrlog::ColorChoice::Always,
75            clap::ColorChoice::Never => stderrlog::ColorChoice::Never,
76        };
77
78        set_tracing(&log_level);
79        stderrlog::new()
80            .verbosity(log_level)
81            .quiet(cfg.quiet)
82            .color(log_color_when)
83            .init()?;
84
85        Ok(cfg)
86    }
87
88    /// Generate completion scripts for the specified shell.
89    pub fn generate_completion_script(shell: clap_complete::Shell) {
90        use clap::CommandFactory;
91        clap_complete::generate(
92            shell,
93            &mut Config::command(),
94            BIN_NAME,
95            &mut std::io::stdout(),
96        );
97    }
98}
99
100#[allow(clippy::large_enum_variant)]
101#[derive(Debug, Subcommand)]
102pub enum Command {
103    /// Run in Listen (server) mode
104    Listen(ListenArgs),
105    /// Run in Send (client) mode
106    Send(SendArgs),
107    /// Use mDNS utilities
108    #[cfg(feature = "mdns")]
109    Mdns(mdns::MdnsArgs),
110    /// Evaluate which compression works best for file content
111    #[cfg(feature = "evaluate-compression")]
112    EvaluateCompression(evaluate_compression::EvaluateCompressionArgs),
113    /// Get a free port from the host OS. Optionally specify on which IP or a port range to scan for a free port.
114    GetFreePort(get_free_port::GetFreePortArgs),
115    /// SCP-like - Send to a target that might not have qft actively listening, authenticating over SSH and transferring over TCP.
116    #[cfg(feature = "ssh")]
117    #[command(long_about("SCP-like transfer to a remote target that might not have qft actively listening.\n\
118    Authentication uses SSH (key based auth only) and while the transfer occurs over TCP, UNENCRYPTED!.\n\
119    Just like the rest of QTF, this is not suitable for transforring sensitive information."))]
120    Ssh(ssh::SendSshArgs),
121}
122
123fn set_tracing(_trace_level: &LogLevelNum) {
124    #[cfg(debug_assertions)]
125    set_dev_tracing(_trace_level);
126    #[cfg(not(debug_assertions))]
127    set_prod_tracing();
128}
129
130#[cfg(not(debug_assertions))]
131fn set_prod_tracing() {
132    let subscriber = tracing_subscriber::FmtSubscriber::builder()
133        .with_writer(std::io::stderr)
134        .with_max_level(tracing::Level::ERROR)
135        .with_file(false)
136        .without_time()
137        .compact()
138        .finish();
139    tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
140}
141
142#[cfg(debug_assertions)]
143fn set_dev_tracing(trace_level: &LogLevelNum) {
144    use tracing::Level;
145    let log_level: Level = match trace_level {
146        LogLevelNum::Info => Level::INFO,
147        LogLevelNum::Debug => Level::DEBUG,
148        LogLevelNum::Trace => Level::TRACE,
149        LogLevelNum::Off => Level::ERROR,
150        _ => Level::ERROR,
151    };
152    let subscriber = tracing_subscriber::FmtSubscriber::builder()
153        .with_writer(std::io::stderr)
154        .with_max_level(log_level)
155        .with_line_number(true)
156        .with_thread_names(true)
157        .finish();
158
159    tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
160}