new: adding sub-directory

This commit is contained in:
qwjyh 2023-12-05 03:24:49 +09:00
parent 7a4c1e5325
commit abcb01897f
6 changed files with 255 additions and 53 deletions

106
src/.storages.rs.rustfmt Normal file
View file

@ -0,0 +1,106 @@
//! Manipulates storages.
use crate::storages::
use crate::storages::directory::Directory;
use crate::storages::physical_drive_partition::PhysicalDrivePartition;
use anyhow::{anyhow, Context, Result};
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use std::{ffi, fmt, fs, io, path::Path, collections::HashMap};
/// YAML file to store known storages..
pub const STORAGESFILE: &str = "storages.yml";
#[derive(ValueEnum, Clone, Copy, Debug)]
pub enum StorageType {
Physical,
SubDirectory,
// Online,
}
/// All storage types.
#[derive(Serialize, Deserialize, Debug)]
pub enum Storage {
PhysicalStorage(PhysicalDrivePartition),
SubDirectory(Directory),
// /// Online storage provided by others.
// OnlineStorage {
// name: String,
// provider: String,
// capacity: u8,
// },
}
impl Storage {
/// Add or update alias of `disk` for current device.
pub fn add_alias(
&mut self,
disk: &sysinfo::Disk,
config_dir: &std::path::PathBuf,
) -> anyhow::Result<()> {
match self {
Self::PhysicalStorage(s) => s.add_alias(disk, config_dir),
Self::SubDirectory(_) => Err(anyhow!("SubDirectory doesn't have system alias.")),
}
}
}
impl StorageExt for Storage {
fn name(&self) -> &String {
match self {
Self::PhysicalStorage(s) => s.name(),
Self::SubDirectory(s) => s.name(),
}
}
}
impl fmt::Display for Storage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PhysicalStorage(s) => s.fmt(f),
Self::SubDirectory(s) => s.fmt(f),
}
}
}
/// Trait to manipulate all `Storage`s (Enums).
pub trait StorageExt {
fn name(&self) -> &String;
}
pub mod directory;
pub mod physical_drive_partition;
/// Get `Vec<Storage>` from devices.yml([DEVICESFILE]).
/// If [DEVICESFILE] isn't found, return empty vec.
pub fn get_storages(config_dir: &Path) -> Result<HashMap<String, Storage>> {
if let Some(storages_file) = fs::read_dir(&config_dir)?
.filter(|f| {
f.as_ref().map_or_else(
|_e| false,
|f| {
let storagesfile: ffi::OsString = STORAGESFILE.into();
f.path().file_name() == Some(&storagesfile)
},
)
})
.next()
{
trace!("{} found: {:?}", STORAGESFILE, storages_file);
let f = fs::File::open(config_dir.join(STORAGESFILE))?;
let reader = io::BufReader::new(f);
let yaml: HashMap<String, Storage> =
serde_yaml::from_reader(reader).context("Failed to read devices.yml")?;
Ok(yaml)
} else {
trace!("No {} found", STORAGESFILE);
Ok(HashMap::new())
}
}
/// Write `storages` to yaml file in `config_dir`.
pub fn write_storages(config_dir: &Path, storages: HashMap<String, Storage>) -> Result<()> {
let f = fs::File::create(config_dir.join(STORAGESFILE))?;
let writer = io::BufWriter::new(f);
serde_yaml::to_writer(writer, &storages).map_err(|e| anyhow!(e))
}

View file

