From 24f34da588d1e3411a7486d64d6ed96551b2144d Mon Sep 17 00:00:00 2001 From: qwjyh Date: Sun, 3 Mar 2024 06:11:25 +0900 Subject: [PATCH] update storage list and some refactor --- Cargo.lock | 1 + Cargo.toml | 1 + src/cmd_args.rs | 6 +- src/cmd_storage.rs | 106 +++++++++++++++++++---- src/main.rs | 4 +- src/storages.rs | 15 +++- src/storages/directory.rs | 81 +++++++++++++++-- src/storages/local_info.rs | 9 +- src/storages/online_storage.rs | 27 +++--- src/storages/physical_drive_partition.rs | 58 ++++++++++--- 10 files changed, 257 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d95a43..a921fd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,4 +1125,5 @@ dependencies = [ "serde", "serde_yaml", "sysinfo", + "unicode-width", ] diff --git a/Cargo.toml b/Cargo.toml index 39d2bcc..14e2632 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ serde_yaml = "0.9" byte-unit = "4.0.19" anyhow = "1.0" pathdiff = "0.2.1" +unicode-width = "0.1.11" diff --git a/src/cmd_args.rs b/src/cmd_args.rs index 09d299d..67a5f1b 100644 --- a/src/cmd_args.rs +++ b/src/cmd_args.rs @@ -57,7 +57,11 @@ pub(crate) enum StorageCommands { path: Option, }, /// List all storages. - List {}, + List { + /// Show note on the storages. + #[arg(short, long)] + long: bool, + }, /// Make `storage` available for the current device. /// For physical disk, the name is taken from system info automatically. Bind { diff --git a/src/cmd_storage.rs b/src/cmd_storage.rs index db1ce84..ba6015c 100644 --- a/src/cmd_storage.rs +++ b/src/cmd_storage.rs @@ -2,22 +2,24 @@ use std::{ collections::HashMap, - path::{Path, PathBuf}, + io::{self, Write}, + path::{Path, PathBuf}, string, }; use anyhow::{anyhow, Context, Result}; +use byte_unit::Byte; use clap::{error::ErrorKind, CommandFactory}; use git2::Repository; use inquire::{min_length, Confirm, CustomType, Select, Text}; +use unicode_width::{self, UnicodeWidthStr}; use crate::{ add_and_commit, ask_unique_name, cmd_args::Cli, - get_device, + devices::{self, Device}, inquire_filepath_completer::FilePathCompleter, storages::{ - self, directory, get_storages, local_info, physical_drive_partition, Storage, StorageExt, - StorageType, + self, directory, local_info, physical_drive_partition, Storage, StorageExt, StorageType, }, }; @@ -30,10 +32,10 @@ pub(crate) fn cmd_storage_add( trace!("Storage Add {:?}, {:?}", storage_type, path); // Get storages // let mut storages: Vec = get_storages(&config_dir)?; - let mut storages: HashMap = get_storages(&config_dir)?; + let mut storages: HashMap = storages::get_storages(&config_dir)?; trace!("found storages: {:?}", storages); - let device = get_device(&config_dir)?; + let device = devices::get_device(&config_dir)?; let (key, storage) = match storage_type { StorageType::Physical => { let use_sysinfo = { @@ -110,6 +112,7 @@ pub(crate) fn cmd_storage_add( } let path = path.unwrap_or_else(|| { let mut cmd = Cli::command(); + // TODO: weired def of cmd argument cmd.error( ErrorKind::MissingRequiredArgument, " is required with sub-directory", @@ -193,15 +196,88 @@ pub(crate) fn cmd_storage_add( Ok(()) } -pub(crate) fn cmd_storage_list(config_dir: &PathBuf) -> Result<()> { +pub(crate) fn cmd_storage_list(config_dir: &PathBuf, with_note: bool) -> Result<()> { // Get storages - let storages: HashMap = get_storages(&config_dir)?; + let storages: HashMap = storages::get_storages(&config_dir)?; trace!("found storages: {:?}", storages); - let device = get_device(&config_dir)?; - for (k, storage) in &storages { - println!("{}: {}", k, storage); - println!(" {}", storage.mount_path(&device, &storages)?.display()); - // println!("{}: {}", storage.shorttypename(), storage.name()); // TODO + let device = devices::get_device(&config_dir)?; + let mut stdout = io::BufWriter::new(io::stdout()); + write_storages_list(&mut stdout, &storages, &device, with_note)?; + stdout.flush()?; + Ok(()) +} + +fn write_storages_list( + mut writer: impl io::Write, + storages: &HashMap, + device: &Device, + long_display: bool, +) -> Result<()> { + let name_width = storages + .iter() + .map(|(_k, v)| v.name().width()) + .max() + .unwrap(); + trace!("name widths: {}", name_width); + for (_k, storage) in storages { + let size_str = match storage.capacity() { + Some(b) => Byte::from_bytes(b.into()) + .get_appropriate_unit(true) + .format(0) + .to_string(), + None => "".to_string(), + }; + let isremovable = if let Storage::PhysicalStorage(s) = storage { + if s.is_removable() { + "+" + } else { + "-" + } + } else { + " " + }; + let path = storage.mount_path(&device, &storages).map_or_else( + |e| { + info!("Not found: {}", e); + "".to_string() + }, + |v| v.display().to_string(), + ); + let parent_name = if let Storage::SubDirectory(s) = storage { + s.parent(&storages) + .context(format!("Failed to get parent of storage {}", s))? + .name() + } else { + "" + }; + writeln!( + writer, + "{stype}{isremovable}: {name:8} {parent: { + s.kind() + }, + Storage::SubDirectory(s) => { + &s.notes + }, + Storage::Online(s) => { + &s.provider + }, + }; + writeln!( + writer, + " {}", + note + )?; + } } Ok(()) } @@ -213,9 +289,9 @@ pub(crate) fn cmd_storage_bind( repo: Repository, config_dir: &PathBuf, ) -> Result<()> { - let device = get_device(&config_dir)?; + let device = devices::get_device(&config_dir)?; // get storages - let mut storages: HashMap = get_storages(&config_dir)?; + let mut storages: HashMap = storages::get_storages(&config_dir)?; let commit_comment = { // find matching storage let storage = &mut storages diff --git a/src/main.rs b/src/main.rs index 75a48d4..e0c7f3c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ extern crate log; extern crate dirs; use anyhow::{anyhow, Context, Result}; -use clap::error::ErrorKind; use clap::{CommandFactory, Parser}; use git2::{Commit, Oid, Repository}; use inquire::{min_length, Confirm, CustomType, Select}; @@ -24,7 +23,6 @@ use std::path::Path; use std::path::{self, PathBuf}; use crate::cmd_args::{Cli, Commands, StorageCommands}; -use crate::cmd_storage; use crate::storages::online_storage; use crate::storages::{ directory, get_storages, local_info, physical_drive_partition, write_storages, Storage, @@ -64,7 +62,7 @@ fn main() -> Result<()> { StorageCommands::Add { storage_type, path } => { cmd_storage::cmd_storage_add(storage_type, path, repo, &config_dir)? } - StorageCommands::List {} => cmd_storage::cmd_storage_list(&config_dir)?, + StorageCommands::List { long } => cmd_storage::cmd_storage_list(&config_dir, long)?, StorageCommands::Bind { storage: storage_name, alias: new_alias, diff --git a/src/storages.rs b/src/storages.rs index 7e3ece6..3e3c838 100644 --- a/src/storages.rs +++ b/src/storages.rs @@ -7,7 +7,7 @@ use crate::storages::physical_drive_partition::PhysicalDrivePartition; use anyhow::{anyhow, Context, Result}; use clap::ValueEnum; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, ffi, fmt, fs, io, path}; +use std::{collections::HashMap, ffi, fmt, fs, io, path, u64}; /// YAML file to store known storages.. pub const STORAGESFILE: &str = "storages.yml"; @@ -88,6 +88,14 @@ impl StorageExt for Storage { Storage::Online(s) => s.bound_on_device(alias, mount_point, device), } } + + fn capacity(&self) -> Option { + match self { + Storage::PhysicalStorage(s) => s.capacity(), + Storage::SubDirectory(s) => s.capacity(), + Storage::Online(s) => s.capacity(), + } + } } impl fmt::Display for Storage { @@ -104,6 +112,10 @@ impl fmt::Display for Storage { pub trait StorageExt { fn name(&self) -> &String; + /// Capacity in bytes. + /// Since [Directory] has no capacity information, it has `None`. + fn capacity(&self) -> Option; + /// Return `true` if `self` has local info on the `device`. /// Used to check if the storage is bound on the `device`. fn has_alias(&self, device: &devices::Device) -> bool { @@ -114,6 +126,7 @@ pub trait StorageExt { fn local_info(&self, device: &devices::Device) -> Option<&local_info::LocalInfo>; /// Get mount path of `self` on `device`. + /// `storages` is a `HashMap` with key of storage name and value of the storage. fn mount_path( &self, device: &devices::Device, diff --git a/src/storages/directory.rs b/src/storages/directory.rs index c77a480..2ac849f 100644 --- a/src/storages/directory.rs +++ b/src/storages/directory.rs @@ -14,8 +14,8 @@ pub struct Directory { name: String, parent: String, relative_path: path::PathBuf, - notes: String, - local_info: HashMap, + pub notes: String, + local_infos: HashMap, } impl Directory { @@ -28,14 +28,14 @@ impl Directory { parent: String, relative_path: path::PathBuf, notes: String, - local_info: HashMap, + local_infos: HashMap, ) -> Directory { Directory { name, parent, relative_path, notes, - local_info, + local_infos, } } @@ -80,12 +80,12 @@ impl Directory { self.parent, self.relative_path, notes, - self.local_info, + self.local_infos, ) } /// Get parent `&Storage` of directory. - fn parent<'a>(&'a self, storages: &'a HashMap) -> Result<&Storage> { + pub fn parent<'a>(&'a self, storages: &'a HashMap) -> Result<&Storage> { let parent = &self.parent; storages.get(parent).context(format!( "No parent {} exists for directory {}", @@ -111,8 +111,12 @@ impl StorageExt for Directory { &self.name } + fn capacity(&self) -> Option { + None + } + fn local_info(&self, device: &devices::Device) -> Option<&LocalInfo> { - self.local_info.get(&device.name()) + self.local_infos.get(&device.name()) } fn mount_path( @@ -131,7 +135,7 @@ impl StorageExt for Directory { device: &devices::Device, ) -> Result<()> { let new_local_info = LocalInfo::new(alias, mount_point); - match self.local_info.insert(device.name(), new_local_info) { + match self.local_infos.insert(device.name(), new_local_info) { Some(v) => println!("Value updated. old val is: {:?}", v), None => println!("inserted new val"), }; @@ -151,3 +155,64 @@ impl fmt::Display for Directory { ) } } + +#[cfg(test)] +mod test { + use std::{collections::HashMap, path::PathBuf}; + + use crate::{ + devices::Device, + storages::{ + self, local_info::LocalInfo, physical_drive_partition::PhysicalDrivePartition, Storage, + StorageExt, + }, + }; + + use super::Directory; + + #[test] + fn name() { + let local_info_phys = + LocalInfo::new("phys_alias".to_string(), PathBuf::from("/mnt/sample")); + let local_info_dir = + LocalInfo::new("dir_alias".to_string(), PathBuf::from("/mnt/sample/subdir")); + let device = Device::new("test_device".to_string()); + let mut local_infos = HashMap::new(); + local_infos.insert(device.name(), local_info_dir); + let physical = PhysicalDrivePartition::new( + "parent".to_string(), + "SSD".to_string(), + 1_000_000_000, + "btrfs".to_string(), + false, + local_info_phys, + &device, + ); + let directory = Directory::new( + "test_name".to_owned(), + "parent".to_string(), + "subdir".into(), + "some note".to_string(), + local_infos, + ); + let mut storages: HashMap = HashMap::new(); + storages.insert( + physical.name().to_string(), + Storage::PhysicalStorage(physical), + ); + storages.insert( + directory.name().to_string(), + Storage::SubDirectory(directory), + ); + // assert_eq!(directory.name(), "test_name"); + assert_eq!(storages.get("test_name").unwrap().name(), "test_name"); + assert_eq!( + storages + .get("test_name") + .unwrap() + .mount_path(&device, &storages) + .unwrap(), + PathBuf::from("/mnt/sample/subdir") + ); + } +} diff --git a/src/storages/local_info.rs b/src/storages/local_info.rs index 4e36a33..1e4b89e 100644 --- a/src/storages/local_info.rs +++ b/src/storages/local_info.rs @@ -1,7 +1,7 @@ //! Device specific common data for storages. use serde::{Deserialize, Serialize}; -use std::path; +use std::path::{self, PathBuf}; /// Store local (device-specific) information /// @@ -26,3 +26,10 @@ impl LocalInfo { self.mount_path.clone() } } + +#[test] +fn localinfo() { + let localinfo = LocalInfo::new("alias".to_string(), PathBuf::from("/mnt/sample")); + assert_eq!(localinfo.alias(), "alias".to_string()); + assert_eq!(localinfo.mount_path(), PathBuf::from("/mnt/sample")); +} diff --git a/src/storages/online_storage.rs b/src/storages/online_storage.rs index 77b16da..d07d74b 100644 --- a/src/storages/online_storage.rs +++ b/src/storages/online_storage.rs @@ -9,16 +9,17 @@ use std::path; use crate::devices; -use super::local_info; -use super::local_info::LocalInfo; -use super::StorageExt; +use super::{ + local_info::{self, LocalInfo}, + StorageExt, +}; #[derive(Serialize, Deserialize, Debug)] pub struct OnlineStorage { name: String, - provider: String, + pub provider: String, capacity: u64, - local_info: HashMap, + local_infos: HashMap, } impl OnlineStorage { @@ -35,7 +36,7 @@ impl OnlineStorage { name, provider, capacity, - local_info: HashMap::from([(device.name(), local_info)]), + local_infos: HashMap::from([(device.name(), local_info)]), } } } @@ -45,17 +46,21 @@ impl StorageExt for OnlineStorage { &self.name } + fn capacity(&self) -> Option { + Some(self.capacity) + } + fn local_info(&self, device: &devices::Device) -> Option<&LocalInfo> { - self.local_info.get(&device.name()) + self.local_infos.get(&device.name()) } fn mount_path( &self, device: &devices::Device, _storages: &HashMap, - ) -> anyhow::Result { + ) -> Result { Ok(self - .local_info + .local_infos .get(&device.name()) .context(format!("LocalInfo for storage: {} not found", &self.name()))? .mount_path()) @@ -68,7 +73,7 @@ impl StorageExt for OnlineStorage { device: &devices::Device, ) -> Result<()> { match self - .local_info + .local_infos .insert(device.name(), LocalInfo::new(alias, mount_point)) { Some(old) => info!("Value replaced. Old value: {:?}", old), @@ -82,7 +87,7 @@ impl fmt::Display for OnlineStorage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "O {name:<10} {size} {provider:<10}", + "O {name:<10} {size:<10} {provider:<10}", name = self.name(), size = Byte::from_bytes(self.capacity.into()).get_appropriate_unit(true), provider = self.provider, diff --git a/src/storages/physical_drive_partition.rs b/src/storages/physical_drive_partition.rs index 48eeb76..14afe66 100644 --- a/src/storages/physical_drive_partition.rs +++ b/src/storages/physical_drive_partition.rs @@ -1,13 +1,13 @@ //! Manipulate partition of physical drive (both removable and unremovable). use crate::devices; +use crate::devices::Device; use crate::storages::{Storage, StorageExt}; -use crate::{devices::Device, get_device}; use anyhow::{anyhow, Context, Result}; use byte_unit::Byte; use inquire::Text; use serde::{Deserialize, Serialize}; -use std::path; +use std::path::{self, PathBuf}; use std::{ collections::{hash_map::RandomState, HashMap}, fmt, @@ -25,7 +25,7 @@ pub struct PhysicalDrivePartition { fs: String, is_removable: bool, // system_names: HashMap, - local_info: HashMap, + local_infos: HashMap, } impl PhysicalDrivePartition { @@ -44,7 +44,7 @@ impl PhysicalDrivePartition { capacity, fs, is_removable, - local_info: HashMap::from([(device.name(), local_info)]), + local_infos: HashMap::from([(device.name(), local_info)]), } } @@ -70,7 +70,7 @@ impl PhysicalDrivePartition { fs: fs.to_string(), is_removable: disk.is_removable(), // system_names: HashMap::from([(device.name(), alias)]), - local_info: HashMap::from([(device.name(), local_info)]), + local_infos: HashMap::from([(device.name(), local_info)]), }) } @@ -79,13 +79,13 @@ impl PhysicalDrivePartition { disk: &sysinfo::Disk, config_dir: &std::path::PathBuf, ) -> Result<()> { - let device = get_device(&config_dir)?; + let device = devices::get_device(&config_dir)?; let alias = match disk.name().to_str() { Some(s) => s.to_string(), None => return Err(anyhow!("Failed to convert storage name to valid str.")), }; let new_local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf()); - let aliases = &mut self.local_info; + let aliases = &mut self.local_infos; trace!("aliases: {:?}", aliases); match aliases.insert(device.name(), new_local_info) { Some(v) => println!("Value updated. old val is: {:?}", v), @@ -94,6 +94,38 @@ impl PhysicalDrivePartition { trace!("aliases: {:?}", aliases); Ok(()) } + + pub fn is_removable(&self) -> bool { + self.is_removable + } + + pub fn kind(&self) -> &String { + &self.kind + } +} + +#[cfg(test)] +mod test { + use crate::{devices::Device, storages::{local_info::LocalInfo, StorageExt}}; + use std::path::PathBuf; + + use super::PhysicalDrivePartition; + + #[test] + fn test_new() { + let localinfo = LocalInfo::new("alias".to_string(), PathBuf::from("/mnt/sample")); + let storage = PhysicalDrivePartition::new( + "name".to_string(), + "SSD".to_string(), + 100, + "ext_4".to_string(), + true, + localinfo, + &Device::new("test_device".to_string()), + ); + assert_eq!(storage.name(), "name"); + assert_eq!(storage.capacity(), Some(100)); + } } impl StorageExt for PhysicalDrivePartition { @@ -101,8 +133,12 @@ impl StorageExt for PhysicalDrivePartition { &self.name } + fn capacity(&self) -> Option { + Some(self.capacity) + } + fn local_info(&self, device: &devices::Device) -> Option<&local_info::LocalInfo> { - self.local_info.get(&device.name()) + self.local_infos.get(&device.name()) } fn mount_path( @@ -111,7 +147,7 @@ impl StorageExt for PhysicalDrivePartition { _: &HashMap, ) -> Result { Ok(self - .local_info + .local_infos .get(&device.name()) .context(format!("LocalInfo for storage: {} not found", &self.name()))? .mount_path()) @@ -124,7 +160,7 @@ impl StorageExt for PhysicalDrivePartition { device: &devices::Device, ) -> Result<()> { match self - .local_info + .local_infos .insert(device.name(), LocalInfo::new(alias, mount_point)) { Some(old) => info!("Value replaced. Old value: {:?}", old), @@ -139,7 +175,7 @@ impl fmt::Display for PhysicalDrivePartition { let removable_indicator = if self.is_removable { "+" } else { "-" }; write!( f, - "P {name:<10} {size} {removable:<1} {kind:<6} {fs:<5}", + "P {name:<10} {size:<10} {removable:<1} {kind:<6} {fs:<5}", name = self.name(), size = Byte::from_bytes(self.capacity.into()).get_appropriate_unit(true), removable = removable_indicator,