diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 46a2f6a..db94fa1 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: cargo build --release --verbose - name: Run tests diff --git a/.github/workflows/pr_main.yml b/.github/workflows/pr_main.yml index db4e60d..8ac36d2 100644 --- a/.github/workflows/pr_main.yml +++ b/.github/workflows/pr_main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: cargo build --release --verbose - name: Run tests @@ -25,7 +25,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: cargo build --release --verbose - name: Run tests @@ -37,7 +37,7 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: cargo build --release --verbose - name: Run tests diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 58c0fab..e2611ed 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,99 +2,95 @@ name: Rust -> Build & Test & Release on: push: - branches: [ "main" ] + branches: ["main"] env: CARGO_TERM_COLOR: always + PROJECT_NAME: ${{ github.event.repository.name }} jobs: build_linux: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --release --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v4 + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose - - name: Upload Linux Binary - uses: actions/upload-artifact@v3 - with: - name: rust_tcp_file_transfer_linux_x64_86 - path: target/release/*transfer + - name: Upload Linux Binary + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PROJECT_NAME }}-linux-x64_86 + path: target/release/${{ env.PROJECT_NAME }} build_windows: - runs-on: windows-latest steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --release --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v4 + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose - - name: Upload Windows Binary - uses: actions/upload-artifact@v3 - with: - name: rust_tcp_file_transfer_windows_x64_86 - path: target/release/*transfer.exe + - name: Upload Windows Binary + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PROJECT_NAME }}-windows-x64_86 + path: target/release/${{ env.PROJECT_NAME }}.exe build_macos: - runs-on: macos-latest steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --release --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v4 + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose - - name: Upload MacOS Binary - uses: actions/upload-artifact@v3 - with: - name: rust_tcp_file_transfer_macos_x64_86 - path: target/release/*transfer + - name: Upload MacOS Binary + uses: actions/upload-artifact@v4 + with: + name: ${{ env.PROJECT_NAME }}-macos-arm64 + path: target/release/${{ env.PROJECT_NAME }} release: needs: [build_linux, build_windows, build_macos] runs-on: ubuntu-latest - permissions: - contents: write + permissions: + contents: write steps: + - uses: actions/checkout@v4 + - name: Forge a Folder + run: mkdir Downloads + working-directory: /home/runner/work/${{ env.PROJECT_NAME }}/${{ env.PROJECT_NAME }}/ - - uses: actions/checkout@v3 - - name: Forge a Folder - run: mkdir Downloads - working-directory: /home/runner/work/rust-tcp-file-transfer/rust-tcp-file-transfer/ + - uses: actions/download-artifact@v4 + name: Download + with: + path: Downloads/ - - uses: actions/download-artifact@v3 - name: Download - with: - path: Downloads/ - - - name: Rename Binaries - run: | - mv Downloads/rust_tcp_file_transfer_linux_x64_86/rust-tcp-file-transfer Downloads/rust_tcp_file_transfer_linux_x64_86/rust-tcp-file-transfer-linux_x64_86 - mv Downloads/rust_tcp_file_transfer_windows_x64_86/rust-tcp-file-transfer.exe Downloads/rust_tcp_file_transfer_windows_x64_86/rust-tcp-file-transfer-windows_x64_86.exe - mv Downloads/rust_tcp_file_transfer_macos_x64_86/rust-tcp-file-transfer Downloads/rust_tcp_file_transfer_macos_x64_86/rust-tcp-file-transfer-macos_x64_86 + - name: Rename Binaries + run: | + tree Downloads/ + mv Downloads/${{ env.PROJECT_NAME }}-linux-x64_86/${{ env.PROJECT_NAME }} Downloads/${{ env.PROJECT_NAME }}-linux-x64_86/${{ env.PROJECT_NAME }}-linux-x64_86 + mv Downloads/${{ env.PROJECT_NAME }}-windows-x64_86/${{ env.PROJECT_NAME }}.exe Downloads/${{ env.PROJECT_NAME }}-windows-x64_86/${{ env.PROJECT_NAME }}-windows-x64_86.exe + mv Downloads/${{ env.PROJECT_NAME }}-macos-arm64/${{ env.PROJECT_NAME }} Downloads/${{ env.PROJECT_NAME }}-macos-arm64/${{ env.PROJECT_NAME }}-macos-arm64 + - name: Git Commit SHA + id: vars + run: | + calculatedSha=$(git rev-parse --short ${{ github.sha }}) + echo "short_sha=$calculatedSha" >> $GITHUB_OUTPUT - - name: Git Commit SHA - id: vars - run: | - calculatedSha=$(git rev-parse --short ${{ github.sha }}) - echo "short_sha=$calculatedSha" >> $GITHUB_OUTPUT - - - uses: softprops/action-gh-release@v0.1.15 - name: Release - with: - tag_name: ${{ steps.vars.outputs.short_sha }} - generate_release_notes: true - files: | - Downloads/*linux*/*transfer* - Downloads/*windows*/*transfer* - Downloads/*macos*/*transfer* - \ No newline at end of file + - uses: softprops/action-gh-release@v2 + name: Release + with: + tag_name: ${{ steps.vars.outputs.short_sha }} + generate_release_notes: true + files: | + Downloads/*linux*/${{ env.PROJECT_NAME }}* + Downloads/*windows*/${{ env.PROJECT_NAME }}* + Downloads/*macos*/${{ env.PROJECT_NAME }}* diff --git a/.gitignore b/.gitignore index 196e176..32d71ea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # will have compiled files and executables debug/ target/ +.vscode/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html diff --git a/Cargo.toml b/Cargo.toml index 3d80046..f4a5455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,15 @@ name = "rust-tcp-file-transfer" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[profile.release] +strip = "symbols" +opt-level = 3 +overflow-checks = true +lto = true +codegen-units = 1 +panic = "abort" + +[lints.rust] +unsafe_code = "forbid" [dependencies] diff --git a/README.md b/README.md index 530b6b4..b80f7bd 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,25 @@ # rust-tcp-file-transfer TCP File Transfer Server and Client in Rust +**Usage** +> ./rust-tcp-file-transfer -h +> + + + +**Examples** +> ./rust-tcp-file-transfer -sv -s -l ~/Desktop/cat.png +> +> ./rust-tcp-file-transfer -cl -r -l ~/Documents/ + + + +**TO-DO List** + +☑ Standard library only. + ☑ File transfer over network. ☑ Remove memory limitations. [d42412c](https://github.com/Tahinli/rust-tcp-file-transfer/pull/1/commits/d42412c57d7d95672ba64b3e489b95f1c4b04a08) -☐ Bidirectional transfer. - -☐ Folder transfer. - -☐ Remember where it stopped. - -☐ Reach over NAT (peer to peer). - -☐ Async. +☑ Bidirectional transfer. [b0531de](https://github.com/Tahinli/rust-tcp-file-transfer/commit/b0531deb257332f46fc76de16d3a17fb3b28acee) diff --git a/assets/example.png b/assets/example.png new file mode 100644 index 0000000..c7f3952 Binary files /dev/null and b/assets/example.png differ diff --git a/assets/help_menu.png b/assets/help_menu.png new file mode 100644 index 0000000..311613f Binary files /dev/null and b/assets/help_menu.png differ diff --git a/src/main.rs b/src/main.rs index afd1683..41d3a98 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,490 +1,729 @@ -use std::fs::{File, Metadata, self}; -use std::time::Instant; -use std::net::{TcpListener, TcpStream}; -use std::io::{Read, Write, self, BufWriter, BufReader, BufRead}; use std::env::{self}; +use std::fs::{self, File, Metadata}; +use std::io::{BufWriter, Read, Write}; +use std::net::{IpAddr, TcpListener, TcpStream}; +use std::path::{Path, PathBuf}; +use std::time::Instant; +const BUFFER_SIZE: u64 = 100000; -const BUFFER_SIZE:u64 = 100000; -struct FileInfo - { - file:Option, - location:String, - size_current:usize, - metadata:Option, - } -impl FileInfo - { - fn reading_operations(&mut self, stream:&mut TcpStream, debug_mode:&bool) - { - self.read_metadata(); - match self.metadata - { - Some(ref mut metadata) => - { - if Metadata::is_file(metadata) - { - self.open_file(); - self.send_file(stream, debug_mode); - } - else if Metadata::is_symlink(metadata) - { - self.open_file(); - self.send_file(stream, debug_mode); - } - else - { - //path recognition and creation on the other side - //std:path - panic!("\n\tError: Folder Transfers've not Supported yet\n") - } - } - None => - { - println!("Error: Read Metadata -> {}", self.location); - } - } - } - fn writing_operations(&mut self, stream:&mut TcpStream, debug_mode:&bool) - { - self.forge_file(); - self.write_file(stream, debug_mode); - } - fn read_metadata(&mut self) - { - self.metadata = Some(fs::metadata(&self.location).expect("Error: Read Metadata")); - } - fn open_file(&mut self) - { - match File::open(&self.location) - { - Ok(file) => - { - self.file = Some(file); - } - Err(err_val) => - { - println!("Error: Open File -> {} | Error: {}", self.location, err_val); - return; - } - } - - } - fn send_file(&mut self, stream:&mut TcpStream, debug_mode:&bool) - { - let size = self.metadata.as_ref().unwrap().len(); - let mut iteration = (size/BUFFER_SIZE)+1; - let total_iteration = iteration; - self.handshake_validation(stream, size, debug_mode); - println!("Size = {}", size); - println!("Iteration = {}", iteration); - while iteration != 0 - { - iteration -= 1; - let mut buffer = [0u8;BUFFER_SIZE as usize]; - if iteration != 0 - { - self.read_exact(&mut buffer, debug_mode); - } - else - { - self.read_exact(&mut buffer[..(size%BUFFER_SIZE) as usize], debug_mode); - } - if *debug_mode - { - println!("Read Data = {:#?}", buffer); - } - self.send_exact(&mut buffer, stream, debug_mode); - println!("%{}", 100 as f64 -((iteration as f64/total_iteration as f64)*100 as f64)); - } - } - fn handshake_validation(&mut self, stream:&mut TcpStream, size:u64, debug_mode:&bool) - { - self.send_exact(String::from(size.to_string()+"\n").as_bytes(), stream, debug_mode); - match self.recv_until(stream, '\n', debug_mode) - { - Some(handshake_callback) => - { - if handshake_callback == size.to_string().as_bytes().to_vec() - { - println!("Done: Handshake -> {}", self.location); - if *debug_mode - { - println!("{:#?} ", handshake_callback); - } - } - else - { - println!("Error: Handshake -> {}", self.location); - println!("{:#?} ", handshake_callback); - panic!() - } - } - None => - { - panic!() - } - } - } - fn read_exact(&mut self, buffer:&mut [u8], debug_mode:&bool) - { - match self.file.as_ref().unwrap().read_exact(buffer) - { - Ok(_) => - { - if *debug_mode - { - println!("Done: Read Bytes -> {}", self.location); - println!("{:#?}", buffer); - } - } - Err(err_val) => - { - println!("Error: Read Bytes -> {} | Error: {}", self.location, err_val); - panic!() - } - } - } - fn send_exact(&mut self, buffer:&[u8], stream:&mut TcpStream, debug_mode:&bool) - { - let mut stream_writer = BufWriter::new(stream.try_clone().unwrap()); - match stream_writer.write_all(buffer) - { - Ok(_) => - { - self.size_current += buffer.len(); - if *debug_mode - { - println!("Done: Send Bytes -> {}", self.location); - println!("{:#?}", buffer); - } - } - Err(err_val) => - { - println!("Error: Send Bytes -> {} | Error: {}", self.location, err_val); - panic!(); - } - } - match stream_writer.flush() - { - Ok(_) => - { - if *debug_mode - { - println!("Done: Flush -> {}", self.location); - } - } - Err(err_val) => - { - println!("Error: Flush -> {} | Error: {}", self.location, err_val); - panic!() - } - } - } - fn recv_exact(&mut self, buffer:&mut [u8], stream:&mut TcpStream, debug_mode:&bool) - { - match stream.read_exact(buffer) - { - Ok(_) => - { - self.size_current += buffer.len(); - if *debug_mode - { - println!("Done: Receive Bytes -> {}", self.location); - println!("{:#?}", buffer); - } - } - Err(err_val) => - { - println!("Error: Receive Bytes -> {} | Error: {}", self.location, err_val); - panic!(); - } - } - } - fn recv_until(&mut self, stream:&mut TcpStream, until:char, debug_mode:&bool) -> Option> - { - let mut buffer = Vec::new(); - let mut stream_reader = BufReader::new(stream.try_clone().unwrap()); - match stream_reader.read_until(until as u8,&mut buffer) - { - Ok(_) => - { - if *debug_mode - { - println!("Done: Receive Until -> {}", self.location); - println!("{:#?}", buffer); - } - buffer.pop(); - } - Err(err_val) => - { - println!("Error: Receive Until -> {} | Error: {}", self.location, err_val); - return None; - } - } - return Some(buffer); - } - fn forge_file(&mut self) - { - self.file = Some(File::create(&self.location).expect("Error: Create File")); - } - fn handshake_recv(&mut self, stream:&mut TcpStream, debug_mode:&bool) -> u64 - { - match self.recv_until(stream, '\n', debug_mode) - { - Some(mut handshake) => - { - println!("Done: Handshake -> {}", self.location); - if *debug_mode - { - println!("{:#?} ", handshake); - } - let size = String::from_utf8(handshake.clone()).unwrap().parse().unwrap(); - handshake.push(b'\n'); - self.send_exact(&handshake.as_slice(), stream, debug_mode); - size - } - None => - { - println!("Error: Handshake -> {}", self.location); - 0 - } - } - } - fn save_exact(&mut self, buffer:&[u8], debug_mode:&bool) - { - let mut file_writer = BufWriter::new(self.file.as_ref().unwrap()); - match file_writer.write_all(buffer) - { - Ok(_) => - { - if *debug_mode - { - println!("Done: Write -> {} | {} bytes", self.location, self.size_current); - println!("{:#?}", buffer); - } - } - Err(err_val) => - { - println!("Error: Write -> {} | Error: {}", self.location,err_val); - panic!(); - } - } - match file_writer.flush() - { - Ok(_) => - { - if *debug_mode - { - println!("Done: Flush -> {}", self.location); - } - } - Err(err_val) => - { - println!("Error: Flush -> {} | Error: {}", self.location,err_val); - panic!(); - } - } - } - fn write_file(&mut self, stream:&mut TcpStream, debug_mode:&bool) - { - let size = self.handshake_recv(stream, debug_mode); - let mut iteration:u64 = (size/BUFFER_SIZE)+1; - let total_iteration = iteration; - println!("Size = {}", size); - println!("Iteration = {}", iteration); - while iteration != 0 - { - iteration -= 1; - let mut buffer = [0u8;BUFFER_SIZE as usize]; - self.recv_exact(&mut buffer, stream, debug_mode); - if iteration != 0 - { - self.save_exact(&buffer, debug_mode); - } - else - { - self.save_exact(&buffer[..(size%BUFFER_SIZE) as usize], debug_mode); - } - println!("%{}", 100 as f64 -((iteration as f64/total_iteration as f64)*100 as f64)); - } - } - } -enum DebugMode - { - On, - Off - } -impl DebugMode { - fn debug_mode(self) -> bool - { - match self - { - DebugMode::On => - { - println!("Debug: ON"); - let debug = true; - debug - } - DebugMode::Off => - { - println!("Debug: OFF"); - let debug = false; - debug - } - } - } +#[derive(Debug)] +enum FileType { + Symlink, + File, + Folder, } -enum Connection - { - Server(String, String), - Client(String, String), +impl FileType { + fn what_type(value: u8, file_info: &FileInfo, debug_mode: &bool) -> FileType { + match value { + 100 => { + if *debug_mode { + println!( + "Done: Symlink Detected -> {}", + file_info.location.as_ref().unwrap() + ); + } + FileType::Symlink + } + 101 => { + if *debug_mode { + println!( + "Done: File Detected -> {}", + file_info.location.as_ref().unwrap() + ); + } + FileType::File + } + 102 => { + if *debug_mode { + println!( + "Done: Folder Detected -> {}", + file_info.location.as_ref().unwrap() + ); + } + FileType::Folder + } + _ => { + println!( + "Error: Undefined Type -> {}", + file_info.location.as_ref().unwrap() + ); + panic!() + } + } } +} +#[derive(Debug)] +struct UserEnvironment { + ip: IpAddr, + port: u16, + server: bool, + send: bool, + location: Option, + debug: bool, +} +impl UserEnvironment { + fn new() -> UserEnvironment { + UserEnvironment { + ip: "127.0.0.1".parse().unwrap(), + port: 2121, + server: false, + send: false, + location: None, + debug: false, + } + } +} +#[derive(Debug)] +struct FileInfo { + file: Option, + location: Option, + sign: Option, + size_total: u64, + size_current: u64, + metadata: Option, + progress: u8, +} +impl FileInfo { + fn new() -> FileInfo { + FileInfo { + file: None, + location: None, + sign: None, + size_total: 0, + size_current: 0, + metadata: None, + progress: 0, + } + } + fn pass_user_environment(&mut self, user_environment: &UserEnvironment) { + self.location = user_environment.location.clone(); + self.sign = user_environment.location.clone(); + } + fn reading_operations(&mut self, stream: &mut TcpStream, debug_mode: &bool) { + match self.location.as_ref() { + Some(_) => { + self.read_metadata(debug_mode); + match self.metadata { + Some(ref mut metadata) => { + if Metadata::is_symlink(metadata) { + //Unix-Windows Problem + println!("\n\tError: Symlink Transfers've not Supported yet\n"); + //self.open_file(debug_mode); + //self.send_file(stream, &(100 as u8),debug_mode); + } else if Metadata::is_file(metadata) { + self.open_file(debug_mode); + self.send_file(stream, &(101_u8), debug_mode); + } else if Metadata::is_dir(metadata) { + //path recognition and creation on the other side + //std:path + println!("\n\tError: Folder Transfers've not Supported yet\n"); + //self.open_file(debug_mode); + //self.send_file(stream, &(102 as u8),debug_mode); + } else { + println!( + "Error: Undefined Type -> {}", + self.location.as_ref().unwrap() + ); + } + } + None => { + println!( + "Error: Read Metadata -> {}", + self.location.as_ref().unwrap() + ); + } + } + } + None => { + println!("Error: Reading Operations -> {:#?}", &self.location); + panic!(); + } + } + } + fn writing_operations(&mut self, stream: &mut TcpStream, debug_mode: &bool) { + self.write_file(stream, debug_mode); + self.cleaning(); + } + fn cleaning(&mut self) { + self.location = self.sign.clone(); + self.size_current = 0; + } + fn read_metadata(&mut self, debug_mode: &bool) { + let path = PathBuf::from(self.location.as_ref().unwrap()); + if path.is_symlink() { + match path.symlink_metadata() { + Ok(metadata) => { + self.metadata = Some(metadata); + } + Err(err_val) => { + println!( + "Error: Symlink Metadata -> {:#?} | Error: {}", + &self.location, err_val + ); + } + } + } else { + match fs::metadata(self.location.as_ref().unwrap()) { + Ok(metadata) => { + self.metadata = Some(metadata); + if *debug_mode { + println!("Done: Read Metadata -> {:#?}", self.metadata); + } + } + Err(err_val) => { + println!( + "Error: Read Metadata -> {} | Error: {}", + &self.location.as_ref().unwrap(), + err_val + ); + } + } + } + } + fn open_file(&mut self, debug_mode: &bool) { + match File::options() + .read(true) + .write(true) + .open(self.location.as_ref().unwrap()) + { + Ok(file) => { + self.file = Some(file); + if *debug_mode { + println!("Done : Open File -> {:#?}", self.file); + } + } + Err(err_val) => { + println!( + "Error: Open File -> {} | Error: {}", + self.location.as_ref().unwrap(), + err_val + ); + } + } + } + fn send_file(&mut self, stream: &mut TcpStream, what_type: &u8, debug_mode: &bool) { + self.size_total = self.metadata.as_ref().unwrap().len(); + let mut iteration = (self.size_total / BUFFER_SIZE) + 1; + let total_iteration = iteration; + let path_buf = PathBuf::from(Path::new(self.location.as_ref().unwrap())); + match what_type { + 100 => { + if *debug_mode { + println!( + "Done: Symlink Detected -> {}", + self.location.as_ref().unwrap() + ); + } + self.callback_validation(stream, &String::from("100"), debug_mode); + } + 101 => { + if *debug_mode { + println!("Done: File Detected -> {}", self.location.as_ref().unwrap()); + } + self.callback_validation(stream, &String::from("101"), debug_mode) + } + 102 => { + if *debug_mode { + println!( + "Done: Folder Detected -> {}", + self.location.as_ref().unwrap() + ); + } + self.callback_validation(stream, &String::from("102"), debug_mode) + } + _ => { + println!( + "Error: Undefined Type Detected ->{}", + self.location.as_ref().unwrap() + ); + return; + } + } + self.callback_validation(stream, &(self.size_total.to_string()), debug_mode); + self.callback_validation( + stream, + &path_buf.file_name().unwrap().to_str().unwrap().to_string(), + debug_mode, + ); -impl Connection - { - fn server(self, file_info:&mut FileInfo, debug_mode:bool) - { - print!("Server -> "); - let ip:String; - let port:String; - let address:String; - match self - { - Connection::Server(in1, in2) => - { - ip = in1.trim_end().to_string(); - port = in2.trim_end().to_string(); - address = format!("{}:{}", ip, port); - println!("{}", address); - } - _ => return - } - let socket = TcpListener::bind(address); - for stream in socket.expect("Error: Can't Check Connections").incoming() - { - match stream - { - Ok(mut stream) => - { - let mut stay = true; - while stay - { - println!("Connected"); - let start_time = Instant::now(); - FileInfo::writing_operations(file_info, &mut stream, &debug_mode); - let finish_time = Instant::now(); - println!("Passed: Total -> {:#?}", finish_time.duration_since(start_time)); - stay = false; - } - } - Err(e) => - { - println!("Error: Can't Visit Stream -> {}", e); - return; - } - } - } + self.show_info(&iteration, debug_mode); + while iteration != 0 { + iteration -= 1; + let mut buffer = [0u8; BUFFER_SIZE as usize]; + if iteration != 0 { + self.read_exact(&mut buffer, debug_mode); + } else { + self.read_exact( + &mut buffer[..(self.size_total % BUFFER_SIZE) as usize], + debug_mode, + ); } - fn client(self, file_info:&mut FileInfo, debug_mode:bool) - { - print!("Client -> "); - let ip:String; - let port:String; - let address:String; - match self - { - Connection::Client(in1, in2) => - { - ip = in1.trim_end().to_string(); - port = in2.trim_end().to_string(); - address = format!("{}:{}", ip, port); - println!("{}", address); - } - _ => return - } - match TcpStream::connect(address) - { - Ok(mut stream) => - { - println!("Connected"); - let start_time = Instant::now(); - FileInfo::reading_operations(file_info, &mut stream, &debug_mode); - let finish_time = Instant::now(); - println!("Passed: Total -> {:#?}", finish_time.duration_since(start_time)); - } - Err(e) => - { - println!("Error: Connection -> {}", e); - } - } - + if *debug_mode { + println!("Read Data = {:#?}", buffer); } + self.send_exact(&buffer, stream, debug_mode); + self.show_progress(iteration, total_iteration); + } } + fn callback_validation(&mut self, stream: &mut TcpStream, data: &String, debug_mode: &bool) { + let mut data_vec: Vec = data.as_bytes().to_vec(); + let mut terminator_vec: Vec = vec![b'\n'; BUFFER_SIZE as usize - data_vec.len()]; + data_vec.append(&mut terminator_vec); + drop(terminator_vec); + let mut data_exact: [u8; BUFFER_SIZE as usize] = [b'\n'; BUFFER_SIZE as usize]; + data_exact.swap_with_slice(data_vec[..].as_mut()); + drop(data_vec); + self.send_exact(&data_exact, stream, debug_mode); + let mut data_exact_check: [u8; BUFFER_SIZE as usize] = [b'\n'; BUFFER_SIZE as usize]; + self.recv_exact(&mut data_exact_check, stream, debug_mode); + if data_exact_check == data_exact { + if *debug_mode { + println!("Done: Callback -> {}", self.location.as_ref().unwrap()); + println!("{:#?} ", data_exact_check); + } + } else { + println!("Error: Callback -> {}", self.location.as_ref().unwrap()); + println!("{:#?} ", data_exact_check); + panic!() + } + } + fn read_exact(&mut self, buffer: &mut [u8], debug_mode: &bool) { + match self.file.as_ref().unwrap().read_exact(buffer) { + Ok(_) => { + if *debug_mode { + println!("Done: Read Bytes -> {}", self.location.as_ref().unwrap()); + println!("{:#?}", buffer); + } + } + Err(err_val) => { + println!( + "Error: Read Bytes -> {} | Error: {}", + self.location.as_ref().unwrap(), + err_val + ); + panic!() + } + } + } + fn send_exact(&mut self, buffer: &[u8], stream: &mut TcpStream, debug_mode: &bool) { + let mut stream_writer = BufWriter::new(stream.try_clone().unwrap()); + match stream_writer.write_all(buffer) { + Ok(_) => { + self.size_current += buffer.len() as u64; + if *debug_mode { + println!("Done: Send Bytes -> {:#?}", self.location); + println!("{:#?}", buffer); + } + } + Err(err_val) => { + println!( + "Error: Send Bytes -> {:#?} | Error: {}", + self.location, err_val + ); + panic!(); + } + } + match stream_writer.flush() { + Ok(_) => { + if *debug_mode { + println!("Done: Flush -> {:#?}", self.location); + } + } + Err(err_val) => { + println!("Error: Flush -> {:#?} | Error: {}", self.location, err_val); + panic!() + } + } + } + fn recv_exact(&mut self, buffer: &mut [u8], stream: &mut TcpStream, debug_mode: &bool) { + match stream.read_exact(buffer) { + Ok(_) => { + self.size_current += buffer.len() as u64; + if *debug_mode { + println!("Done: Receive Bytes -> {:#?}", self.location); + println!("{:#?}", buffer); + } + } + Err(err_val) => { + println!( + "Error: Receive Bytes -> {:#?} | Error: {}", + self.location, err_val + ); + panic!(); + } + } + } + fn forge_file(&mut self, location: String, debug_mode: &bool) { + //dont forget + //directory recognition required for received location + match self.location.as_ref() { + Some(self_location) => { + let mut path = PathBuf::from(&self_location); + path.push(location); + self.forge_folder(self_location.clone(), debug_mode); + self.location = Some(path.to_str().unwrap().to_string()); + } + None => { + self.location = Some(location); + } + } + match File::create(self.location.as_ref().unwrap()) { + Ok(file) => { + if *debug_mode { + println!("Done Forge File -> {:#?}", file); + } + } + Err(err_val) => { + println!( + "Error: Forge File -> {:#?} | Error: {}", + self.location.as_ref(), + err_val + ); + } + } + } + fn forge_folder(&mut self, location: String, debug_mode: &bool) { + match fs::create_dir_all(&location) { + Ok(_) => { + if *debug_mode { + println!("Done: Forge Folder -> {}", &location); + } + } + Err(err_val) => { + println!("Error: Forge Folder -> {} | Error: {}", location, err_val); + } + } + } + fn callback_recv(&mut self, stream: &mut TcpStream, debug_mode: &bool) -> String { + let mut buffer: [u8; BUFFER_SIZE as usize] = [0; BUFFER_SIZE as usize]; + self.recv_exact(&mut buffer, stream, debug_mode); + if *debug_mode { + println!("Done: Callback -> {:#?}", self.location); + println!("{:#?} ", buffer); + } + let data = String::from_utf8( + buffer + .split(|element| *element == b'\n') + .next() + .unwrap() + .to_vec(), + ) + .unwrap(); + if *debug_mode { + println!("Done: Split -> {:#?}", self.location); + println!("{:#?}", data); + } + self.send_exact(&buffer, stream, debug_mode); + data + } + fn save_exact(&mut self, buffer: &[u8], debug_mode: &bool) { + let mut file_writer = BufWriter::new(self.file.as_ref().unwrap()); + if *debug_mode { + println!("{:#?}", file_writer); + } + match file_writer.write_all(buffer) { + Ok(_) => { + if *debug_mode { + println!( + "Done: Write -> {} | {} bytes", + self.location.as_ref().unwrap(), + self.size_current + ); + println!("{:#?}", buffer); + } + } + Err(err_val) => { + println!( + "Error: Write -> {} | Error: {}", + self.location.as_ref().unwrap(), + err_val + ); + panic!(); + } + } + match file_writer.flush() { + Ok(_) => { + if *debug_mode { + println!("Done: Flush -> {}", self.location.as_ref().unwrap()); + } + } + Err(err_val) => { + println!( + "Error: Flush -> {} | Error: {}", + self.location.as_ref().unwrap(), + err_val + ); + panic!(); + } + } + } + fn write_file(&mut self, stream: &mut TcpStream, debug_mode: &bool) { + let what_type: FileType = FileType::what_type( + self.callback_recv(stream, debug_mode) + .parse::() + .unwrap(), + self, + debug_mode, + ); + self.size_total = self.callback_recv(stream, debug_mode).parse().unwrap(); + let location: String = self.callback_recv(stream, debug_mode); + self.file_forger(what_type, location, debug_mode); + self.open_file(debug_mode); + let mut iteration: u64 = (self.size_total / BUFFER_SIZE) + 1; + let total_iteration = iteration; + self.show_info(&iteration, debug_mode); + while iteration != 0 { + iteration -= 1; + let mut buffer = [0u8; BUFFER_SIZE as usize]; + self.recv_exact(&mut buffer, stream, debug_mode); + if iteration != 0 { + self.save_exact(&buffer, debug_mode); + } else { + self.save_exact( + &buffer[..(self.size_total % BUFFER_SIZE) as usize], + debug_mode, + ); + } + self.show_progress(iteration, total_iteration); + } + } + fn file_forger(&mut self, file_type: FileType, location: String, debug_mode: &bool) { + match file_type { + FileType::Symlink => { + self.forge_file(location, debug_mode); + } + FileType::File => { + self.forge_file(location, debug_mode); + } + FileType::Folder => { + self.forge_file(location, debug_mode); + } + } + } + fn show_info(&mut self, iteration: &u64, debug_mode: &bool) { + println!("File = {}", self.location.as_ref().unwrap()); + println!("Size = {}", self.size_total); + if *debug_mode { + println!("Iteration = {}", iteration); + } + } + fn show_progress(&mut self, iteration: u64, total_iteration: u64) { + if iteration % 10 == 0 { + let progress: u8 = + 100_u8 - ((iteration as f64 / total_iteration as f64) * 100_f64) as u8; + if progress != self.progress { + self.progress = progress; + println!("%{}", self.progress); + } + } + } +} +enum Connection { + Server(String, String), + Client(String, String), +} -fn take_string(output:String) -> String - { - let mut input = String::new(); - println!("{}", output); - io::stdin().read_line(&mut input).expect("Error: Failed to Read from Console"); - input - } -fn take_arg() -> String - { - env::args().last().as_deref().unwrap_or("default").to_string() - } -fn debug_mod() -> DebugMode - { - match &take_string("Input: Debug -> On '1', Debug -> Off '0'".to_string())[0..1] - { - "1" => - { - DebugMode::On - } - "0" => - { - DebugMode::Off - } - input => - { - println!("Error: Give Valid Input, You Gave : {}", input); - panic!() - } +impl Connection { + fn server(self, file_info: &mut FileInfo, user_environment: &UserEnvironment) { + print!("Server -> "); + if user_environment.debug { + println!("{:#?}", user_environment); + println!("{:#?}", file_info); + } + let ip: String; + let port: String; + let address: String; + match self { + Connection::Server(in1, in2) => { + ip = in1.trim_end().to_string(); + port = in2.trim_end().to_string(); + address = format!("{}:{}", ip, port); + println!("{}", address); } - } -fn main() - { - //DONT FORGET - //First we should check folder structure and validation then make connection. - println!("Hello, world!"); - - let mut data = FileInfo - { - file:None, - location:take_arg(), - size_current:0 as usize, - metadata:None, - }; - match &take_string("Input: Server 's', Client 'c'".to_string())[0..1] - { - "s" => - { - Connection::server - (Connection::Server(take_string("Input: Server Stream IP Address".to_string()), - take_string("Input: Server Stream Port Address".to_string())), - &mut data, DebugMode::debug_mode(debug_mod())); - }, - "c" => - { - Connection::client - (Connection::Client(take_string("Input: Server IP Address to Connect".to_string()), - take_string("Input: Server Port Address to Connect".to_string())), - &mut data, DebugMode::debug_mode(debug_mod())); - } - input => - { - println!("Error: Give Valid Input, You Gave : {}", input); - return; - } + _ => return, + } + let socket = TcpListener::bind(address); + for stream in socket.expect("Error: Can't Check Connections").incoming() { + match stream { + Ok(mut stream) => { + println!("Connected"); + send_or_receive( + file_info, + &mut stream, + &user_environment.debug, + user_environment, + ); + } + Err(e) => { + println!("Error: Can't Visit Stream -> {}", e); + return; + } } + } } + fn client(self, file_info: &mut FileInfo, user_environment: &UserEnvironment) { + print!("Client -> "); + if user_environment.debug { + println!("{:#?}", user_environment); + println!("{:#?}", file_info); + } + let ip: String; + let port: String; + let address: String; + match self { + Connection::Client(in1, in2) => { + ip = in1.trim_end().to_string(); + port = in2.trim_end().to_string(); + address = format!("{}:{}", ip, port); + println!("{}", address); + } + _ => return, + } + match TcpStream::connect(address) { + Ok(mut stream) => { + println!("Connected"); + send_or_receive( + file_info, + &mut stream, + &user_environment.debug, + user_environment, + ); + } + Err(e) => { + println!("Error: Connection -> {}", e); + } + } + } +} +fn send_or_receive( + file_info: &mut FileInfo, + stream: &mut TcpStream, + debug_mode: &bool, + user_environment: &UserEnvironment, +) { + match user_environment.send { + true => { + let start_time = Instant::now(); + FileInfo::reading_operations(file_info, stream, debug_mode); + let finish_time = Instant::now(); + println!("Done: Transfer"); + println!( + "Passed: Total -> {:#?}", + finish_time.duration_since(start_time) + ); + } + false => { + let start_time = Instant::now(); + FileInfo::writing_operations(file_info, stream, debug_mode); + let finish_time = Instant::now(); + println!("Done: Transfer"); + println!( + "Passed: Total -> {:#?}", + finish_time.duration_since(start_time) + ); + } + } +} +fn take_args(mut user_environment: UserEnvironment) -> Option { + let env_args: Vec = env::args().collect(); + if env_args.len() > 16 { + println!( + "Error: Too Many Arguments, You Gave {} Arguments", + env_args.len() + ); + return None; + } + let mut i = 1; + while i < env_args.len() { + match env_args[i].as_str() { + "--ip" | "-i" => { + user_environment.ip = env_args[i + 1].parse().unwrap(); + i += 1; + } + "--port" | "-p" => { + user_environment.port = env_args[i + 1].parse().unwrap(); + i += 1; + } + "--location" | "-l" => { + user_environment.location = Some(env_args[i + 1].parse().unwrap()); + i += 1; + } + "--server" | "-sv" => { + user_environment.server = true; + } + "--client" | "-cl" => { + user_environment.server = false; + } + "--send" | "-s" => { + user_environment.send = true; + } + "--receive" | "-r" => { + user_environment.send = false; + } + "--debug" | "-d" => { + user_environment.debug = true; + } + "--help" | "-h" => { + show_help(); + return None; + } + err => { + println!("Error: Invalid Argument, You Gave {}", err); + return None; + } + } + i += 1; + } + Some(user_environment) +} +fn show_help() { + println!("\n\n\n"); + println!(" Arguments | Details | Defaults"); + println!("----------------------------------------------------------------------"); + println!(" -i -> --ip | Specifies IP Address | 127.0.0.1"); + println!(" -p -> --port | Specifies Port Address | 2121"); + println!(" -l -> --location | Specifies Location Address | Same as Program"); + println!(" -sv -> --server | Starts as a Server | False"); + println!(" -cl -> --client | Starts as a Client | True"); + println!(" -s -> --send | Starts as a Sender | False"); + println!(" -r -> --receive | Starts as a Receiver | True"); + println!(" -d -> --debug | Starts in Debug Mode | False"); + println!(" -h -> --help | Shows Help | False"); + println!("\n\n\n"); +} +fn main() { + //DONT FORGET + //First we should check folder structure and validation then make connection. + println!("Hello, world!"); + let mut file_info: FileInfo = FileInfo::new(); + let user_environment: UserEnvironment = match take_args(UserEnvironment::new()) { + Some(usr_env) => usr_env, + None => { + return; + } + }; + file_info.pass_user_environment(&user_environment); + match user_environment.server { + true => { + Connection::server( + Connection::Server( + user_environment.ip.to_string(), + user_environment.port.to_string(), + ), + &mut file_info, + &user_environment, + ); + } + false => { + Connection::client( + Connection::Client( + user_environment.ip.to_string(), + user_environment.port.to_string(), + ), + &mut file_info, + &user_environment, + ); + } + } +}