mirror of
https://github.com/veeso/termscp.git
synced 2025-12-07 09:36:00 -08:00
1007 lines
39 KiB
Rust
1007 lines
39 KiB
Rust
//! ## Host
|
|
//!
|
|
//! `host` is the module which provides functionalities to host file system
|
|
|
|
/*
|
|
*
|
|
* Copyright (C) 2020-2021 Christian Visintin - christian.visintin1997@gmail.com
|
|
*
|
|
* This file is part of "TermSCP"
|
|
*
|
|
* TermSCP is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* TermSCP is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with TermSCP. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
use std::fs::{self, File, Metadata, OpenOptions};
|
|
use std::path::{Path, PathBuf};
|
|
use std::time::SystemTime;
|
|
// Metadata ext
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
use std::fs::set_permissions;
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
|
|
|
// Locals
|
|
use crate::fs::{FsDirectory, FsEntry, FsFile};
|
|
|
|
/// ## HostErrorType
|
|
///
|
|
/// HostErrorType provides an overview of the specific host error
|
|
#[derive(PartialEq, std::fmt::Debug)]
|
|
pub enum HostErrorType {
|
|
NoSuchFileOrDirectory,
|
|
ReadonlyFile,
|
|
DirNotAccessible,
|
|
FileNotAccessible,
|
|
FileAlreadyExists,
|
|
CouldNotCreateFile,
|
|
DeleteFailed,
|
|
}
|
|
|
|
/// ### HostError
|
|
///
|
|
/// HostError is a wrapper for the error type and the exact io error
|
|
|
|
pub struct HostError {
|
|
pub error: HostErrorType,
|
|
pub ioerr: Option<std::io::Error>,
|
|
}
|
|
|
|
impl HostError {
|
|
/// ### new
|
|
///
|
|
/// Instantiates a new HostError
|
|
pub(crate) fn new(error: HostErrorType, errno: Option<std::io::Error>) -> HostError {
|
|
HostError {
|
|
error,
|
|
ioerr: errno,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for HostError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
let code_str: &str = match self.error {
|
|
HostErrorType::NoSuchFileOrDirectory => "No such file or directory",
|
|
HostErrorType::ReadonlyFile => "File is readonly",
|
|
HostErrorType::DirNotAccessible => "Could not access directory",
|
|
HostErrorType::FileNotAccessible => "Could not access file",
|
|
HostErrorType::FileAlreadyExists => "File already exists",
|
|
HostErrorType::CouldNotCreateFile => "Could not create file",
|
|
HostErrorType::DeleteFailed => "Could not delete file",
|
|
};
|
|
match &self.ioerr {
|
|
Some(err) => write!(f, "{}: {}", code_str, err),
|
|
None => write!(f, "{}", code_str),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// ## 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,
|
|
files: Vec::new(),
|
|
};
|
|
// Check if dir exists
|
|
if !host.file_exists(host.wrkdir.as_path()) {
|
|
return Err(HostError::new(HostErrorType::NoSuchFileOrDirectory, None));
|
|
}
|
|
// Retrieve files for provided path
|
|
host.files = match host.scan_dir(host.wrkdir.as_path()) {
|
|
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
|
|
#[allow(dead_code)]
|
|
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: &Path) -> Result<PathBuf, HostError> {
|
|
let new_dir: PathBuf = self.to_abs_path(new_dir);
|
|
// Check whether directory exists
|
|
if !self.file_exists(new_dir.as_path()) {
|
|
return Err(HostError::new(HostErrorType::NoSuchFileOrDirectory, None));
|
|
}
|
|
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(self.wrkdir.as_path()) {
|
|
Ok(files) => files,
|
|
Err(err) => {
|
|
// Restore directory
|
|
self.wrkdir = prev_dir;
|
|
return Err(err);
|
|
}
|
|
};
|
|
Ok(self.wrkdir.clone())
|
|
}
|
|
|
|
/// ### mkdir
|
|
///
|
|
/// Make a directory at path and update the file list (only if relative)
|
|
pub fn mkdir(&mut self, dir_name: &Path) -> Result<(), HostError> {
|
|
self.mkdir_ex(dir_name, false)
|
|
}
|
|
|
|
/// ### mkdir_ex
|
|
///
|
|
/// Extended option version of makedir.
|
|
/// ignex: don't report error if directory already exists
|
|
pub fn mkdir_ex(&mut self, dir_name: &Path, ignex: bool) -> Result<(), HostError> {
|
|
let dir_path: PathBuf = self.to_abs_path(dir_name);
|
|
// If dir already exists, return Error
|
|
if dir_path.exists() {
|
|
match ignex {
|
|
true => return Ok(()),
|
|
false => return Err(HostError::new(HostErrorType::FileAlreadyExists, None)),
|
|
}
|
|
}
|
|
match std::fs::create_dir(dir_path) {
|
|
Ok(_) => {
|
|
// Update dir
|
|
if dir_name.is_relative() {
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
}
|
|
Ok(())
|
|
}
|
|
Err(err) => Err(HostError::new(HostErrorType::CouldNotCreateFile, Some(err))),
|
|
}
|
|
}
|
|
|
|
/// ### remove
|
|
///
|
|
/// Remove file entry
|
|
pub fn remove(&mut self, entry: &FsEntry) -> Result<(), HostError> {
|
|
match entry {
|
|
FsEntry::Directory(dir) => {
|
|
// If file doesn't exist; return error
|
|
if !dir.abs_path.as_path().exists() {
|
|
return Err(HostError::new(HostErrorType::NoSuchFileOrDirectory, None));
|
|
}
|
|
// Remove
|
|
match std::fs::remove_dir_all(dir.abs_path.as_path()) {
|
|
Ok(_) => {
|
|
// Update dir
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
Ok(())
|
|
}
|
|
Err(err) => Err(HostError::new(HostErrorType::DeleteFailed, Some(err))),
|
|
}
|
|
}
|
|
FsEntry::File(file) => {
|
|
// If file doesn't exist; return error
|
|
if !file.abs_path.as_path().exists() {
|
|
return Err(HostError::new(HostErrorType::NoSuchFileOrDirectory, None));
|
|
}
|
|
// Remove
|
|
match std::fs::remove_file(file.abs_path.as_path()) {
|
|
Ok(_) => {
|
|
// Update dir
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
Ok(())
|
|
}
|
|
Err(err) => Err(HostError::new(HostErrorType::DeleteFailed, Some(err))),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// ### rename
|
|
///
|
|
/// Rename file or directory to new name
|
|
pub fn rename(&mut self, entry: &FsEntry, dst_path: &Path) -> Result<(), HostError> {
|
|
let abs_path: PathBuf = entry.get_abs_path();
|
|
match std::fs::rename(abs_path.as_path(), dst_path) {
|
|
Ok(_) => {
|
|
// Scan dir
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
Ok(())
|
|
}
|
|
Err(err) => Err(HostError::new(HostErrorType::CouldNotCreateFile, Some(err))),
|
|
}
|
|
}
|
|
|
|
/// ### copy
|
|
///
|
|
/// Copy file to destination path
|
|
pub fn copy(&mut self, entry: &FsEntry, dst: &Path) -> Result<(), HostError> {
|
|
// Get absolute path of dest
|
|
let dst: PathBuf = self.to_abs_path(dst);
|
|
// Match entry
|
|
match entry {
|
|
FsEntry::File(file) => {
|
|
// Copy file
|
|
// If destination path is a directory, push file name
|
|
let dst: PathBuf = match dst.as_path().is_dir() {
|
|
true => {
|
|
let mut p: PathBuf = dst.clone();
|
|
p.push(file.name.as_str());
|
|
p
|
|
}
|
|
false => dst.clone(),
|
|
};
|
|
// Copy entry path to dst path
|
|
if let Err(err) = std::fs::copy(file.abs_path.as_path(), dst.as_path()) {
|
|
return Err(HostError::new(HostErrorType::CouldNotCreateFile, Some(err)));
|
|
}
|
|
}
|
|
FsEntry::Directory(dir) => {
|
|
// If destination path doesn't exist, create destination
|
|
if !dst.exists() {
|
|
self.mkdir(dst.as_path())?;
|
|
}
|
|
// Scan dir
|
|
let dir_files: Vec<FsEntry> = self.scan_dir(dir.abs_path.as_path())?;
|
|
// Iterate files
|
|
for dir_entry in dir_files.iter() {
|
|
// Calculate dst
|
|
let mut sub_dst: PathBuf = dst.clone();
|
|
sub_dst.push(dir_entry.get_name());
|
|
// Call function recursively
|
|
self.copy(dir_entry, sub_dst.as_path())?;
|
|
}
|
|
}
|
|
}
|
|
// Reload directory if dst is pwd
|
|
match dst.is_dir() {
|
|
true => {
|
|
if dst == self.pwd().as_path() {
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
} else if let Some(parent) = dst.parent() {
|
|
// If parent is pwd, scan directory
|
|
if parent == self.pwd().as_path() {
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
}
|
|
}
|
|
}
|
|
false => {
|
|
if let Some(parent) = dst.parent() {
|
|
// If parent is pwd, scan directory
|
|
if parent == self.pwd().as_path() {
|
|
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// ### stat
|
|
///
|
|
/// Stat file and create a FsEntry
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> {
|
|
let path: PathBuf = self.to_abs_path(path);
|
|
let attr: Metadata = match fs::metadata(path.as_path()) {
|
|
Ok(metadata) => metadata,
|
|
Err(err) => return Err(HostError::new(HostErrorType::FileNotAccessible, Some(err))),
|
|
};
|
|
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
|
|
// Match dir / file
|
|
Ok(match path.is_dir() {
|
|
true => FsEntry::Directory(FsDirectory {
|
|
name: file_name,
|
|
abs_path: path.clone(),
|
|
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 fs::read_link(path.as_path()) {
|
|
Ok(p) => match self.stat(p.as_path()) {
|
|
Ok(entry) => Some(Box::new(entry)),
|
|
Err(_) => None,
|
|
},
|
|
Err(_) => None,
|
|
},
|
|
user: Some(attr.uid()),
|
|
group: Some(attr.gid()),
|
|
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: file_name,
|
|
abs_path: path.clone(),
|
|
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 fs::read_link(path.as_path()) {
|
|
Ok(p) => match self.stat(p.as_path()) {
|
|
Ok(entry) => Some(Box::new(entry)),
|
|
Err(_) => None,
|
|
},
|
|
Err(_) => None, // Ignore errors
|
|
},
|
|
user: Some(attr.uid()),
|
|
group: Some(attr.gid()),
|
|
unix_pex: Some(self.u32_to_mode(attr.mode())),
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
/// ### stat
|
|
///
|
|
/// Stat file and create a FsEntry
|
|
#[cfg(target_os = "windows")]
|
|
#[cfg(not(tarpaulin_include))]
|
|
pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> {
|
|
let path: PathBuf = self.to_abs_path(path);
|
|
let attr: Metadata = match fs::metadata(path.as_path()) {
|
|
Ok(metadata) => metadata,
|
|
Err(err) => return Err(HostError::new(HostErrorType::FileNotAccessible, Some(err))),
|
|
};
|
|
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
|
|
// Match dir / file
|
|
Ok(match path.is_dir() {
|
|
true => FsEntry::Directory(FsDirectory {
|
|
name: file_name,
|
|
abs_path: path.clone(),
|
|
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 fs::read_link(path.as_path()) {
|
|
Ok(p) => match self.stat(p.as_path()) {
|
|
Ok(entry) => Some(Box::new(entry)),
|
|
Err(_) => None, // Ignore errors
|
|
},
|
|
Err(_) => 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: file_name,
|
|
abs_path: path.clone(),
|
|
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 fs::read_link(path.as_path()) {
|
|
Ok(p) => match self.stat(p.as_path()) {
|
|
Ok(entry) => Some(Box::new(entry)),
|
|
Err(_) => None,
|
|
},
|
|
Err(_) => None,
|
|
},
|
|
user: None,
|
|
group: None,
|
|
unix_pex: None,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
/// ### chmod
|
|
///
|
|
/// Change file mode to file, according to UNIX permissions
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
pub fn chmod(&self, path: &Path, pex: (u8, u8, u8)) -> Result<(), HostError> {
|
|
let path: PathBuf = self.to_abs_path(path);
|
|
// Get metadta
|
|
match fs::metadata(path.as_path()) {
|
|
Ok(metadata) => {
|
|
let mut mpex = metadata.permissions();
|
|
mpex.set_mode(self.mode_to_u32(pex));
|
|
match set_permissions(path.as_path(), mpex) {
|
|
Ok(_) => Ok(()),
|
|
Err(err) => Err(HostError::new(HostErrorType::FileNotAccessible, Some(err))),
|
|
}
|
|
}
|
|
Err(err) => Err(HostError::new(HostErrorType::FileNotAccessible, Some(err))),
|
|
}
|
|
}
|
|
|
|
/// ### open_file_read
|
|
///
|
|
/// Open file for read
|
|
pub fn open_file_read(&self, file: &Path) -> Result<File, HostError> {
|
|
let file: PathBuf = self.to_abs_path(file);
|
|
if !self.file_exists(file.as_path()) {
|
|
return Err(HostError::new(HostErrorType::NoSuchFileOrDirectory, None));
|
|
}
|
|
match OpenOptions::new()
|
|
.create(false)
|
|
.read(true)
|
|
.write(false)
|
|
.open(file.as_path())
|
|
{
|
|
Ok(f) => Ok(f),
|
|
Err(err) => Err(HostError::new(HostErrorType::FileNotAccessible, Some(err))),
|
|
}
|
|
}
|
|
|
|
/// ### open_file_write
|
|
///
|
|
/// Open file for write
|
|
pub fn open_file_write(&self, file: &Path) -> Result<File, HostError> {
|
|
let file: PathBuf = self.to_abs_path(file);
|
|
match OpenOptions::new()
|
|
.create(true)
|
|
.write(true)
|
|
.truncate(true)
|
|
.open(file.as_path())
|
|
{
|
|
Ok(f) => Ok(f),
|
|
Err(err) => match self.file_exists(file.as_path()) {
|
|
true => Err(HostError::new(HostErrorType::ReadonlyFile, Some(err))),
|
|
false => Err(HostError::new(HostErrorType::FileNotAccessible, Some(err))),
|
|
},
|
|
}
|
|
}
|
|
|
|
/// ### file_exists
|
|
///
|
|
/// Returns whether provided file path exists
|
|
pub fn file_exists(&self, path: &Path) -> bool {
|
|
path.exists()
|
|
}
|
|
|
|
/// ### scan_dir
|
|
///
|
|
/// Get content of the current directory as a list of fs entry (Windows)
|
|
pub fn scan_dir(&self, dir: &Path) -> Result<Vec<FsEntry>, HostError> {
|
|
let entries = match std::fs::read_dir(dir) {
|
|
Ok(e) => e,
|
|
Err(err) => return Err(HostError::new(HostErrorType::DirNotAccessible, Some(err))),
|
|
};
|
|
let mut fs_entries: Vec<FsEntry> = Vec::new();
|
|
for entry in entries {
|
|
if let Ok(entry) = entry {
|
|
fs_entries.push(match self.stat(entry.path().as_path()) {
|
|
Ok(entry) => entry,
|
|
Err(err) => return Err(err),
|
|
});
|
|
}
|
|
}
|
|
Ok(fs_entries)
|
|
}
|
|
|
|
/// ### u32_to_mode
|
|
///
|
|
/// Return string with format xxxxxx to tuple of permissions (user, group, others)
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "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)
|
|
}
|
|
|
|
/// mode_to_u32
|
|
///
|
|
/// Convert owner,group,others to u32
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn mode_to_u32(&self, mode: (u8, u8, u8)) -> u32 {
|
|
((mode.0 as u32) << 6) + ((mode.1 as u32) << 3) + mode.2 as u32
|
|
}
|
|
|
|
/// ### to_abs_path
|
|
///
|
|
/// Convert path to absolute path
|
|
fn to_abs_path(&self, p: &Path) -> PathBuf {
|
|
// Convert to abs path
|
|
match p.is_relative() {
|
|
true => {
|
|
let mut path: PathBuf = self.wrkdir.clone();
|
|
path.push(p);
|
|
path
|
|
}
|
|
false => PathBuf::from(p),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
use std::fs::File;
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
use std::io::Write;
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
use std::os::unix::fs::{symlink, PermissionsExt};
|
|
|
|
#[test]
|
|
fn test_host_error_new() {
|
|
let error: HostError = HostError::new(HostErrorType::CouldNotCreateFile, None);
|
|
assert_eq!(error.error, HostErrorType::CouldNotCreateFile);
|
|
assert!(error.ioerr.is_none());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_new() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
assert_eq!(host.wrkdir, PathBuf::from("/bin"));
|
|
// Scan dir
|
|
let entries = std::fs::read_dir(PathBuf::from("/bin").as_path()).unwrap();
|
|
let mut counter: usize = 0;
|
|
for _ in entries {
|
|
counter = counter + 1;
|
|
}
|
|
assert_eq!(host.files.len(), counter);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(target_os = "windows")]
|
|
fn test_host_localhost_new() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap();
|
|
assert_eq!(host.wrkdir, PathBuf::from("C:\\users"));
|
|
// Scan dir
|
|
let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap();
|
|
let mut counter: usize = 0;
|
|
for _ in entries {
|
|
counter = counter + 1;
|
|
}
|
|
assert_eq!(host.files.len(), counter);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_host_localhost_new_bad() {
|
|
Localhost::new(PathBuf::from("/omargabber/123/345"))
|
|
.ok()
|
|
.unwrap();
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_pwd() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
assert_eq!(host.pwd(), PathBuf::from("/bin"));
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_list_files() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
// Scan dir
|
|
let entries = std::fs::read_dir(PathBuf::from("/bin").as_path()).unwrap();
|
|
let mut counter: usize = 0;
|
|
for _ in entries {
|
|
counter = counter + 1;
|
|
}
|
|
assert_eq!(host.list_dir().len(), counter);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_change_dir() {
|
|
let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
|
|
let new_dir: PathBuf = PathBuf::from("/dev");
|
|
assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
|
|
// Verify new files
|
|
// Scan dir
|
|
let entries = std::fs::read_dir(PathBuf::from(new_dir).as_path()).unwrap();
|
|
let mut counter: usize = 0;
|
|
for _ in entries {
|
|
counter = counter + 1;
|
|
}
|
|
assert_eq!(host.files.len(), counter);
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[should_panic]
|
|
fn test_host_localhost_change_dir_failed() {
|
|
let mut host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
let new_dir: PathBuf = PathBuf::from("/omar/gabber/123/456");
|
|
assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_open_read() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
// Create temp file
|
|
let file: tempfile::NamedTempFile = create_sample_file();
|
|
assert!(host.open_file_read(file.path()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[should_panic]
|
|
fn test_host_localhost_open_read_err_no_such_file() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
assert!(host
|
|
.open_file_read(PathBuf::from("/bin/foo-bar-test-omar-123-456-789.txt").as_path())
|
|
.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_open_read_err_not_accessible() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
let file: tempfile::NamedTempFile = create_sample_file();
|
|
//let mut perms = fs::metadata(file.path())?.permissions();
|
|
fs::set_permissions(file.path(), PermissionsExt::from_mode(0o222)).unwrap();
|
|
//fs::set_permissions(file.path(), perms)?;
|
|
assert!(host.open_file_read(file.path()).is_err());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_open_write() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
// Create temp file
|
|
let file: tempfile::NamedTempFile = create_sample_file();
|
|
assert!(host.open_file_write(file.path()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_open_write_err() {
|
|
let host: Localhost = Localhost::new(PathBuf::from("/bin")).ok().unwrap();
|
|
let file: tempfile::NamedTempFile = create_sample_file();
|
|
//let mut perms = fs::metadata(file.path())?.permissions();
|
|
fs::set_permissions(file.path(), PermissionsExt::from_mode(0o444)).unwrap();
|
|
//fs::set_permissions(file.path(), perms)?;
|
|
assert!(host.open_file_write(file.path()).is_err());
|
|
}
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[test]
|
|
fn test_host_localhost_symlinks() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create sample file
|
|
assert!(File::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok());
|
|
// Create symlink
|
|
assert!(symlink(
|
|
format!("{}/foo.txt", tmpdir.path().display()),
|
|
format!("{}/bar.txt", tmpdir.path().display())
|
|
)
|
|
.is_ok());
|
|
// Get dir
|
|
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
println!("Entries {:?}", files);
|
|
// Verify files
|
|
let file_0: &FsEntry = files.get(0).unwrap();
|
|
match file_0 {
|
|
FsEntry::File(file_0) => {
|
|
if file_0.name == String::from("foo.txt") {
|
|
assert!(file_0.symlink.is_none());
|
|
} else {
|
|
assert_eq!(
|
|
*file_0.symlink.as_ref().unwrap().get_abs_path(),
|
|
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
|
);
|
|
}
|
|
}
|
|
_ => panic!("expected entry 0 to be file: {:?}", file_0),
|
|
};
|
|
// Verify simlink
|
|
let file_1: &FsEntry = files.get(1).unwrap();
|
|
match file_1 {
|
|
FsEntry::File(file_1) => {
|
|
if file_1.name == String::from("bar.txt") {
|
|
assert_eq!(
|
|
*file_1.symlink.as_ref().unwrap().get_abs_path(),
|
|
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
|
);
|
|
} else {
|
|
assert!(file_1.symlink.is_none());
|
|
}
|
|
}
|
|
_ => panic!("expected entry 0 to be file: {:?}", file_1),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_mkdir() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 0); // There should be 0 files now
|
|
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 1); // There should be 1 file now
|
|
// Try to re-create directory
|
|
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err());
|
|
// Try abs path
|
|
assert!(host
|
|
.mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true)
|
|
.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_remove() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create sample file
|
|
assert!(File::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok());
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 1); // There should be 1 file now
|
|
// Remove file
|
|
assert!(host.remove(files.get(0).unwrap()).is_ok());
|
|
// There should be 0 files now
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 0); // There should be 0 files now
|
|
// Create directory
|
|
assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
|
|
// Delete directory
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 1); // There should be 1 file now
|
|
assert!(host.remove(files.get(0).unwrap()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn test_host_localhost_rename() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create sample file
|
|
let src_path: PathBuf =
|
|
PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str());
|
|
assert!(File::create(src_path.as_path()).is_ok());
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 1); // There should be 1 file now
|
|
assert_eq!(get_filename(files.get(0).unwrap()), String::from("foo.txt"));
|
|
// Rename file
|
|
let dst_path: PathBuf =
|
|
PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str());
|
|
assert!(host
|
|
.rename(files.get(0).unwrap(), dst_path.as_path())
|
|
.is_ok());
|
|
// There should be still 1 file now, but named bar.txt
|
|
let files: Vec<FsEntry> = host.list_dir();
|
|
assert_eq!(files.len(), 1); // There should be 0 files now
|
|
assert_eq!(get_filename(files.get(0).unwrap()), String::from("bar.txt"));
|
|
// Fail
|
|
let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu");
|
|
assert!(host
|
|
.rename(files.get(0).unwrap(), bad_path.as_path())
|
|
.is_err());
|
|
}
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[test]
|
|
fn test_host_chmod() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
let file: tempfile::NamedTempFile = create_sample_file();
|
|
let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
// mode_to_u32
|
|
assert_eq!(host.mode_to_u32((6, 4, 4)), 0o644);
|
|
assert_eq!(host.mode_to_u32((7, 7, 5)), 0o775);
|
|
// Chmod to file
|
|
assert!(host.chmod(file.path(), (7, 7, 5)).is_ok());
|
|
// Chmod to dir
|
|
assert!(host.chmod(tmpdir.path(), (7, 5, 0)).is_ok());
|
|
// Error
|
|
assert!(host
|
|
.chmod(Path::new("/tmp/krgiogoiegj/kwrgnoerig"), (7, 7, 7))
|
|
.is_err());
|
|
}
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[test]
|
|
fn test_host_copy_file_absolute() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create file in tmpdir
|
|
let mut file1_path: PathBuf = PathBuf::from(tmpdir.path());
|
|
file1_path.push("foo.txt");
|
|
// Write file 1
|
|
let mut file1: File = File::create(file1_path.as_path()).ok().unwrap();
|
|
assert!(file1.write_all(b"Hello world!\n").is_ok());
|
|
// Get file 2 path
|
|
let mut file2_path: PathBuf = PathBuf::from(tmpdir.path());
|
|
file2_path.push("bar.txt");
|
|
// Create host
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let file1_entry: FsEntry = host.files.get(0).unwrap().clone();
|
|
assert_eq!(file1_entry.get_name(), String::from("foo.txt"));
|
|
// Copy
|
|
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
|
|
// Verify host has two files
|
|
assert_eq!(host.files.len(), 2);
|
|
}
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[test]
|
|
fn test_host_copy_file_relative() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create file in tmpdir
|
|
let mut file1_path: PathBuf = PathBuf::from(tmpdir.path());
|
|
file1_path.push("foo.txt");
|
|
// Write file 1
|
|
let mut file1: File = File::create(file1_path.as_path()).ok().unwrap();
|
|
assert!(file1.write_all(b"Hello world!\n").is_ok());
|
|
// Get file 2 path
|
|
let file2_path: PathBuf = PathBuf::from("bar.txt");
|
|
// Create host
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let file1_entry: FsEntry = host.files.get(0).unwrap().clone();
|
|
assert_eq!(file1_entry.get_name(), String::from("foo.txt"));
|
|
// Copy
|
|
assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
|
|
// Verify host has two files
|
|
assert_eq!(host.files.len(), 2);
|
|
}
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[test]
|
|
fn test_host_copy_directory_absolute() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create directory in tmpdir
|
|
let mut dir_src: PathBuf = PathBuf::from(tmpdir.path());
|
|
dir_src.push("test_dir/");
|
|
assert!(std::fs::create_dir(dir_src.as_path()).is_ok());
|
|
// Create file in src dir
|
|
let mut file1_path: PathBuf = dir_src.clone();
|
|
file1_path.push("foo.txt");
|
|
// Write file 1
|
|
let mut file1: File = File::create(file1_path.as_path()).ok().unwrap();
|
|
assert!(file1.write_all(b"Hello world!\n").is_ok());
|
|
// Copy dir src to dir ddest
|
|
let mut dir_dest: PathBuf = PathBuf::from(tmpdir.path());
|
|
dir_dest.push("test_dest_dir/");
|
|
// Create host
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let dir_src_entry: FsEntry = host.files.get(0).unwrap().clone();
|
|
assert_eq!(dir_src_entry.get_name(), String::from("test_dir"));
|
|
// Copy
|
|
assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok());
|
|
// Verify host has two files
|
|
assert_eq!(host.files.len(), 2);
|
|
// Verify dir_dest contains foo.txt
|
|
let mut test_file_path: PathBuf = dir_dest.clone();
|
|
test_file_path.push("foo.txt");
|
|
assert!(host.stat(test_file_path.as_path()).is_ok());
|
|
}
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
#[test]
|
|
fn test_host_copy_directory_relative() {
|
|
let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
|
|
// Create directory in tmpdir
|
|
let mut dir_src: PathBuf = PathBuf::from(tmpdir.path());
|
|
dir_src.push("test_dir/");
|
|
assert!(std::fs::create_dir(dir_src.as_path()).is_ok());
|
|
// Create file in src dir
|
|
let mut file1_path: PathBuf = dir_src.clone();
|
|
file1_path.push("foo.txt");
|
|
// Write file 1
|
|
let mut file1: File = File::create(file1_path.as_path()).ok().unwrap();
|
|
assert!(file1.write_all(b"Hello world!\n").is_ok());
|
|
// Copy dir src to dir ddest
|
|
let dir_dest: PathBuf = PathBuf::from("test_dest_dir/");
|
|
// Create host
|
|
let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
|
|
let dir_src_entry: FsEntry = host.files.get(0).unwrap().clone();
|
|
assert_eq!(dir_src_entry.get_name(), String::from("test_dir"));
|
|
// Copy
|
|
assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok());
|
|
// Verify host has two files
|
|
assert_eq!(host.files.len(), 2);
|
|
// Verify dir_dest contains foo.txt
|
|
let mut test_file_path: PathBuf = dir_dest.clone();
|
|
test_file_path.push("foo.txt");
|
|
println!("{:?}", host.scan_dir(tmpdir.path()).ok().unwrap());
|
|
assert!(host.stat(test_file_path.as_path()).is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_host_fmt_error() {
|
|
let err: HostError = HostError::new(
|
|
HostErrorType::CouldNotCreateFile,
|
|
Some(std::io::Error::from(std::io::ErrorKind::AddrInUse)),
|
|
);
|
|
assert_eq!(
|
|
format!("{}", err),
|
|
String::from("Could not create file: address in use")
|
|
);
|
|
assert_eq!(
|
|
format!("{}", HostError::new(HostErrorType::DeleteFailed, None)),
|
|
String::from("Could not delete file")
|
|
);
|
|
assert_eq!(
|
|
format!("{}", HostError::new(HostErrorType::DirNotAccessible, None)),
|
|
String::from("Could not access directory")
|
|
);
|
|
assert_eq!(
|
|
format!(
|
|
"{}",
|
|
HostError::new(HostErrorType::NoSuchFileOrDirectory, None)
|
|
),
|
|
String::from("No such file or directory")
|
|
);
|
|
assert_eq!(
|
|
format!("{}", HostError::new(HostErrorType::ReadonlyFile, None)),
|
|
String::from("File is readonly")
|
|
);
|
|
assert_eq!(
|
|
format!("{}", HostError::new(HostErrorType::FileNotAccessible, None)),
|
|
String::from("Could not access file")
|
|
);
|
|
assert_eq!(
|
|
format!("{}", HostError::new(HostErrorType::FileAlreadyExists, None)),
|
|
String::from("File already exists")
|
|
);
|
|
}
|
|
|
|
/// ### create_sample_file
|
|
///
|
|
/// Create a sample file
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn create_sample_file() -> tempfile::NamedTempFile {
|
|
// Write
|
|
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
|
write!(
|
|
tmpfile,
|
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.\nMauris ultricies consequat eros,\nnec scelerisque magna imperdiet metus.\n"
|
|
)
|
|
.unwrap();
|
|
tmpfile
|
|
}
|
|
|
|
#[cfg(any(target_os = "unix", target_os = "macos", target_os = "linux"))]
|
|
fn get_filename(entry: &FsEntry) -> String {
|
|
match entry {
|
|
FsEntry::Directory(d) => d.name.clone(),
|
|
FsEntry::File(f) => f.name.clone(),
|
|
}
|
|
}
|
|
}
|