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(())
}
}