update storage list and some refactor

This commit is contained in:
qwjyh 2024-03-03 06:11:25 +09:00
parent ff72228821
commit 24f34da588
10 changed files with 257 additions and 51 deletions

1
Cargo.lock generated
View file

@ -1125,4 +1125,5 @@ dependencies = [
"serde",
"serde_yaml",
"sysinfo",
"unicode-width",
]

View file

@ -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"

View file

@ -57,7 +57,11 @@ pub(crate) enum StorageCommands {
path: Option<PathBuf>,
},
/// 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 {

View file

@ -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<Storage> = get_storages(&config_dir)?;
let mut storages: HashMap<String, Storage> = get_storages(&config_dir)?;
let mut storages: HashMap<String, Storage> = 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,
"<PATH> 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<String, Storage> = get_storages(&config_dir)?;
let storages: HashMap<String, Storage> = 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<String, Storage>,
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:<name_width$} {size:>8} {parent:<name_width$} {path}",
stype = storage.shorttypename(),
isremovable = isremovable,
name = storage.name(),
size = size_str,
parent = parent_name,
path = path,
)?;
if long_display {
let note = match storage {
Storage::PhysicalStorage(s) => {
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<String, Storage> = get_storages(&config_dir)?;
let mut storages: HashMap<String, Storage> = storages::get_storages(&config_dir)?;
let commit_comment = {
// find matching storage
let storage = &mut storages

View file

@ -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,

View file

@ -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<u64> {
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<u64>;
/// 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,

View file

@ -14,8 +14,8 @@ pub struct Directory {
name: String,
parent: String,
relative_path: path::PathBuf,
notes: String,
local_info: HashMap<String, LocalInfo>,
pub notes: String,
local_infos: HashMap<String, LocalInfo>,
}
impl Directory {
@ -28,14 +28,14 @@ impl Directory {
parent: String,
relative_path: path::PathBuf,
notes: String,
local_info: HashMap<String, LocalInfo>,
local_infos: HashMap<String, LocalInfo>,
) -> 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<String, Storage>) -> Result<&Storage> {
pub fn parent<'a>(&'a self, storages: &'a HashMap<String, Storage>) -> 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<u64> {
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<String, Storage> = 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")
);
}
}

View file

@ -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"));
}

View file

@ -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<String, LocalInfo>,
local_infos: HashMap<String, LocalInfo>,
}
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<u64> {
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<String, super::Storage>,
) -> anyhow::Result<std::path::PathBuf> {
) -> Result<std::path::PathBuf> {
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,

View file

@ -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<String, String>,
local_info: HashMap<String, LocalInfo>,
local_infos: HashMap<String, LocalInfo>,
}
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<u64> {
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<String, Storage>,
) -> Result<path::PathBuf> {
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,