Working on logging
This commit is contained in:
parent
1e3c859ae0
commit
c1b3511c06
|
@ -319,14 +319,14 @@ impl std::string::ToString for FileTransferProtocol {
|
|||
}
|
||||
|
||||
impl std::str::FromStr for FileTransferProtocol {
|
||||
type Err = ();
|
||||
type Err = String;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_ascii_uppercase().as_str() {
|
||||
"FTP" => Ok(FileTransferProtocol::Ftp(false)),
|
||||
"FTPS" => Ok(FileTransferProtocol::Ftp(true)),
|
||||
"SCP" => Ok(FileTransferProtocol::Scp),
|
||||
"SFTP" => Ok(FileTransferProtocol::Sftp),
|
||||
_ => Err(()),
|
||||
_ => Err(s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
212
src/host/mod.rs
212
src/host/mod.rs
|
@ -125,12 +125,17 @@ impl Localhost {
|
|||
///
|
||||
/// Instantiates a new Localhost struct
|
||||
pub fn new(wrkdir: PathBuf) -> Result<Localhost, HostError> {
|
||||
debug!("Initializing localhost at {}", wrkdir.display());
|
||||
let mut host: Localhost = Localhost {
|
||||
wrkdir,
|
||||
files: Vec::new(),
|
||||
};
|
||||
// Check if dir exists
|
||||
if !host.file_exists(host.wrkdir.as_path()) {
|
||||
error!(
|
||||
"Failed to initialize localhost: {} doesn't exist",
|
||||
host.wrkdir.display()
|
||||
);
|
||||
return Err(HostError::new(
|
||||
HostErrorType::NoSuchFileOrDirectory,
|
||||
None,
|
||||
|
@ -140,8 +145,15 @@ impl Localhost {
|
|||
// Retrieve files for provided path
|
||||
host.files = match host.scan_dir(host.wrkdir.as_path()) {
|
||||
Ok(files) => files,
|
||||
Err(err) => return Err(err),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to initialize localhost: could not scan wrkdir: {}",
|
||||
err
|
||||
);
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
info!("Localhost initialized with success");
|
||||
Ok(host)
|
||||
}
|
||||
|
||||
|
@ -165,8 +177,10 @@ impl Localhost {
|
|||
/// 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);
|
||||
info!("Changing localhost directory to {}...", new_dir.display());
|
||||
// Check whether directory exists
|
||||
if !self.file_exists(new_dir.as_path()) {
|
||||
error!("Could not change directory: No such file or directory");
|
||||
return Err(HostError::new(
|
||||
HostErrorType::NoSuchFileOrDirectory,
|
||||
None,
|
||||
|
@ -174,10 +188,11 @@ impl Localhost {
|
|||
));
|
||||
}
|
||||
// Change directory
|
||||
if std::env::set_current_dir(new_dir.as_path()).is_err() {
|
||||
if let Err(err) = std::env::set_current_dir(new_dir.as_path()) {
|
||||
error!("Could not enter directory: {}", err);
|
||||
return Err(HostError::new(
|
||||
HostErrorType::NoSuchFileOrDirectory,
|
||||
None,
|
||||
Some(err),
|
||||
new_dir.as_path(),
|
||||
));
|
||||
}
|
||||
|
@ -189,11 +204,13 @@ impl Localhost {
|
|||
self.files = match self.scan_dir(self.wrkdir.as_path()) {
|
||||
Ok(files) => files,
|
||||
Err(err) => {
|
||||
error!("Could not scan new directory: {}", err);
|
||||
// Restore directory
|
||||
self.wrkdir = prev_dir;
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
debug!("Changed directory to {}", self.wrkdir.display());
|
||||
Ok(self.wrkdir.clone())
|
||||
}
|
||||
|
||||
|
@ -210,6 +227,7 @@ impl Localhost {
|
|||
/// 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);
|
||||
info!("Making directory {}", dir_path.display());
|
||||
// If dir already exists, return Error
|
||||
if dir_path.exists() {
|
||||
match ignex {
|
||||
|
@ -229,13 +247,17 @@ impl Localhost {
|
|||
if dir_name.is_relative() {
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
}
|
||||
info!("Created directory {}", dir_path.display());
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::CouldNotCreateFile,
|
||||
Some(err),
|
||||
dir_path.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Could not make directory: {}", err);
|
||||
Err(HostError::new(
|
||||
HostErrorType::CouldNotCreateFile,
|
||||
Some(err),
|
||||
dir_path.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,7 +268,9 @@ impl Localhost {
|
|||
match entry {
|
||||
FsEntry::Directory(dir) => {
|
||||
// If file doesn't exist; return error
|
||||
debug!("Removing directory {}", dir.abs_path.display());
|
||||
if !dir.abs_path.as_path().exists() {
|
||||
error!("Directory doesn't exist");
|
||||
return Err(HostError::new(
|
||||
HostErrorType::NoSuchFileOrDirectory,
|
||||
None,
|
||||
|
@ -258,18 +282,24 @@ impl Localhost {
|
|||
Ok(_) => {
|
||||
// Update dir
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
info!("Removed directory {}", dir.abs_path.display());
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::DeleteFailed,
|
||||
Some(err),
|
||||
dir.abs_path.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Could not remove directory: {}", err);
|
||||
Err(HostError::new(
|
||||
HostErrorType::DeleteFailed,
|
||||
Some(err),
|
||||
dir.abs_path.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
FsEntry::File(file) => {
|
||||
// If file doesn't exist; return error
|
||||
debug!("Removing file {}", file.abs_path.display());
|
||||
if !file.abs_path.as_path().exists() {
|
||||
error!("File doesn't exist");
|
||||
return Err(HostError::new(
|
||||
HostErrorType::NoSuchFileOrDirectory,
|
||||
None,
|
||||
|
@ -281,13 +311,17 @@ impl Localhost {
|
|||
Ok(_) => {
|
||||
// Update dir
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
info!("Removed file {}", file.abs_path.display());
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::DeleteFailed,
|
||||
Some(err),
|
||||
file.abs_path.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Could not remove file: {}", err);
|
||||
Err(HostError::new(
|
||||
HostErrorType::DeleteFailed,
|
||||
Some(err),
|
||||
file.abs_path.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -302,13 +336,26 @@ impl Localhost {
|
|||
Ok(_) => {
|
||||
// Scan dir
|
||||
self.files = self.scan_dir(self.wrkdir.as_path())?;
|
||||
debug!(
|
||||
"Moved file {} to {}",
|
||||
entry.get_abs_path().display(),
|
||||
dst_path.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::CouldNotCreateFile,
|
||||
Some(err),
|
||||
abs_path.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Failed to move {} to {}: {}",
|
||||
entry.get_abs_path().display(),
|
||||
dst_path.display(),
|
||||
err
|
||||
);
|
||||
Err(HostError::new(
|
||||
HostErrorType::CouldNotCreateFile,
|
||||
Some(err),
|
||||
abs_path.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,6 +365,11 @@ impl Localhost {
|
|||
pub fn copy(&mut self, entry: &FsEntry, dst: &Path) -> Result<(), HostError> {
|
||||
// Get absolute path of dest
|
||||
let dst: PathBuf = self.to_abs_path(dst);
|
||||
info!(
|
||||
"Copying file {} to {}",
|
||||
entry.get_abs_path().display(),
|
||||
dst.display()
|
||||
);
|
||||
// Match entry
|
||||
match entry {
|
||||
FsEntry::File(file) => {
|
||||
|
@ -333,16 +385,19 @@ impl Localhost {
|
|||
};
|
||||
// Copy entry path to dst path
|
||||
if let Err(err) = std::fs::copy(file.abs_path.as_path(), dst.as_path()) {
|
||||
error!("Failed to copy file: {}", err);
|
||||
return Err(HostError::new(
|
||||
HostErrorType::CouldNotCreateFile,
|
||||
Some(err),
|
||||
file.abs_path.as_path(),
|
||||
));
|
||||
}
|
||||
info!("File copied");
|
||||
}
|
||||
FsEntry::Directory(dir) => {
|
||||
// If destination path doesn't exist, create destination
|
||||
if !dst.exists() {
|
||||
debug!("Directory {} doesn't exist; creating it", dst.display());
|
||||
self.mkdir(dst.as_path())?;
|
||||
}
|
||||
// Scan dir
|
||||
|
@ -386,15 +441,17 @@ impl Localhost {
|
|||
/// 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> {
|
||||
info!("Stating file {}", path.display());
|
||||
let path: PathBuf = self.to_abs_path(path);
|
||||
let attr: Metadata = match fs::metadata(path.as_path()) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => {
|
||||
error!("Could not read file metadata: {}", err);
|
||||
return Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
path.as_path(),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
|
||||
|
@ -454,14 +511,16 @@ impl Localhost {
|
|||
#[cfg(not(tarpaulin_include))]
|
||||
pub fn stat(&self, path: &Path) -> Result<FsEntry, HostError> {
|
||||
let path: PathBuf = self.to_abs_path(path);
|
||||
info!("Stating file {}", path.display());
|
||||
let attr: Metadata = match fs::metadata(path.as_path()) {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => {
|
||||
error!("Could not read file metadata: {}", err);
|
||||
return Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
path.as_path(),
|
||||
))
|
||||
));
|
||||
}
|
||||
};
|
||||
let file_name: String = String::from(path.file_name().unwrap().to_str().unwrap_or(""));
|
||||
|
@ -523,16 +582,23 @@ impl Localhost {
|
|||
let args: Vec<&str> = cmd.split(' ').collect();
|
||||
let cmd: &str = args.first().unwrap();
|
||||
let argv: &[&str] = &args[1..];
|
||||
info!("Executing command: {} {:?}", cmd, argv);
|
||||
match std::process::Command::new(cmd).args(argv).output() {
|
||||
Ok(output) => match std::str::from_utf8(&output.stdout) {
|
||||
Ok(s) => Ok(s.to_string()),
|
||||
Ok(s) => {
|
||||
info!("Command output: {}", s);
|
||||
Ok(s.to_string())
|
||||
}
|
||||
Err(_) => Ok(String::new()),
|
||||
},
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::ExecutionFailed,
|
||||
Some(err),
|
||||
self.wrkdir.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Failed to run command: {}", err);
|
||||
Err(HostError::new(
|
||||
HostErrorType::ExecutionFailed,
|
||||
Some(err),
|
||||
self.wrkdir.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -548,19 +614,32 @@ impl Localhost {
|
|||
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),
|
||||
path.as_path(),
|
||||
)),
|
||||
Ok(_) => {
|
||||
info!("Changed mode for {} to {:?}", path.display(), pex);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Could not change mode for file {}: {}", path.display(), err);
|
||||
Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
path.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
path.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Chmod failed; could not read metadata for file {}: {}",
|
||||
path.display(),
|
||||
err
|
||||
);
|
||||
Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
path.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,7 +648,9 @@ impl Localhost {
|
|||
/// Open file for read
|
||||
pub fn open_file_read(&self, file: &Path) -> Result<File, HostError> {
|
||||
let file: PathBuf = self.to_abs_path(file);
|
||||
info!("Opening file {} for read", file.display());
|
||||
if !self.file_exists(file.as_path()) {
|
||||
error!("File doesn't exist!");
|
||||
return Err(HostError::new(
|
||||
HostErrorType::NoSuchFileOrDirectory,
|
||||
None,
|
||||
|
@ -583,11 +664,14 @@ impl Localhost {
|
|||
.open(file.as_path())
|
||||
{
|
||||
Ok(f) => Ok(f),
|
||||
Err(err) => Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
file.as_path(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Could not open file for read: {}", err);
|
||||
Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
file.as_path(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -596,6 +680,7 @@ impl Localhost {
|
|||
/// Open file for write
|
||||
pub fn open_file_write(&self, file: &Path) -> Result<File, HostError> {
|
||||
let file: PathBuf = self.to_abs_path(file);
|
||||
info!("Opening file {} for write", file.display());
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
|
@ -603,18 +688,21 @@ impl Localhost {
|
|||
.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),
|
||||
file.as_path(),
|
||||
)),
|
||||
false => Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
file.as_path(),
|
||||
)),
|
||||
},
|
||||
Err(err) => {
|
||||
error!("Failed to open file: {}", err);
|
||||
match self.file_exists(file.as_path()) {
|
||||
true => Err(HostError::new(
|
||||
HostErrorType::ReadonlyFile,
|
||||
Some(err),
|
||||
file.as_path(),
|
||||
)),
|
||||
false => Err(HostError::new(
|
||||
HostErrorType::FileNotAccessible,
|
||||
Some(err),
|
||||
file.as_path(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -629,13 +717,15 @@ impl Localhost {
|
|||
///
|
||||
/// Get content of the current directory as a list of fs entry
|
||||
pub fn scan_dir(&self, dir: &Path) -> Result<Vec<FsEntry>, HostError> {
|
||||
info!("Reading directory {}", dir.display());
|
||||
match std::fs::read_dir(dir) {
|
||||
Ok(e) => {
|
||||
let mut fs_entries: Vec<FsEntry> = Vec::new();
|
||||
for entry in e.flatten() {
|
||||
// NOTE: 0.4.1, don't fail if stat for one file fails
|
||||
if let Ok(entry) = self.stat(entry.path().as_path()) {
|
||||
fs_entries.push(entry);
|
||||
match self.stat(entry.path().as_path()) {
|
||||
Ok(entry) => fs_entries.push(entry),
|
||||
Err(e) => error!("Failed to stat {}: {}", entry.path().display(), e),
|
||||
}
|
||||
}
|
||||
Ok(fs_entries)
|
||||
|
|
|
@ -68,9 +68,11 @@ impl BookmarksClient {
|
|||
) -> Result<BookmarksClient, SerializerError> {
|
||||
// Create default hosts
|
||||
let default_hosts: UserHosts = Default::default();
|
||||
debug!("Setting up bookmarks client...");
|
||||
// Make a key storage (windows / macos)
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
let (key_storage, service_id): (Box<dyn KeyStorage>, &str) = {
|
||||
debug!("Setting up KeyStorage");
|
||||
let username: String = whoami::username();
|
||||
let storage: KeyringStorage = KeyringStorage::new(username.as_str());
|
||||
// Check if keyring storage is supported
|
||||
|
@ -79,8 +81,14 @@ impl BookmarksClient {
|
|||
#[cfg(test)] // NOTE: when running test, add -test
|
||||
let app_name: &str = "termscp-test";
|
||||
match storage.is_supported() {
|
||||
true => (Box::new(storage), app_name),
|
||||
false => (Box::new(FileStorage::new(storage_path)), "bookmarks"),
|
||||
true => {
|
||||
debug!("Using KeyringStorage");
|
||||
(Box::new(storage), app_name)
|
||||
}
|
||||
false => {
|
||||
warn!("KeyringStorage is not supported; using FileStorage");
|
||||
(Box::new(FileStorage::new(storage_path)), "bookmarks")
|
||||
}
|
||||
}
|
||||
};
|
||||
// Make a key storage (linux / unix)
|
||||
|
@ -90,16 +98,22 @@ impl BookmarksClient {
|
|||
let app_name: &str = "bookmarks";
|
||||
#[cfg(test)] // NOTE: when running test, add -test
|
||||
let app_name: &str = "bookmarks-test";
|
||||
debug!("Using FileStorage");
|
||||
(Box::new(FileStorage::new(storage_path)), app_name)
|
||||
};
|
||||
// Load key
|
||||
let key: String = match key_storage.get_key(service_id) {
|
||||
Ok(k) => k,
|
||||
Ok(k) => {
|
||||
debug!("Key loaded with success");
|
||||
k
|
||||
}
|
||||
Err(e) => match e {
|
||||
KeyStorageError::NoSuchKey => {
|
||||
// If no such key, generate key and set it into the storage
|
||||
let key: String = Self::generate_key();
|
||||
debug!("Key doesn't exist yet or could not be loaded; generated a new key");
|
||||
if let Err(e) = key_storage.set_key(service_id, key.as_str()) {
|
||||
error!("Failed to set new key into storage: {}", e);
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
format!("Could not write key to storage: {}", e),
|
||||
|
@ -109,10 +123,11 @@ impl BookmarksClient {
|
|||
key
|
||||
}
|
||||
_ => {
|
||||
error!("Failed to get key from storage: {}", e);
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
format!("Could not get key from storage: {}", e),
|
||||
))
|
||||
));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -124,15 +139,19 @@ impl BookmarksClient {
|
|||
};
|
||||
// If bookmark file doesn't exist, initialize it
|
||||
if !bookmarks_file.exists() {
|
||||
info!("Bookmarks file doesn't exist yet; creating it...");
|
||||
if let Err(err) = client.write_bookmarks() {
|
||||
error!("Failed to create bookmarks file: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
} else {
|
||||
// Load bookmarks from file
|
||||
if let Err(err) = client.read_bookmarks() {
|
||||
error!("Failed to load bookmarks: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
info!("Bookmarks client initialized");
|
||||
// Load key
|
||||
Ok(client)
|
||||
}
|
||||
|
@ -152,19 +171,29 @@ impl BookmarksClient {
|
|||
key: &str,
|
||||
) -> Option<(String, u16, FileTransferProtocol, String, Option<String>)> {
|
||||
let entry: &Bookmark = self.hosts.bookmarks.get(key)?;
|
||||
debug!("Getting bookmark {}", key);
|
||||
Some((
|
||||
entry.address.clone(),
|
||||
entry.port,
|
||||
match FileTransferProtocol::from_str(entry.protocol.as_str()) {
|
||||
Ok(proto) => proto,
|
||||
Err(_) => FileTransferProtocol::Sftp, // Default
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Found invalid protocol in bookmarks: {}; defaulting to SFTP",
|
||||
err
|
||||
);
|
||||
FileTransferProtocol::Sftp // Default
|
||||
}
|
||||
},
|
||||
entry.username.clone(),
|
||||
match &entry.password {
|
||||
// Decrypted password if Some; if decryption fails return None
|
||||
Some(pwd) => match self.decrypt_str(pwd.as_str()) {
|
||||
Ok(decrypted_pwd) => Some(decrypted_pwd),
|
||||
Err(_) => None,
|
||||
Err(err) => {
|
||||
error!("Failed to decrypt password for bookmark: {}", err);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
},
|
||||
|
@ -184,9 +213,11 @@ impl BookmarksClient {
|
|||
password: Option<String>,
|
||||
) {
|
||||
if name.is_empty() {
|
||||
error!("Fatal error; bookmark name is empty");
|
||||
panic!("Bookmark name can't be empty");
|
||||
}
|
||||
// Make bookmark
|
||||
info!("Added bookmark {} with address {}", name, addr);
|
||||
let host: Bookmark = self.make_bookmark(addr, port, protocol, username, password);
|
||||
self.hosts.bookmarks.insert(name, host);
|
||||
}
|
||||
|
@ -196,6 +227,7 @@ impl BookmarksClient {
|
|||
/// Delete entry from bookmarks
|
||||
pub fn del_bookmark(&mut self, name: &str) {
|
||||
let _ = self.hosts.bookmarks.remove(name);
|
||||
info!("Removed bookmark {}", name);
|
||||
}
|
||||
/// ### iter_recents
|
||||
///
|
||||
|
@ -209,13 +241,20 @@ impl BookmarksClient {
|
|||
/// Get recent associated to key
|
||||
pub fn get_recent(&self, key: &str) -> Option<(String, u16, FileTransferProtocol, String)> {
|
||||
// NOTE: password is not decrypted; recents will never have password
|
||||
info!("Getting bookmark {}", key);
|
||||
let entry: &Bookmark = self.hosts.recents.get(key)?;
|
||||
Some((
|
||||
entry.address.clone(),
|
||||
entry.port,
|
||||
match FileTransferProtocol::from_str(entry.protocol.as_str()) {
|
||||
Ok(proto) => proto,
|
||||
Err(_) => FileTransferProtocol::Sftp, // Default
|
||||
Err(err) => {
|
||||
error!(
|
||||
"Found invalid protocol in bookmarks: {}; defaulting to SFTP",
|
||||
err
|
||||
);
|
||||
FileTransferProtocol::Sftp // Default
|
||||
}
|
||||
},
|
||||
entry.username.clone(),
|
||||
))
|
||||
|
@ -236,6 +275,7 @@ impl BookmarksClient {
|
|||
// Check if duplicated
|
||||
for recent_host in self.hosts.recents.values() {
|
||||
if *recent_host == host {
|
||||
debug!("Discarding recent since duplicated ({})", host.address);
|
||||
// Don't save duplicates
|
||||
return;
|
||||
}
|
||||
|
@ -252,6 +292,7 @@ impl BookmarksClient {
|
|||
// Delete keys starting from the last one
|
||||
for key in keys.iter() {
|
||||
let _ = self.hosts.recents.remove(key);
|
||||
debug!("Removed recent bookmark {}", key);
|
||||
// If length is < self.recents_size; break
|
||||
if self.hosts.recents.len() < self.recents_size {
|
||||
break;
|
||||
|
@ -259,6 +300,7 @@ impl BookmarksClient {
|
|||
}
|
||||
}
|
||||
let name: String = fmt_time(SystemTime::now(), "ISO%Y%m%dT%H%M%S");
|
||||
info!("Saved recent host {} ({})", name, host.address);
|
||||
self.hosts.recents.insert(name, host);
|
||||
}
|
||||
|
||||
|
@ -267,6 +309,7 @@ impl BookmarksClient {
|
|||
/// Delete entry from recents
|
||||
pub fn del_recent(&mut self, name: &str) {
|
||||
let _ = self.hosts.recents.remove(name);
|
||||
info!("Removed recent host {}", name);
|
||||
}
|
||||
|
||||
/// ### write_bookmarks
|
||||
|
@ -274,6 +317,7 @@ impl BookmarksClient {
|
|||
/// Write bookmarks to file
|
||||
pub fn write_bookmarks(&self) -> Result<(), SerializerError> {
|
||||
// Open file
|
||||
debug!("Writing bookmarks");
|
||||
match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
|
@ -284,10 +328,13 @@ impl BookmarksClient {
|
|||
let serializer: BookmarkSerializer = BookmarkSerializer {};
|
||||
serializer.serialize(Box::new(writer), &self.hosts)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Failed to write bookmarks: {}", err);
|
||||
Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -296,6 +343,7 @@ impl BookmarksClient {
|
|||
/// Read bookmarks from file
|
||||
fn read_bookmarks(&mut self) -> Result<(), SerializerError> {
|
||||
// Open bookmarks file for read
|
||||
debug!("Reading bookmarks");
|
||||
match OpenOptions::new()
|
||||
.read(true)
|
||||
.open(self.bookmarks_file.as_path())
|
||||
|
@ -311,10 +359,13 @@ impl BookmarksClient {
|
|||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Failed to read bookmarks: {}", err);
|
||||
Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,11 @@ impl ConfigClient {
|
|||
pub fn new(config_path: &Path, ssh_key_dir: &Path) -> Result<ConfigClient, SerializerError> {
|
||||
// Initialize a default configuration
|
||||
let default_config: UserConfig = UserConfig::default();
|
||||
info!(
|
||||
"Setting up config client with config path {} and SSH key directory {}",
|
||||
config_path.display(),
|
||||
ssh_key_dir.display()
|
||||
);
|
||||
// Create client
|
||||
let mut client: ConfigClient = ConfigClient {
|
||||
config: default_config,
|
||||
|
@ -67,6 +72,7 @@ impl ConfigClient {
|
|||
// If ssh key directory doesn't exist, create it
|
||||
if !ssh_key_dir.exists() {
|
||||
if let Err(err) = create_dir(ssh_key_dir) {
|
||||
error!("Failed to create SSH key dir: {}", err);
|
||||
return Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
format!(
|
||||
|
@ -76,17 +82,22 @@ impl ConfigClient {
|
|||
),
|
||||
));
|
||||
}
|
||||
debug!("Created SSH key directory");
|
||||
}
|
||||
// If Config file doesn't exist, create it
|
||||
if !config_path.exists() {
|
||||
if let Err(err) = client.write_config() {
|
||||
error!("Couldn't create configuration file: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
debug!("Config file didn't exist; created file");
|
||||
} else {
|
||||
// otherwise Load configuration from file
|
||||
if let Err(err) = client.read_config() {
|
||||
error!("Couldn't read configuration file: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
debug!("Read configuration file");
|
||||
}
|
||||
Ok(client)
|
||||
}
|
||||
|
@ -230,12 +241,18 @@ impl ConfigClient {
|
|||
p.push(format!("{}.key", host_name));
|
||||
p
|
||||
};
|
||||
info!(
|
||||
"Writing SSH file to {} for host {}",
|
||||
ssh_key_path.display(),
|
||||
host_name
|
||||
);
|
||||
// Write key to file
|
||||
let mut f: File = match File::create(ssh_key_path.as_path()) {
|
||||
Ok(f) => f,
|
||||
Err(err) => return Self::make_io_err(err),
|
||||
};
|
||||
if let Err(err) = f.write_all(ssh_key.as_bytes()) {
|
||||
error!("Failed to write SSH key to file: {}", err);
|
||||
return Self::make_io_err(err);
|
||||
}
|
||||
// Add host to keys
|
||||
|
@ -251,6 +268,7 @@ impl ConfigClient {
|
|||
/// and also commits changes to configuration, to prevent incoerent data
|
||||
pub fn del_ssh_key(&mut self, host: &str, username: &str) -> Result<(), SerializerError> {
|
||||
// Remove key from configuration and get key path
|
||||
info!("Removing key for {}@{}", host, username);
|
||||
let key_path: PathBuf = match self
|
||||
.config
|
||||
.remote
|
||||
|
@ -262,6 +280,7 @@ impl ConfigClient {
|
|||
};
|
||||
// Remove file
|
||||
if let Err(err) = remove_file(key_path.as_path()) {
|
||||
error!("Failed to remove key file {}: {}", key_path.display(), err);
|
||||
return Self::make_io_err(err);
|
||||
}
|
||||
// Commit changes to configuration
|
||||
|
@ -310,10 +329,13 @@ impl ConfigClient {
|
|||
let serializer: ConfigSerializer = ConfigSerializer {};
|
||||
serializer.serialize(Box::new(writer), &self.config)
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Failed to write configuration file: {}", err);
|
||||
Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,10 +359,13 @@ impl ConfigClient {
|
|||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
)),
|
||||
Err(err) => {
|
||||
error!("Failed to read configuration: {}", err);
|
||||
Err(SerializerError::new_ex(
|
||||
SerializerErrorKind::IoError,
|
||||
err.to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ impl SshKeyStorage {
|
|||
pub fn storage_from_config(cfg_client: &ConfigClient) -> Self {
|
||||
let mut hosts: HashMap<String, PathBuf> =
|
||||
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
|
||||
debug!("Setting up SSH key storage");
|
||||
// Iterate over keys
|
||||
for key in cfg_client.iter_ssh_keys() {
|
||||
match cfg_client.get_ssh_key(key) {
|
||||
|
@ -52,8 +53,12 @@ impl SshKeyStorage {
|
|||
}
|
||||
None => continue,
|
||||
},
|
||||
Err(_) => continue,
|
||||
Err(err) => {
|
||||
error!("Failed to get SSH key for {}: {}", key, err);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
info!("Got SSH key for {}", key);
|
||||
}
|
||||
// Return storage
|
||||
SshKeyStorage { hosts }
|
||||
|
|
Loading…
Reference in a new issue