Working on Host

This commit is contained in:
ChristianVisintin
2020-11-08 17:56:06 +01:00
parent 34ad55eac5
commit aa963b5000
6 changed files with 281 additions and 9 deletions

11
Cargo.lock generated
View File

@@ -279,6 +279,7 @@ dependencies = [
"crossterm 0.18.2", "crossterm 0.18.2",
"getopts", "getopts",
"tui", "tui",
"users",
] ]
[[package]] [[package]]
@@ -306,6 +307,16 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 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]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -18,6 +18,7 @@ readme = "README.md"
crossterm = "0.18.2" crossterm = "0.18.2"
getopts = "0.2.21" getopts = "0.2.21"
tui = { version = "0.12.0", features = ["crossterm"], default-features = false } tui = { version = "0.12.0", features = ["crossterm"], default-features = false }
users = "0.11.0"
[[bin]] [[bin]]
name = "termscp" name = "termscp"

View File

@@ -45,7 +45,14 @@ pub enum FileTransferProtocol {
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum FileTransferError { pub enum FileTransferError {
ConnectionError,
BadAddress,
AuthenticationFailed,
NoSuchFileOrDirectory,
DirStatFailed,
FileReadonly,
DownloadError,
UnknownError,
} }
/// ## FileTransfer /// ## FileTransfer

View File

@@ -24,12 +24,13 @@
*/ */
use std::path::PathBuf; use std::path::PathBuf;
use std::time::Instant; use std::time::SystemTime;
/// ## FsEntry /// ## FsEntry
/// ///
/// FsEntry represents a generic entry in a directory /// FsEntry represents a generic entry in a directory
#[derive(Clone)]
pub enum FsEntry { pub enum FsEntry {
Directory(FsDirectory), Directory(FsDirectory),
File(FsFile) File(FsFile)
@@ -39,11 +40,12 @@ pub enum FsEntry {
/// ///
/// Directory provides an interface to file system directories /// Directory provides an interface to file system directories
#[derive(Clone)]
pub struct FsDirectory { pub struct FsDirectory {
pub name: PathBuf, pub name: PathBuf,
pub last_change_time: Instant, pub last_change_time: SystemTime,
pub last_access_time: Instant, pub last_access_time: SystemTime,
pub creation_time: Instant, pub creation_time: SystemTime,
pub readonly: bool, pub readonly: bool,
pub symlink: Option<PathBuf>, // UNIX only pub symlink: Option<PathBuf>, // UNIX only
pub user: Option<String>, // UNIX only pub user: Option<String>, // UNIX only
@@ -55,13 +57,14 @@ pub struct FsDirectory {
/// ///
/// FsFile provides an interface to file system files /// FsFile provides an interface to file system files
#[derive(Clone)]
pub struct FsFile { pub struct FsFile {
pub name: PathBuf, pub name: PathBuf,
pub last_change_time: Instant, pub last_change_time: SystemTime,
pub last_access_time: Instant, pub last_access_time: SystemTime,
pub creation_time: Instant, pub creation_time: SystemTime,
pub size: usize, pub size: usize,
pub ftype: String, // File type pub ftype: Option<String>, // File type
pub readonly: bool, pub readonly: bool,
pub symlink: Option<PathBuf>, // UNIX only pub symlink: Option<PathBuf>, // UNIX only
pub user: Option<String>, // UNIX only pub user: Option<String>, // UNIX only

View File

@@ -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<FsEntry>,
}
impl Localhost {
/// ### new
///
/// Instantiates a new Localhost struct
pub fn new(wrkdir: PathBuf) -> Result<Localhost, HostError> {
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<FsEntry> {
self.files.clone()
}
/// ### change_wrkdir
///
/// Change working directory with the new provided directory
pub fn change_wrkdir(&mut self, new_dir: PathBuf) -> Result<PathBuf, HostError> {
// 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<Vec<FsEntry>, 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<FsEntry> = 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<String> = match get_user_by_uid(attr.uid()) {
Some(user) => String::from(user.name().to_str().unwrap_or("")),
None => None,
};
let group: Option<String> = 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<String> = 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<Vec<FsEntry>, 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<FsEntry> = 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<String> = 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)
}
}

View File

@@ -22,3 +22,5 @@
pub mod filetransfer; pub mod filetransfer;
pub mod fs; pub mod fs;
pub mod host; pub mod host;
// TODO: struct holder of filetransfer, host and gfx client