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}