file_transfer_system/
file_transfer.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::fs::{self, create_dir_all, File};
use tokio::net::TcpStream;
use std::path::{Path, PathBuf};
use std::io::Error as IoError;
use serde::{Serialize, Deserialize};
use futures::future::BoxFuture;

/// Error type for transfer errors
#[derive(Debug, Serialize, Deserialize)]
pub enum TransferError {
    IoError(String), // Store the error message as a String
    ConnectionClosed,
    FileNotFound,
    FileCorrupted,
    ChunkError,
    // Other errors can be added here
}

impl From<IoError> for TransferError {
    fn from(err: IoError) -> TransferError {
        TransferError::IoError(err.to_string()) // Convert IoError to String
    }
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct FSObjectMetadata {
    pub file_size: Option<u64>,
    pub file_name: String,
    pub path_type: PathType
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum PathType {
    File,
    Directory
}

impl FSObjectMetadata {

    fn new(file_size: Option<u64>, file_name: String, path_type: PathType) -> Self {
        Self {file_size, file_name, path_type}
    }

    // Serialize to bytes for sending over the network
    pub fn to_bytes(&self) -> Vec<u8> {
        bincode::serialize(self).unwrap()  // Using `bincode` for serialization
    }

    // Deserialize from bytes for receiving over the network
    pub fn from_bytes(bytes: &[u8]) -> Self {
        bincode::deserialize(bytes).unwrap()
    }
}

// Simplified Connection struct using only TcpStream
pub struct Connection<'a> {
    pub stream: &'a mut TcpStream,
}


impl<'a> Connection<'a> {
    // Example of writing to the TCP connection
    pub async fn write(&mut self, data: &[u8]) -> Result<(), TransferError> {
        self.stream
            .write_all(data)
            .await
            .map_err(|e| TransferError::IoError(e.to_string())) // Convert std::io::Error to String
    }

    // Example of reading from the TCP connection
    pub async fn read(&mut self, buffer: &mut [u8]) -> Result<usize, TransferError> {
        self.stream
            .read(buffer)
            .await
            .map_err(|e| TransferError::IoError(e.to_string())) // Convert std::io::Error to String
    }
}

// Define the file transfer protocol
pub struct FileTransferProtocol {
    pub path: PathBuf,
    pub chunk_size: u64,
}

impl FileTransferProtocol {
    // Initialize the file transfer protocol
    pub fn new(path: &Path, chunk_size: u64) -> Self {
        FileTransferProtocol {
            path: path.to_owned(),
            chunk_size,
        }
    }

    pub async fn init_send(&self, connection: &mut Connection<'_>) -> Result<(), TransferError> {
        if self.path.is_dir() {
            self.send_directory(connection).await?;
        } else {
            self.send_file(connection).await?;
        }
        Ok(())
    }
    pub async fn init_receive(&self, connection: &mut Connection<'_>, path_type: &PathType) -> Result<(), TransferError> {
        match path_type {
            PathType::File => self.receive_file(connection).await?,
            PathType::Directory => self.receive_directory(connection).await?
        }
        Ok(())
    }
    // Send file logic over TCP
    pub async fn send_file(&self, connection: &mut Connection<'_>) -> Result<(), TransferError> {
        let mut file = File::open(&self.path).await.map_err(|_| TransferError::FileNotFound)?;

        let mut buffer = vec![0u8; self.chunk_size as usize];
        let mut total_bytes_sent = 0;

        // Read the file chunk by chunk and send it over the connection
        loop {
            let n = file.read(&mut buffer).await.map_err(TransferError::from)?;
            if n == 0 {
                break;
            }

            // Send the chunk over the TCP connection
            connection.write(&buffer[..n]).await?;
            total_bytes_sent += n as u64;

            // TODO implement progress reporting
            println!("Sent {} bytes", total_bytes_sent);
        }

        Ok(())
    }

    // Receive file logic over TCP
    pub async fn receive_file(&self, connection: &mut Connection<'_>) -> Result<(), TransferError> {
        let mut file = File::create(&self.path).await.map_err(TransferError::from)?;

        let mut buffer = vec![0u8; self.chunk_size as usize];
        let mut total_bytes_received = 0;

        // Receive the file chunk by chunk and write it to the file
        loop {
            let n = connection.read(&mut buffer).await?;
            if n == 0 {
                break;
            }

            // Write the chunk to the file
            file.write_all(&buffer[..n]).await.map_err(TransferError::from)?;
            total_bytes_received += n as u64;

            // Optional: You can implement progress reporting
            println!("Received {} bytes", total_bytes_received);
        }

        Ok(())
    }

    pub fn send_directory<'a>(&'a self, connection: &'a mut Connection) -> BoxFuture<'a, Result<(), TransferError>> {
        Box::pin(async move {
            let mut dir_entries = fs::read_dir(&self.path).await.map_err(|_| TransferError::FileNotFound)?;

            while let Some(entry) = dir_entries.next_entry().await.map_err(TransferError::from)? {
                let entry_path = entry.path();
                let metadata = entry.metadata().await.map_err(TransferError::from)?;

                if metadata.is_dir() {
                    // Send directory metadata
                    let dir_metadata = FSObjectMetadata::new(None, entry_path.file_name().unwrap().to_string_lossy().to_string(), PathType::Directory);
                    connection.write(&dir_metadata.to_bytes()).await?;

                    // Recursively send the directory's contents
                    self.send_directory(connection).await?;
                } else if metadata.is_file() {
                    // Send file metadata
                    let file_metadata = FSObjectMetadata::new(Some(metadata.len()), entry_path.file_name().unwrap().to_string_lossy().to_string(), PathType::File);
                    connection.write(&file_metadata.to_bytes()).await?;

                    // Send the file content
                    let file_transfer = FileTransferProtocol::new(&entry_path, self.chunk_size);
                    file_transfer.send_file(connection).await?;
                }
            }
            Ok(())
        })
    }

    pub async fn receive_directory(&self, connection: &mut Connection<'_>) -> Result<(), TransferError> {
        loop {
            // Receive metadata (directory or file)
            let mut metadata_buffer = vec![0u8; 1024];
            let n = connection.read(&mut metadata_buffer).await?;
            if n == 0 {
                break;  // End of transfer
            }

            let metadata = FSObjectMetadata::from_bytes(&metadata_buffer[..n]);

            match metadata.path_type {
                PathType::Directory => {
                    // Create directory
                    let dir_path = self.path.join(&metadata.file_name);
                    create_dir_all(&dir_path).await.map_err(TransferError::from)?;
                }
                PathType::File => {
                    // Receive the file
                    let file_path = self.path.join(&metadata.file_name);
                    let file_transfer = FileTransferProtocol::new(&file_path, self.chunk_size);
                    file_transfer.receive_file(connection).await?;
                }
            }
        }

        Ok(())
    }
}