@ -200,16 +200,18 @@ fn main() -> Result<()> {
let mut storages: HashMap<String, Storage> = get_storages(&config_dir)?; let mut storages: HashMap<String, Storage> = get_storages(&config_dir)?;
trace!("found storages: {:?}", storages); trace!("found storages: {:?}", storages);
let device = get_device(&config_dir)?;
let (key, storage) = match storage_type { let (key, storage) = match storage_type {
StorageType::Physical => { StorageType::Physical => {
// select storage // select storage
let device = get_device(&config_dir)?;
let (key, storage) = select_physical_storage(device, &storages)?; let (key, storage) = select_physical_storage(device, &storages)?;
println!("storage: {}: {:?}", key, storage); println!("storage: {}: {:?}", key, storage);
(key, Storage::PhysicalStorage(storage)) (key, Storage::PhysicalStorage(storage))
} }
StorageType::SubDirectory => { StorageType::SubDirectory => {
let mut storages: HashMap<String, Storage> = get_storages(&config_dir)?; if storages.is_empty() {
return Err(anyhow!("No storages found. Please add at least 1 physical storage first."));
}
let path = path.unwrap_or_else(|| { let path = path.unwrap_or_else(|| {
let mut cmd = Cli::command(); let mut cmd = Cli::command();
cmd.error( cmd.error(
@ -223,8 +225,16 @@ fn main() -> Result<()> {
let path = path.canonicalize()?; let path = path.canonicalize()?;
trace!("canonicalized: path: {:?}", path); trace!("canonicalized: path: {:?}", path);
// let (key, storage) = storages::directory::Directory::new(name, parent, relative_path, notes) let key_name = ask_unique_name(&storages, "sub-directory".to_string())?;
todo!() let notes = Text::new("Notes for this sub-directory:").prompt()?;
let storage = storages::directory::Directory::try_from_device_path(
key_name.clone(),
path,
notes,
&device,
&storages,
)?;
(key_name, Storage::SubDirectory(storage))
} }
}; };
@ -249,8 +259,10 @@ fn main() -> Result<()> {
// Get storages // Get storages
let storages: HashMap<String, Storage> = get_storages(&config_dir)?; let storages: HashMap<String, Storage> = get_storages(&config_dir)?;
trace!("found storages: {:?}", storages); trace!("found storages: {:?}", storages);
for (k, storage) in storages { let device = get_device(&config_dir)?;
for (k, storage) in &storages {
println!("{}: {}", k, storage); println!("{}: {}", k, storage);
println!(" {}", storage.mount_path(&device, &storages)?.display());
} }
} }
StorageCommands::Bind { StorageCommands::Bind {
@ -307,7 +319,7 @@ fn main() -> Result<()> {
Ok(()) Ok(())
} }
/// Get devname of the device. /// Get devname of the device from file `devname`.
fn get_devname(config_dir: &Path) -> Result<String> { fn get_devname(config_dir: &Path) -> Result<String> {
let f = File::open(config_dir.join("devname")).context("Failed to open devname file")?; let f = File::open(config_dir.join("devname")).context("Failed to open devname file")?;
let bufreader = BufReader::new(f); let bufreader = BufReader::new(f);
@ -385,6 +397,18 @@ fn write_devices(config_dir: &Path, devices: Vec<Device>) -> Result<()> {
serde_yaml::to_writer(writer, &devices).map_err(|e| anyhow!(e)) serde_yaml::to_writer(writer, &devices).map_err(|e| anyhow!(e))
} }
fn ask_unique_name(storages: &HashMap<String, Storage>, target: String) -> Result<String> {
let mut disk_name = String::new();
loop {
disk_name = Text::new(format!("Name for {}:", target).as_str()).prompt()?;
if storages.iter().all(|(k, v)| k != &disk_name) {
break;
}
println!("The name {} is already used.", disk_name);
}
Ok(disk_name)
}
fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> { fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> {
if repo.is_empty()? { if repo.is_empty()? {
Ok(None) Ok(None)

View file

@ -1,12 +1,12 @@
//! Manipulates storages. //! Manipulates storages.
use crate::storages::physical_drive_partition::PhysicalDrivePartition; use crate::storages::physical_drive_partition::PhysicalDrivePartition;
use crate::{devices, storages::directory::Directory}; use crate::storages::directory::Directory;
use crate::devices;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use clap::ValueEnum; use clap::ValueEnum;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::{self}; use std::{collections::HashMap, ffi, fmt, fs, io, path};
use std::{collections::HashMap, ffi, fmt, fs, io, path::Path};
/// YAML file to store known storages.. /// YAML file to store known storages..
pub const STORAGESFILE: &str = "storages.yml"; pub const STORAGESFILE: &str = "storages.yml";
@ -53,16 +53,26 @@ impl StorageExt for Storage {
} }
} }
// fn mount_path( fn has_alias(
// &self, &self,
// &device: &devices::Device, device: &devices::Device,
// &storages: &HashMap<String, Storage>, ) -> bool {
// ) -> Result<&path::PathBuf> { match self {
// match self { Self::PhysicalStorage(s) => s.has_alias(&device),
// Self::PhysicalStorage(s) => s.mount_path(&device, &storages), Self::SubDirectory(s) => s.has_alias(&device),
// Self::SubDirectory(s) => s.mount_path(&device, &storages), }
// } }
// }
fn mount_path(
&self,
device: &devices::Device,
storages: &HashMap<String, Storage>,
) -> Result<path::PathBuf> {
match self {
Self::PhysicalStorage(s) => s.mount_path(&device, &storages),
Self::SubDirectory(s) => s.mount_path(&device, &storages),
}
}
} }
impl fmt::Display for Storage { impl fmt::Display for Storage {
@ -77,11 +87,12 @@ impl fmt::Display for Storage {
/// Trait to manipulate all `Storage`s (Enums). /// Trait to manipulate all `Storage`s (Enums).
pub trait StorageExt { pub trait StorageExt {
fn name(&self) -> &String; fn name(&self) -> &String;
// fn mount_path( fn has_alias(&self, device: &devices::Device) -> bool;
// &self, fn mount_path(
// &device: &devices::Device, &self,
// &storages: &HashMap<String, Storage>, device: &devices::Device,
// ) -> Result<&path::PathBuf>; storages: &HashMap<String, Storage>,
) -> Result<path::PathBuf>;
} }
pub mod directory; pub mod directory;
@ -90,7 +101,7 @@ pub mod physical_drive_partition;
/// Get `Vec<Storage>` from devices.yml([DEVICESFILE]). /// Get `Vec<Storage>` from devices.yml([DEVICESFILE]).
/// If [DEVICESFILE] isn't found, return empty vec. /// If [DEVICESFILE] isn't found, return empty vec.
pub fn get_storages(config_dir: &Path) -> Result<HashMap<String, Storage>> { pub fn get_storages(config_dir: &path::Path) -> Result<HashMap<String, Storage>> {
if let Some(storages_file) = fs::read_dir(&config_dir)? if let Some(storages_file) = fs::read_dir(&config_dir)?
.filter(|f| { .filter(|f| {
f.as_ref().map_or_else( f.as_ref().map_or_else(
@ -116,7 +127,7 @@ pub fn get_storages(config_dir: &Path) -> Result<HashMap<String, Storage>> {
} }
/// Write `storages` to yaml file in `config_dir`. /// Write `storages` to yaml file in `config_dir`.
pub fn write_storages(config_dir: &Path, storages: HashMap<String, Storage>) -> Result<()> { pub fn write_storages(config_dir: &path::Path, storages: HashMap<String, Storage>) -> Result<()> {
let f = fs::File::create(config_dir.join(STORAGESFILE))?; let f = fs::File::create(config_dir.join(STORAGESFILE))?;
let writer = io::BufWriter::new(f); let writer = io::BufWriter::new(f);
serde_yaml::to_writer(writer, &storages).map_err(|e| anyhow!(e)) serde_yaml::to_writer(writer, &storages).map_err(|e| anyhow!(e))

View file

@ -5,13 +5,14 @@ use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::HashMap, collections::HashMap,
fmt::{self, write}, fmt::{self, write},
hash::Hash,
path, path,
rc::Rc, rc::Rc,
}; };
use crate::devices; use crate::devices;
use super::{Storage, StorageExt}; use super::{local_info::LocalInfo, Storage, StorageExt};
/// Subdirectory of other [Storage]s. /// Subdirectory of other [Storage]s.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -20,6 +21,7 @@ pub struct Directory {
parent: String, parent: String,
relative_path: path::PathBuf, relative_path: path::PathBuf,
notes: String, notes: String,
local_info: HashMap<String, LocalInfo>,
} }
impl Directory { impl Directory {
@ -27,40 +29,83 @@ impl Directory {
/// - `parent`: where the directory locates. /// - `parent`: where the directory locates.
/// - `relative_path`: path from root of the parent storage. /// - `relative_path`: path from root of the parent storage.
/// - `notes`: supplimental notes. /// - `notes`: supplimental notes.
pub fn new( fn new(
name: String, name: String,
parent: String, // todo implement serialize parent: String, // todo implement serialize
relative_path: path::PathBuf, relative_path: path::PathBuf,
notes: String, notes: String,
local_info: HashMap<String, LocalInfo>,
) -> Directory { ) -> Directory {
Directory { Directory {
name, name,
parent, parent,
relative_path, relative_path,
notes, notes,
local_info,
} }
} }
pub fn try_from_device_path(
name: String,
path: path::PathBuf,
notes: String,
device: &devices::Device,
storages: &HashMap<String, Storage>,
) -> Result<Directory> {
let (parent_name, diff_path) = storages
.iter()
.filter(|(_k, v)| v.has_alias(&device))
.filter_map(|(k, v)| {
let diff = pathdiff::diff_paths(&path, v.mount_path(&device, &storages).unwrap())?;
trace!("Pathdiff: {:?}", diff);
Some((k, diff))
})
.min_by_key(|(_k, v)| {
let diff_paths: Vec<path::Component<'_>> = v.components().collect();
diff_paths.len()
})
.context(format!("Failed to compare diff of paths"))?;
trace!("Selected parent: {}", parent_name);
let local_info = LocalInfo::new("".to_string(), path);
Ok(Directory::new(
name,
parent_name.clone(),
diff_path,
notes,
HashMap::from([(device.name(), local_info)]),
))
}
pub fn update_note(self, notes: String) -> Directory {
Directory::new(
self.name,
self.parent,
self.relative_path,
notes,
self.local_info,
)
}
/// Get parent `&Storage` of directory. /// Get parent `&Storage` of directory.
fn parent<'a>(&'a self, storages: &'a HashMap<String, Storage>) -> Result<&Storage> { fn parent<'a>(&'a self, storages: &'a HashMap<String, Storage>) -> Result<&Storage> {
let parent = &self.parent; let parent = &self.parent;
storages.get(&self.parent.clone()).context(format!( storages.get(parent).context(format!(
"No parent {} exists for directory {}", "No parent {} exists for directory {}",
parent, parent,
self.name() self.name()
)) ))
} }
// /// Resolve mount path of directory with current device. /// Resolve mount path of directory with current device.
// fn mount_path( fn mount_path(
// &self, &self,
// &device: &devices::Device, device: &devices::Device,
// &storages: &HashMap<String, Storage>, storages: &HashMap<String, Storage>,
// ) -> Result<path::PathBuf> { ) -> Result<path::PathBuf> {
// let parent = self.parent(&storages)?; let parent = self.parent(&storages)?;
// let parent_mount_path = parent.mount_path(&device, &storages)?; let parent_mount_path = parent.mount_path(&device, &storages)?;
// Ok(parent_mount_path.join(self.relative_path.clone())) Ok(parent_mount_path.join(self.relative_path.clone()))
// } }
} }
impl StorageExt for Directory { impl StorageExt for Directory {
@ -68,9 +113,17 @@ impl StorageExt for Directory {
&self.name &self.name
} }
// fn mount_path(&self, &device: &devices::Device, &storages: &HashMap<String, Storage>) -> Result<&path::PathBuf> { fn has_alias(&self, device: &devices::Device) -> bool {
// Ok(&self.mount_path(&device, &storages)?) self.local_info.get(&device.name()).is_some()
// } }
fn mount_path(
&self,
device: &devices::Device,
storages: &HashMap<String, Storage>,
) -> Result<path::PathBuf> {
Ok(self.mount_path(device, storages)?)
}
} }
impl fmt::Display for Directory { impl fmt::Display for Directory {

View file

@ -1,6 +1,10 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path; use std::path;
/// Store local (device-specific) information
///
/// - `alias`: name in device
/// - `mount_path`: mount path on the device
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct LocalInfo { pub struct LocalInfo {
alias: String, alias: String,
@ -12,7 +16,7 @@ impl LocalInfo {
LocalInfo { alias, mount_path } LocalInfo { alias, mount_path }
} }
pub fn mount_path(&self) -> &path::PathBuf { pub fn mount_path(&self) -> path::PathBuf {
&self.mount_path self.mount_path.clone()
} }
} }

View file

@ -48,7 +48,7 @@ impl PhysicalDrivePartition {
let fs = disk.file_system(); let fs = disk.file_system();
trace!("fs: {:?}", fs); trace!("fs: {:?}", fs);
let fs = std::str::from_utf8(fs)?; let fs = std::str::from_utf8(fs)?;
let local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf()); let local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf().canonicalize()?);
Ok(PhysicalDrivePartition { Ok(PhysicalDrivePartition {
name: name, name: name,
kind: format!("{:?}", disk.kind()), kind: format!("{:?}", disk.kind()),
@ -87,13 +87,17 @@ impl StorageExt for PhysicalDrivePartition {
&self.name &self.name
} }
// fn mount_path(&self, &device: &devices::Device, &storages: &HashMap<String, Storage>) -> Result<&path::PathBuf> { fn has_alias(&self, device: &devices::Device) -> bool {
// Ok(&self self.local_info.get(&device.name()).is_some()
// .local_info }
// .get(&device.name())
// .context(format!("LocalInfo for storage: {} not found", &self.name()))? fn mount_path(&self, device: &devices::Device, _: &HashMap<String, Storage>) -> Result<path::PathBuf> {
// .mount_path()) Ok(self
// } .local_info
.get(&device.name())
.context(format!("LocalInfo for storage: {} not found", &self.name()))?
.mount_path())
}
} }
impl fmt::Display for PhysicalDrivePartition { impl fmt::Display for PhysicalDrivePartition {