diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index db94fa1..46a2f6a 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - 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 8ac36d2..db4e60d 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@v4 + - uses: actions/checkout@v3 - name: Build run: cargo build --release --verbose - name: Run tests @@ -25,7 +25,7 @@ jobs: runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build run: cargo build --release --verbose - name: Run tests @@ -37,7 +37,7 @@ jobs: runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - name: Build run: cargo build --release --verbose - name: Run tests diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e2611ed..58c0fab 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,95 +2,99 @@ 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@v4 - - name: Build - run: cargo build --release --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v3 + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose - - name: Upload Linux Binary - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PROJECT_NAME }}-linux-x64_86 - path: target/release/${{ env.PROJECT_NAME }} + - name: Upload Linux Binary + uses: actions/upload-artifact@v3 + with: + name: rust_tcp_file_transfer_linux_x64_86 + path: target/release/*transfer build_windows: + runs-on: windows-latest steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --release --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v3 + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose - - name: Upload Windows Binary - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PROJECT_NAME }}-windows-x64_86 - path: target/release/${{ env.PROJECT_NAME }}.exe + - name: Upload Windows Binary + uses: actions/upload-artifact@v3 + with: + name: rust_tcp_file_transfer_windows_x64_86 + path: target/release/*transfer.exe build_macos: + runs-on: macos-latest steps: - - uses: actions/checkout@v4 - - name: Build - run: cargo build --release --verbose - - name: Run tests - run: cargo test --verbose + - uses: actions/checkout@v3 + - name: Build + run: cargo build --release --verbose + - name: Run tests + run: cargo test --verbose - - name: Upload MacOS Binary - uses: actions/upload-artifact@v4 - with: - name: ${{ env.PROJECT_NAME }}-macos-arm64 - path: target/release/${{ env.PROJECT_NAME }} + - name: Upload MacOS Binary + uses: actions/upload-artifact@v3 + with: + name: rust_tcp_file_transfer_macos_x64_86 + path: target/release/*transfer 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/download-artifact@v4 - name: Download - with: - path: Downloads/ + - uses: actions/checkout@v3 + - name: Forge a Folder + run: mkdir Downloads + working-directory: /home/runner/work/rust-tcp-file-transfer/rust-tcp-file-transfer/ - - 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 + - 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 - - 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 }}* + - 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 diff --git a/.gitignore b/.gitignore index 32d71ea..196e176 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ # 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 f4a5455..3d80046 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,15 +3,6 @@ name = "rust-tcp-file-transfer" version = "0.1.0" edition = "2021" -[profile.release] -strip = "symbols" -opt-level = 3 -overflow-checks = true -lto = true -codegen-units = 1 -panic = "abort" - -[lints.rust] -unsafe_code = "forbid" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/README.md b/README.md index b80f7bd..530b6b4 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,16 @@ # 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. [b0531de](https://github.com/Tahinli/rust-tcp-file-transfer/commit/b0531deb257332f46fc76de16d3a17fb3b28acee) +☐ Bidirectional transfer. + +☐ Folder transfer. + +☐ Remember where it stopped. + +☐ Reach over NAT (peer to peer). + +☐ Async. diff --git a/assets/example.png b/assets/example.png deleted file mode 100644 index c7f3952..0000000 Binary files a/assets/example.png and /dev/null differ diff --git a/assets/help_menu.png b/assets/help_menu.png deleted file mode 100644 index 311613f..0000000 Binary files a/assets/help_menu.png and /dev/null differ diff --git a/src/main.rs b/src/main.rs index 41d3a98..ee5d644 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,729 +1,491 @@ -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::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}; -const BUFFER_SIZE: u64 = 100000; -#[derive(Debug)] -enum FileType { - Symlink, - File, - Folder, -} -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!() - } - } +const BUFFER_SIZE:u64 = 100000; +struct FileInfo + { + file:Option, + location:String, + size_current:usize, + metadata:Option, } -} -#[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() - ); - } +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); + } } - None => { - println!( - "Error: Read Metadata -> {}", - self.location.as_ref().unwrap() - ); + } + 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; + } } - } + } - 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); + 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)); } - } - Err(err_val) => { - println!( - "Error: Read Metadata -> {} | Error: {}", - &self.location.as_ref().unwrap(), - err_val - ); - } } - } + 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) + { + let mut stream_reader = BufReader::new(stream.try_clone().unwrap()); + match stream_reader.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)); + } + } } - fn open_file(&mut self, debug_mode: &bool) { - match File::options() - .read(true) - .write(true) - .open(self.location.as_ref().unwrap()) +enum DebugMode + { + On, + Off + } +impl DebugMode { + fn debug_mode(self) -> bool { - Ok(file) => { - self.file = Some(file); - if *debug_mode { - println!("Done : Open File -> {:#?}", self.file); + match self + { + DebugMode::On => + { + println!("Debug: ON"); + let debug = true; + debug + } + DebugMode::Off => + { + println!("Debug: OFF"); + let debug = false; + debug + } } - } - Err(err_val) => { - println!( - "Error: Open File -> {} | Error: {}", - self.location.as_ref().unwrap(), - err_val - ); - } } +} +enum Connection + { + Server(String, String), + Client(String, String), } - 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, - ); - 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, - ); +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; + } + } + } } - if *debug_mode { - println!("Read Data = {:#?}", buffer); + 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); + } + } + } - 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), -} -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); - } - _ => 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 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 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 take_arg() -> String + { + env::args().last().as_deref().unwrap_or("default").to_string() } -} -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 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!() + } + } } -} -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; +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; + } + } } - 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, - ); - } - } -}