1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
use caesar_core::relay;
use caesar_core::sender;
use caesar_core::{receiver, sender::util::generate_random_name};
use clap::{Parser, Subcommand};
use std::{env, sync::Arc};
use tracing::debug;

use crate::config::GLOBAL_CONFIG;

/// Struct representing the command line arguments parsed by clap.
///
/// It uses the clap library to define the command line arguments and their
/// attributes. The version of the application is obtained from the cargo.toml
/// file.
///
/// The `command` field is an optional subcommand. It is represented by the
/// `Commands` enum which defines the different subcommands that can be used.
#[derive(Parser, Debug)]
#[command(version = env!("CARGO_PKG_VERSION"), about = "Send and receive files securely")]
#[command(long_about = None)]
pub struct Args {
    /// The subcommand to run.
    ///
    /// It is an optional field. If it is not provided, the program will run without
    /// any specific subcommand.
    #[command(subcommand)]
    pub command: Option<Commands>,
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Send files to the receiver or relay server
    Send {
        /// Address of the relay server. Accepted formats are: 127.0.0.1:8080, [::1]:8080, example.com
        #[arg(short, long)]
        relay: Option<String>,
        /// Path to file(s)
        #[arg(value_name = "FILES")]
        files: Vec<String>,
    },
    /// Receives Files from the sender with the matching password
    Receive {
        /// Address of the relay server. Accepted formats are: 127.0.0.1:8080, [::1]:8080, example.com
        #[arg(short, long)]
        relay: Option<String>,

        /// Name of Transfer to download files
        #[arg(value_name = "Transfer_Name")]
        name: String,
    },
    /// Start a relay server
    Serve {
        /// Port to run the relay server on
        #[arg(short, long)]
        port: Option<i32>,
        /// The Listen address to run the relay server on
        #[arg(short, long)]
        listen_address: Option<String>,
    },
}


/// Default implementation of the `Args` struct.
///
/// This implementation uses the `new` method to create a new instance of `Args`.
impl Default for Args {
    /// Creates a new instance of `Args` by calling the `new` method.
    ///
    /// # Returns
    ///
    /// A new instance of `Args`.
    fn default() -> Self {
        Self::new()
    }
}


/// Struct representing the parsed command line arguments.
///
/// This struct implements the `Default` trait to create a new instance of `Args` by calling the
/// `new` method.
///
/// The `run` method is used to execute the corresponding command based on the parsed arguments.
impl Args {
    /// Creates a new instance of `Args` by calling the `parse` method.
    pub fn new() -> Self {
        Self::parse()
    }

    /// Executes the corresponding command based on the parsed arguments.
    ///
    /// This method takes no parameters.
    ///
    /// # Returns
    ///
    /// A `Result` that either returns `Ok(())` indicating successful execution or an `Err`
    /// indicating an error.
    pub async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        // Retrieve the global configuration
        let cfg = &GLOBAL_CONFIG;
        debug!("args: {:#?}", self);

        // Match on the `command` field of `Args` to execute the corresponding command
        match &self.command {
            // Command to send files to the receiver or relay server
            Some(Commands::Send { relay, files }) => {
                // Create a string representation of the relay address
                let relay_string: String = relay.as_deref().unwrap_or(&cfg.app_origin).to_string();
                // Create Arc wrappers for the relay address and file paths
                let relay_arc = Arc::new(relay_string);
                let files_arc = Arc::new(files.to_vec());
                // Generate a random name
                let rand_name = generate_random_name();
                // Start the sender with the generated name, relay address, and file paths
                sender::start_sender(rand_name, relay_arc, files_arc).await;
            }
            // Command to receive files from the sender with the matching password
            Some(Commands::Receive {
                relay,
                name,
            }) => {
                // Print the received transfer name
                println!("Receive for {name:?}");
                // Start the receiver with the current directory, relay address, and transfer name
                let _ = receiver::start_receiver(
                    ".".to_string(),
                    relay.as_deref().unwrap_or(&cfg.app_origin),
                    name,
                )
                .await;
            }
            // Command to start a relay server
            Some(Commands::Serve {
                port,
                listen_address,
            }) => {
                // Create a string representation of the listen address
                let address: String = listen_address
                    .as_deref()
                    .unwrap_or(&cfg.app_host)
                    .to_string();
                // Create an integer representation of the port
                let port_value = port.unwrap_or(cfg.app_port.parse::<i32>().unwrap_or(0));
                let port: i32 = port_value;
                // Start the relay server with the port and listen address
                relay::server::start_ws(&port, &address).await;
            }
            // No command provided
            None => {}
        }
        Ok(())
    }
}