file_transfer_system/
client.rs

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
use std::{ path::Path, sync::Arc, time::Duration};

use tokio::{io::AsyncWriteExt, net::TcpStream, sync::Mutex, time};
use bincode;
use crate::{file_transfer::{Connection, FileTransferProtocol, TransferError}, network::Request};

/// Represents a client for managing file transfers over a TCP connection.
///
/// The `Client` struct encapsulates the necessary details for establishing and managing
/// connections to a server for file transfer operations. It holds connection details,
/// configuration options, and storage settings for the client.
///
/// # Fields
///
/// * `client_storage_path` - A `String` specifying the local directory path where
///   files will be stored or retrieved for transfer.
/// 
/// * `server_address` - A `String` containing the address (IP and port) of the server
///   to which the client will connect for file transfers.
/// 
/// * `timeout` - An `Option<Duration>` specifying the maximum amount of time to wait 
///   for connection attempts or operations before timing out. If `None`, the client 
///   will use a default timeout or no timeout, depending on the underlying connection 
///   logic.
///
/// * `connection` - An `Arc<Mutex<Option<TcpStream>>>` that holds the TCP connection
///   to the server. This field is wrapped in `Arc` and `Mutex` to allow for safe, 
///   concurrent access across async contexts. The `Option<TcpStream>` is `None` until
///   the client successfully connects to the server.
pub struct Client {
    client_storage_path: String,
    server_address: String,
    timeout: Option<Duration>,
    connection: Arc<Mutex<Option<TcpStream>>>,  
}

impl Client {
    pub fn new(client_storage_path: &str, server_address: &str) -> Self {
        Self {
            client_storage_path: client_storage_path.to_owned(),
            server_address: server_address.to_owned(),
            timeout: None,
            connection: Arc::new(Mutex::new(None))
        }
    }

    /// Sets a timeout duration for the client.
    pub fn set_timeout(&mut self, timeout: Duration) {
        self.timeout = Some(timeout);
    }

    /// Connects to the server.
    pub async fn connect(&mut self) -> Result<(), anyhow::Error> {
        let timeout_duration = self.timeout.unwrap_or(Duration::from_secs(30)); // Default timeout
        let connect_future = TcpStream::connect(&self.server_address);

        // Apply timeout to the connection attempt
        let stream = time::timeout(timeout_duration, connect_future).await??;
        let mut connection = self.connection.lock().await;
        *connection = Some(stream);
        Ok(())
    }

    /// Sends a request to the server. Ok if if ok to continue, Err if server declines for some reason
    pub async fn send_request(&self, request: Request) -> Result<(), anyhow::Error> {
        let mut connection = self.connection.lock().await;
        if let Some(ref mut connection) = *connection {
            let request_bytes = bincode::serialize(&request)?;
            let timeout_duration = self.timeout.unwrap_or(Duration::from_secs(30)); // Default timeout
            
            // Apply timeout to the write operation
            time::timeout(timeout_duration, connection.write_all(&request_bytes)).await??;
        } else {
            return Err(anyhow::Error::msg("No active connection"))
        };
        Ok(())
    }

    /// Initiates a file transfer to the server using the File Transfer Protocol (FTP).
    /// 
    /// This asynchronous function sends a file located at `path_to_send` through an 
    /// existing connection to the server. It establishes the transfer by setting up
    /// the file path and buffer size, then utilizes the `init_send` function of 
    /// `FileTransferProtocol` to handle the transmission over the connection.
    /// 
    /// # Arguments
    /// 
    /// * `path_to_send` - A string slice that specifies the path to the file 
    ///   intended for transfer to the server.
    /// 
    /// # Returns
    /// 
    /// Returns `Ok(())` if the file transfer is successfully initiated and completes
    /// without errors, or `Err(TransferError)` if any issue arises during the process.
    /// 
    /// # Errors
    /// 
    /// This function will return an error in the following cases:
    /// 
    /// * The connection is not established, causing a "Connection is not established"
    ///   error to be raised.
    /// * The `init_send` function encounters an error while transferring the file.
    pub async fn send(&self, path_to_send: &str) -> Result<(), TransferError> {
        let mut connection = self.connection.lock().await;
        let connection = connection.as_mut().expect("Connection is not established");
        FileTransferProtocol::new(&Path::new(path_to_send), 64 * 1024)
            .init_send(&mut Connection { stream: connection })
            .await?;
        Ok(())
    }

    /// Downloads a file from the server to the client's storage path using the File Transfer Protocol.
    ///
    /// This asynchronous function initiates a file download from the server through an 
    /// already established connection. The file will be saved at the path specified by 
    /// `client_storage_path`, using a buffer size of 64 KB for efficient data transfer.
    ///
    /// # Arguments
    /// 
    /// This function does not take any additional arguments but relies on the `client_storage_path`
    /// field of the `Client` struct to determine the location where the file should be saved.
    ///
    /// # Returns
    /// 
    /// Returns `Ok(())` if the file is successfully downloaded, or `Err(TransferError)` if an error
    /// occurs during the download process.
    ///
    /// # Errors
    ///
    /// This function may return an error in the following cases:
    /// 
    /// * The connection is not established, resulting in an "Connection is not established" error.
    /// * The `init_receive` function encounters an issue during the download process, returning a 
    ///   `TransferError`.
    pub async fn download(&self) -> Result<(), TransferError>{
        let mut connection = self.connection.lock().await;
        let connection = connection.as_mut().expect("Connection is not established");
        FileTransferProtocol::new(&Path::new(&self.client_storage_path), 64 * 1024)
        .init_receive(&mut Connection { stream: connection })
        .await?;
        Ok(())
    }

    /// Closes the connection to the server.
    pub async fn close(&mut self) -> Result<(), anyhow::Error> {
        let mut connection = self.connection.lock().await;
        if let Some(mut connection) = connection.take() {
            connection.shutdown().await?;
        }
        Ok(())
    }
}