diff --git a/Cargo.lock b/Cargo.lock index 98e07ed..1b60398 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,6 +279,7 @@ dependencies = [ "crossterm 0.18.2", "getopts", "tui", + "users", ] [[package]] @@ -306,6 +307,16 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 2cdd272..7e79e06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ readme = "README.md" crossterm = "0.18.2" getopts = "0.2.21" tui = { version = "0.12.0", features = ["crossterm"], default-features = false } +users = "0.11.0" [[bin]] name = "termscp" diff --git a/src/filetransfer/mod.rs b/src/filetransfer/mod.rs index 5d7ce74..6757e6f 100644 --- a/src/filetransfer/mod.rs +++ b/src/filetransfer/mod.rs @@ -45,7 +45,14 @@ pub enum FileTransferProtocol { #[derive(PartialEq, Clone)] pub enum FileTransferError { - + ConnectionError, + BadAddress, + AuthenticationFailed, + NoSuchFileOrDirectory, + DirStatFailed, + FileReadonly, + DownloadError, + UnknownError, } /// ## FileTransfer diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 899a9ec..aabca24 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -24,12 +24,13 @@ */ use std::path::PathBuf; -use std::time::Instant; +use std::time::SystemTime; /// ## FsEntry /// /// FsEntry represents a generic entry in a directory +#[derive(Clone)] pub enum FsEntry { Directory(FsDirectory), File(FsFile) @@ -39,11 +40,12 @@ pub enum FsEntry { /// /// Directory provides an interface to file system directories +#[derive(Clone)] pub struct FsDirectory { pub name: PathBuf, - pub last_change_time: Instant, - pub last_access_time: Instant, - pub creation_time: Instant, + pub last_change_time: SystemTime, + pub last_access_time: SystemTime, + pub creation_time: SystemTime, pub readonly: bool, pub symlink: Option, // UNIX only pub user: Option, // UNIX only @@ -55,13 +57,14 @@ pub struct FsDirectory { /// /// FsFile provides an interface to file system files +#[derive(Clone)] pub struct FsFile { pub name: PathBuf, - pub last_change_time: Instant, - pub last_access_time: Instant, - pub creation_time: Instant, + pub last_change_time: SystemTime, + pub last_access_time: SystemTime, + pub creation_time: SystemTime, pub size: usize, - pub ftype: String, // File type + pub ftype: Option, // File type pub readonly: bool, pub symlink: Option, // UNIX only pub user: Option, // UNIX only diff --git a/src/host/mod.rs b/src/host/mod.rs index d32a18c..9af9e77 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -23,3 +23,251 @@ * */ +use std::fs::{self, Metadata}; +use std::path::{Path, PathBuf}; +use std::time::SystemTime; +// Metadata ext +#[cfg(any(unix, macos, linux))] +extern crate users; +#[cfg(any(unix, macos, linux))] +use std::os::unix::fs::{MetadataExt, PermissionsExt}; +#[cfg(any(unix, macos, linux))] +use users::{get_group_by_gid, get_user_by_uid}; + +// Locals +use crate::fs::{FsDirectory, FsEntry, FsFile}; + +/// ## HostError +/// +/// HostError provides errors related to local host file system +pub enum HostError { + NoSuchFileOrDirectory, + ReadonlyFile, + DirNotAccessible, +} + +/// ## Localhost +/// +/// Localhost is the entity which holds the information about the current directory and host. +/// It provides functions to navigate across the local host file system +pub struct Localhost { + wrkdir: PathBuf, + files: Vec, +} + +impl Localhost { + /// ### new + /// + /// Instantiates a new Localhost struct + pub fn new(wrkdir: PathBuf) -> Result { + let mut host: Localhost = Localhost { + wrkdir: wrkdir, + files: Vec::new(), + }; + // Check if dir exists + if !host.file_exists(host.wrkdir.as_path()) { + return Err(HostError::NoSuchFileOrDirectory); + } + // Retrieve files for provided path + host.files = match host.scan_dir() { + Ok(files) => files, + Err(err) => return Err(err), + }; + Ok(host) + } + + /// ### pwd + /// + /// Print working directory + pub fn pwd(&self) -> PathBuf { + self.wrkdir.clone() + } + + /// ### list_dir + /// + /// List files in current directory + pub fn list_dir(&self) -> Vec { + self.files.clone() + } + + /// ### change_wrkdir + /// + /// Change working directory with the new provided directory + pub fn change_wrkdir(&mut self, new_dir: PathBuf) -> Result { + // Check whether directory exists + if !self.file_exists(new_dir.as_path()) { + return Err(HostError::NoSuchFileOrDirectory); + } + let prev_dir: PathBuf = self.wrkdir.clone(); // Backup location + // Update working directory + self.wrkdir = new_dir; + // Scan new directory + self.files = match self.scan_dir() { + Ok(files) => files, + Err(err) => { + // Restore directory + self.wrkdir = prev_dir; + return Err(err); + } + }; + Ok(self.wrkdir.clone()) + } + + /// ### file_exists + /// + /// Returns whether provided file path exists + fn file_exists(&self, path: &Path) -> bool { + path.exists() + } + + /// ### scan_dir + /// + /// Get content of the current directory as a list of fs entry (Windows) + #[cfg(any(unix, macos, linux))] + fn scan_dir(&self) -> Result, HostError> { + let entries = match std::fs::read_dir(self.wrkdir.as_path()) { + Ok(e) => e, + Err(_) => return Err(HostError::DirNotAccessible), + }; + let mut fs_entries: Vec = Vec::new(); + for entry in entries { + if let Ok(entry) = entry { + let path: PathBuf = entry.path(); + let attr: Metadata = fs::metadata(path.clone()).unwrap(); + let is_symlink: bool = attr.file_type().is_symlink(); + // Get user stuff + let user: Option = match get_user_by_uid(attr.uid()) { + Some(user) => String::from(user.name().to_str().unwrap_or("")), + None => None, + }; + let group: Option = match get_group_by_gid(attr.gid()) { + Some(gruop) => String::from(group.name().to_str().unwrap_or("")), + None => None, + }; + // Match dir / file + fs_entries.push(match path.is_dir() { + true => { + // Is dir + FsEntry::Directory(FsDirectory { + name: path.file_name(), + last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), + last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), + creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), + readonly: attr.permissions().readonly(), + symlink: match is_symlink { + true => { + // Read link + match fs::read_link(path) { + Ok(p) => Some(p), + Err(_) => None, + } + } + false => None, + }, + user: user, + group: group, + unix_pex: Some(self.u32_to_mode(attr.mode())), + }) + } + false => { + // Is File + let extension: Option = match path.extension() { + Some(s) => Some(String::from(s.to_str().unwrap_or(""))), + None => None, + }; + FsEntry::File(FsFile { + name: path.file_name(), + last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), + last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), + creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), + readonly: attr.permissions().readonly(), + size: attr.len() as usize, + ftype: extension, + symlink: match is_symlink { + true => { + // Read link + match fs::read_link(path) { + Ok(p) => Some(p), + Err(_) => None, + } + } + false => None, + }, + user: user, + group: group, + unix_pex: Some(self.u32_to_mode(attr.mode())), + }) + } + }); + } + } + Ok(fs_entries) + } + + /// ### scan_dir + /// + /// Get content of the current directory as a list of fs entry (Windows) + #[cfg(target_os = "windows")] + #[cfg(not(tarpaulin_include))] + fn scan_dir(&self) -> Result, HostError> { + let entries = match std::fs::read_dir(self.wrkdir.as_path()) { + Ok(e) => e, + Err(_) => return Err(HostError::DirNotAccessible), + }; + let mut fs_entries: Vec = Vec::new(); + for entry in entries { + if let Ok(entry) = entry { + let path: PathBuf = entry.path(); + let attr: Metadata = fs::metadata(path.clone()).unwrap(); + fs_entries.push(match path.is_dir() { + true => { + // Is dir + FsEntry::Directory(FsDirectory { + name: path, + last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), + last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), + creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), + readonly: attr.permissions().readonly(), + symlink: None, + user: None, + group: None, + unix_pex: None, + }) + } + false => { + // Is File + let extension: Option = match path.extension() { + Some(s) => Some(String::from(s.to_str().unwrap_or(""))), + None => None, + }; + FsEntry::File(FsFile { + name: path, + last_change_time: attr.modified().unwrap_or(SystemTime::UNIX_EPOCH), + last_access_time: attr.accessed().unwrap_or(SystemTime::UNIX_EPOCH), + creation_time: attr.created().unwrap_or(SystemTime::UNIX_EPOCH), + readonly: attr.permissions().readonly(), + size: attr.len() as usize, + ftype: extension, + symlink: None, + user: None, + group: None, + unix_pex: None, + }) + } + }); + } + } + Ok(fs_entries) + } + + /// ### u32_to_mode + /// + /// Return string with format xxxxxx to tuple of permissions (user, group, others) + #[cfg(any(unix, macos, linux))] + fn u32_to_mode(&self, mode: u32) -> (u8, u8, u8) { + let user: u8 = ((mode >> 6) & 0x7) as u8; + let group: u8 = ((mode >> 3) & 0x7) as u8; + let others: u8 = (mode & 0x7) as u8; + (user, group, others) + } +} diff --git a/src/lib.rs b/src/lib.rs index 8de1a24..71e4d83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,3 +22,5 @@ pub mod filetransfer; pub mod fs; pub mod host; + +// TODO: struct holder of filetransfer, host and gfx client \ No newline at end of file