file_transfer_system/
client.rs

1use std::{ net::{IpAddr, SocketAddr}, sync::Arc, time::Duration};
2
3use tokio::{io::AsyncWriteExt, net::TcpStream, sync::Mutex, time};
4use bincode;
5use crate::{file_transfer::{Connection, FileTransferProtocol, TransferError}, network::Request};
6
7/// Represents a client for managing file transfers over a TCP connection.
8///
9/// The `Client` struct encapsulates the necessary details for establishing and managing
10/// connections to a server for file transfer operations. It holds connection details,
11/// configuration options, and storage settings for the client.
12///
13/// # Fields
14///
15/// * `client_storage_path` - A `String` specifying the local directory path where
16///   files will be stored or retrieved for transfer.
17/// 
18/// * `server_address` - A `String` containing the address (IP and port) of the server
19///   to which the client will connect for file transfers.
20/// 
21/// * `timeout` - An `Option<Duration>` specifying the maximum amount of time to wait 
22///   for connection attempts or operations before timing out. If `None`, the client 
23///   will use a default timeout or no timeout, depending on the underlying connection 
24///   logic.
25///
26/// * `connection` - An `Arc<Mutex<Option<TcpStream>>>` that holds the TCP connection
27///   to the server. This field is wrapped in `Arc` and `Mutex` to allow for safe, 
28///   concurrent access across async contexts. The `Option<TcpStream>` is `None` until
29///   the client successfully connects to the server.
30pub struct Client {
31    client_storage_path: String,
32    server_address: IpAddr,
33    timeout: Option<Duration>,
34    connection: Arc<Mutex<Option<TcpStream>>>,  
35}
36
37impl Client {
38    pub fn new(client_storage_path: &str, server_address: IpAddr) -> Self {
39        Self {
40            client_storage_path: client_storage_path.to_owned(),
41            server_address,
42            timeout: None,
43            connection: Arc::new(Mutex::new(None))
44        }
45    }
46
47    /// Sets a timeout duration for the client.
48    pub fn set_timeout(&mut self, timeout: Duration) {
49        self.timeout = Some(timeout);
50    }
51
52/// Connects to the server.
53pub async fn connect(&mut self) -> Result<(), anyhow::Error> {
54    // Set the timeout duration
55    let timeout_duration = self.timeout.unwrap_or(Duration::from_secs(30)); // Default timeout
56    let addr = SocketAddr::new(self.server_address, 8080);
57
58    // Apply timeout to the connection attempt
59    match time::timeout(timeout_duration, TcpStream::connect(addr)).await {
60        Ok(Ok(stream)) => {
61            let mut connection = self.connection.lock().await; // Ensure you're using tokio::sync::Mutex
62            *connection = Some(stream);
63            Ok(())
64        },
65        Ok(Err(e)) => Err(anyhow::anyhow!("Connection error: {}", e)),
66        Err(_) => Err(anyhow::anyhow!("Connection attempt timed out")),
67    }
68}
69
70    /// Sends a request to the server. Ok if if ok to continue, Err if server declines for some reason
71    pub async fn send_request(&self, request: Request) -> Result<(), anyhow::Error> {
72        let mut connection = self.connection.lock().await;
73        if let Some(ref mut connection) = *connection {
74            let request_bytes = bincode::serialize(&request)?;
75            let timeout_duration = self.timeout.unwrap_or(Duration::from_secs(30)); // Default timeout
76            
77            // Apply timeout to the write operation
78            time::timeout(timeout_duration, connection.write_all(&request_bytes)).await??;
79        } else {
80            return Err(anyhow::Error::msg("No active connection"))
81        };
82        Ok(())
83    }
84
85    /// Initiates a file transfer to the server using the File Transfer Protocol (FTP).
86    /// 
87    /// This asynchronous function sends a file located at `path_to_send` through an 
88    /// existing connection to the server. It establishes the transfer by setting up
89    /// the file path and buffer size, then utilizes the `init_send` function of 
90    /// `FileTransferProtocol` to handle the transmission over the connection.
91    /// 
92    /// # Arguments
93    /// 
94    /// * `path_to_send` - A string slice that specifies the path to the file 
95    ///   intended for transfer to the server.
96    /// 
97    /// # Returns
98    /// 
99    /// Returns `Ok(())` if the file transfer is successfully initiated and completes
100    /// without errors, or `Err(TransferError)` if any issue arises during the process.
101    /// 
102    /// # Errors
103    /// 
104    /// This function will return an error in the following cases:
105    /// 
106    /// * The connection is not established, causing a "Connection is not established"
107    ///   error to be raised.
108    /// * The `init_send` function encounters an error while transferring the file.
109    pub async fn send(&self, path_to_send: &str) -> Result<(), TransferError> {
110        let mut connection = self.connection.lock().await;
111        let connection = connection.as_mut().expect("Connection is not established");
112        FileTransferProtocol::new(path_to_send, 64 * 1024)
113            .init_send(&mut Connection { stream: connection })
114            .await?;
115        Ok(())
116    }
117
118    /// Downloads a file from the server to the client's storage path using the File Transfer Protocol.
119    ///
120    /// This asynchronous function initiates a file download from the server through an 
121    /// already established connection. The file will be saved at the path specified by 
122    /// `client_storage_path`, using a buffer size of 64 KB for efficient data transfer.
123    ///
124    /// # Arguments
125    /// 
126    /// This function does not take any additional arguments but relies on the `client_storage_path`
127    /// field of the `Client` struct to determine the location where the file should be saved.
128    ///
129    /// # Returns
130    /// 
131    /// Returns `Ok(())` if the file is successfully downloaded, or `Err(TransferError)` if an error
132    /// occurs during the download process.
133    ///
134    /// # Errors
135    ///
136    /// This function may return an error in the following cases:
137    /// 
138    /// * The connection is not established, resulting in an "Connection is not established" error.
139    /// * The `init_receive` function encounters an issue during the download process, returning a 
140    ///   `TransferError`.
141    pub async fn download(&self) -> Result<(), TransferError>{
142        let mut connection = self.connection.lock().await;
143        let connection = connection.as_mut().expect("Connection is not established");
144        let ftp = FileTransferProtocol::new(&self.client_storage_path, 64 * 1024);
145        ftp.receive(&mut Connection {stream: connection}).await?;
146        Ok(())
147    }
148
149    /// Closes the connection to the server.
150    pub async fn close(&mut self) -> Result<(), anyhow::Error> {
151        let mut connection = self.connection.lock().await;
152        if let Some(mut connection) = connection.take() {
153            connection.shutdown().await?;
154        }
155        Ok(())
156    }
157}