mirror of
https://github.com/qwjyh/xdbm
synced 2025-06-27 18:29:24 +09:00
Use HashMap instead of Vector<Storage>
This commit is contained in:
parent
1f0e43cd52
commit
7a4c1e5325
7 changed files with 404 additions and 234 deletions
116
src/main.rs
116
src/main.rs
|
@ -11,13 +11,16 @@ extern crate dirs;
|
|||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use byte_unit::Byte;
|
||||
use clap::{Parser, Subcommand};
|
||||
use clap::error::ErrorKind;
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use clap_verbosity_flag::Verbosity;
|
||||
use git2::{Commit, Oid, Repository};
|
||||
use inquire::{validator::Validation, Text};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_yaml;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::format;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, io::BufReader, path::Path};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
|
@ -31,8 +34,8 @@ use std::{fs, io::prelude::*};
|
|||
use sysinfo::{Disk, DiskExt, SystemExt};
|
||||
|
||||
use crate::storages::{
|
||||
get_storages, physical_drive_partition::*, write_storages, Storage, StorageExt, StorageType,
|
||||
STORAGESFILE,
|
||||
get_storages, local_info, physical_drive_partition::*, write_storages, Storage, StorageExt,
|
||||
StorageType, STORAGESFILE,
|
||||
};
|
||||
use devices::{Device, DEVICESFILE};
|
||||
|
||||
|
@ -48,7 +51,9 @@ struct Cli {
|
|||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
/// Initialize
|
||||
/// Initialize for this device.
|
||||
/// Provide `repo_url` to use existing repository, otherwise this device will be configured as the
|
||||
/// first device.
|
||||
Init {
|
||||
repo_url: Option<String>, // url?
|
||||
},
|
||||
|
@ -76,6 +81,9 @@ enum StorageCommands {
|
|||
Add {
|
||||
#[arg(value_enum)]
|
||||
storage_type: StorageType,
|
||||
|
||||
#[arg(short, long, value_name = "PATH")]
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
/// List all storages.
|
||||
List {},
|
||||
|
@ -89,6 +97,7 @@ mod storages;
|
|||
|
||||
struct BackupLog {}
|
||||
|
||||
#[feature(absolute_path)]
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
env_logger::Builder::new()
|
||||
|
@ -145,7 +154,8 @@ fn main() -> Result<()> {
|
|||
// save devname
|
||||
let devname_path = &config_dir.join("devname");
|
||||
{
|
||||
let f = File::create(devname_path).context("Failed to create devname file")?;
|
||||
let f = File::create(devname_path)
|
||||
.context("Failed to create a file to store local device name")?;
|
||||
let writer = BufWriter::new(f);
|
||||
serde_yaml::to_writer(writer, &device.name()).unwrap();
|
||||
};
|
||||
|
@ -158,6 +168,7 @@ fn main() -> Result<()> {
|
|||
} else {
|
||||
get_devices(&config_dir)?
|
||||
};
|
||||
trace!("devices: {:?}", devices);
|
||||
if devices.iter().any(|x| x.name() == device.name()) {
|
||||
return Err(anyhow!("device name is already used."));
|
||||
}
|
||||
|
@ -182,60 +193,75 @@ fn main() -> Result<()> {
|
|||
)?;
|
||||
trace!("repo state: {:?}", repo.state());
|
||||
match storage.command {
|
||||
StorageCommands::Add { storage_type } => {
|
||||
trace!("Storage Add {:?}", storage_type);
|
||||
match storage_type {
|
||||
StorageType::Physical => {
|
||||
// Get storages
|
||||
let mut storages: Vec<Storage> = get_storages(&config_dir)?;
|
||||
trace!("found storages: {:?}", storages);
|
||||
StorageCommands::Add { storage_type, path } => {
|
||||
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)?;
|
||||
trace!("found storages: {:?}", storages);
|
||||
|
||||
let (key, storage) = match storage_type {
|
||||
StorageType::Physical => {
|
||||
// select storage
|
||||
let device = get_device(&config_dir)?;
|
||||
let storage = select_physical_storage(device, &storages)?;
|
||||
println!("storage: {:?}", storage);
|
||||
let new_storage_name = storage.name().clone();
|
||||
|
||||
// add to storages
|
||||
storages.push(Storage::PhysicalStorage(storage));
|
||||
trace!("updated storages: {:?}", storages);
|
||||
|
||||
// write to file
|
||||
write_storages(&config_dir, storages)?;
|
||||
|
||||
// commit
|
||||
add_and_commit(
|
||||
&repo,
|
||||
&Path::new(STORAGESFILE),
|
||||
&format!("Add new storage(physical drive): {}", new_storage_name),
|
||||
)?;
|
||||
|
||||
println!("Added new storage.");
|
||||
trace!("Finished adding storage");
|
||||
let (key, storage) = select_physical_storage(device, &storages)?;
|
||||
println!("storage: {}: {:?}", key, storage);
|
||||
(key, Storage::PhysicalStorage(storage))
|
||||
}
|
||||
StorageType::SubDirectory => {
|
||||
let mut storages: HashMap<String, Storage> = get_storages(&config_dir)?;
|
||||
let path = path.unwrap_or_else(|| {
|
||||
let mut cmd = Cli::command();
|
||||
cmd.error(
|
||||
ErrorKind::MissingRequiredArgument,
|
||||
"<PATH> is required with sub-directory",
|
||||
)
|
||||
.exit();
|
||||
});
|
||||
trace!("SubDirectory arguments: path: {:?}", path);
|
||||
// Nightly feature std::path::absolute
|
||||
let path = path.canonicalize()?;
|
||||
trace!("canonicalized: path: {:?}", path);
|
||||
|
||||
// let (key, storage) = storages::directory::Directory::new(name, parent, relative_path, notes)
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// add to storages
|
||||
storages.insert(key.clone(), storage);
|
||||
trace!("updated storages: {:?}", storages);
|
||||
|
||||
// write to file
|
||||
write_storages(&config_dir, storages)?;
|
||||
|
||||
// commit
|
||||
add_and_commit(
|
||||
&repo,
|
||||
&Path::new(STORAGESFILE),
|
||||
&format!("Add new storage(physical drive): {}", key),
|
||||
)?;
|
||||
|
||||
println!("Added new storage.");
|
||||
trace!("Finished adding storage");
|
||||
}
|
||||
StorageCommands::List {} => {
|
||||
// Get storages
|
||||
let storages: Vec<Storage> = get_storages(&config_dir)?;
|
||||
let storages: HashMap<String, Storage> = get_storages(&config_dir)?;
|
||||
trace!("found storages: {:?}", storages);
|
||||
for storage in storages {
|
||||
println!("{}", storage);
|
||||
for (k, storage) in storages {
|
||||
println!("{}: {}", k, storage);
|
||||
}
|
||||
}
|
||||
StorageCommands::Bind {
|
||||
storage: storage_name,
|
||||
} => {
|
||||
// get storages
|
||||
let mut storages: Vec<Storage> = get_storages(&config_dir)?;
|
||||
let mut storages: HashMap<String, Storage> = get_storages(&config_dir)?;
|
||||
let commit_comment = {
|
||||
// find matching storage
|
||||
let storage = &mut storages
|
||||
.iter_mut()
|
||||
.find(|s| s.name() == &storage_name)
|
||||
let storage = storages
|
||||
.get_mut(&storage_name)
|
||||
.context(format!("No storage has name {}", storage_name))?;
|
||||
// get disk from sysinfo
|
||||
let mut sysinfo = sysinfo::System::new_all();
|
||||
|
@ -316,7 +342,7 @@ fn set_device_name() -> Result<Device> {
|
|||
}
|
||||
};
|
||||
|
||||
let device_name = Text::new("Provide the device(PC) name:")
|
||||
let device_name = Text::new("Provide name for this device:")
|
||||
.with_validator(validator)
|
||||
.prompt();
|
||||
|
||||
|
@ -406,7 +432,7 @@ fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid,
|
|||
result
|
||||
}
|
||||
|
||||
/// Print git repo status
|
||||
/// Print git repo status as trace
|
||||
fn full_status(repo: &Repository) -> Result<()> {
|
||||
trace!("status: ");
|
||||
for status in repo.statuses(None)?.iter() {
|
||||
|
@ -416,3 +442,9 @@ fn full_status(repo: &Repository) -> Result<()> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_cli() {
|
||||
use clap::CommandFactory;
|
||||
Cli::command().debug_assert()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
//! Manipulates storages.
|
||||
|
||||
use crate::storages::directory::Directory;
|
||||
use crate::storages::physical_drive_partition::PhysicalDrivePartition;
|
||||
use crate::{devices, storages::directory::Directory};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use clap::ValueEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{ffi, fmt, fs, io, path::Path};
|
||||
use std::path::{self};
|
||||
use std::{collections::HashMap, ffi, fmt, fs, io, path::Path};
|
||||
|
||||
/// YAML file to store known storages..
|
||||
pub const STORAGESFILE: &str = "storages.yml";
|
||||
|
@ -31,6 +32,7 @@ pub enum Storage {
|
|||
}
|
||||
|
||||
impl Storage {
|
||||
/// Add or update alias of `disk` for current device.
|
||||
pub fn add_alias(
|
||||
&mut self,
|
||||
disk: &sysinfo::Disk,
|
||||
|
@ -50,6 +52,17 @@ impl StorageExt for Storage {
|
|||
Self::SubDirectory(s) => s.name(),
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -64,14 +77,20 @@ impl fmt::Display for Storage {
|
|||
/// Trait to manipulate all `Storage`s (Enums).
|
||||
pub trait StorageExt {
|
||||
fn name(&self) -> &String;
|
||||
// fn mount_path(
|
||||
// &self,
|
||||
// &device: &devices::Device,
|
||||
// &storages: &HashMap<String, Storage>,
|
||||
// ) -> Result<&path::PathBuf>;
|
||||
}
|
||||
|
||||
pub mod directory;
|
||||
pub mod local_info;
|
||||
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<Vec<Storage>> {
|
||||
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(
|
||||
|
@ -87,17 +106,17 @@ pub fn get_storages(config_dir: &Path) -> Result<Vec<Storage>> {
|
|||
trace!("{} found: {:?}", STORAGESFILE, storages_file);
|
||||
let f = fs::File::open(config_dir.join(STORAGESFILE))?;
|
||||
let reader = io::BufReader::new(f);
|
||||
let yaml: Vec<Storage> =
|
||||
let yaml: HashMap<String, Storage> =
|
||||
serde_yaml::from_reader(reader).context("Failed to read devices.yml")?;
|
||||
Ok(yaml)
|
||||
} else {
|
||||
trace!("No {} found", STORAGESFILE);
|
||||
Ok(vec![])
|
||||
Ok(HashMap::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write `storages` to yaml file in `config_dir`.
|
||||
pub fn write_storages(config_dir: &Path, storages: Vec<Storage>) -> Result<()> {
|
||||
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))
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
//! Manipulate subdirectories of other storages, including directories.
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{path, fmt::{self, write}, rc::Rc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::{self, write},
|
||||
path,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::devices;
|
||||
|
||||
use super::{Storage, StorageExt};
|
||||
|
||||
|
@ -9,7 +17,7 @@ use super::{Storage, StorageExt};
|
|||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Directory {
|
||||
name: String,
|
||||
parent: Box<Storage>,
|
||||
parent: String,
|
||||
relative_path: path::PathBuf,
|
||||
notes: String,
|
||||
}
|
||||
|
@ -21,18 +29,48 @@ impl Directory {
|
|||
/// - `notes`: supplimental notes.
|
||||
pub fn new(
|
||||
name: String,
|
||||
parent: Rc<Storage>, // todo implement serialize
|
||||
parent: String, // todo implement serialize
|
||||
relative_path: path::PathBuf,
|
||||
notes: String,
|
||||
) -> Directory {
|
||||
Directory {name, parent, relative_path, notes}
|
||||
Directory {
|
||||
name,
|
||||
parent,
|
||||
relative_path,
|
||||
notes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get parent `&Storage` of directory.
|
||||
fn parent<'a>(&'a self, storages: &'a HashMap<String, Storage>) -> Result<&Storage> {
|
||||
let parent = &self.parent;
|
||||
storages.get(&self.parent.clone()).context(format!(
|
||||
"No parent {} exists for directory {}",
|
||||
parent,
|
||||
self.name()
|
||||
))
|
||||
}
|
||||
|
||||
// /// Resolve mount path of directory with current device.
|
||||
// fn mount_path(
|
||||
// &self,
|
||||
// &device: &devices::Device,
|
||||
// &storages: &HashMap<String, Storage>,
|
||||
// ) -> Result<path::PathBuf> {
|
||||
// let parent = self.parent(&storages)?;
|
||||
// let parent_mount_path = parent.mount_path(&device, &storages)?;
|
||||
// Ok(parent_mount_path.join(self.relative_path.clone()))
|
||||
// }
|
||||
}
|
||||
|
||||
impl StorageExt for Directory {
|
||||
fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -46,4 +84,4 @@ impl fmt::Display for Directory {
|
|||
notes = self.notes,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
18
src/storages/local_info.rs
Normal file
18
src/storages/local_info.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LocalInfo {
|
||||
alias: String,
|
||||
mount_path: path::PathBuf,
|
||||
}
|
||||
|
||||
impl LocalInfo {
|
||||
pub fn new(alias: String, mount_path: path::PathBuf) -> LocalInfo {
|
||||
LocalInfo { alias, mount_path }
|
||||
}
|
||||
|
||||
pub fn mount_path(&self) -> &path::PathBuf {
|
||||
&self.mount_path
|
||||
}
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
//! Manipulate partition of physical drive (both removable and unremovable).
|
||||
|
||||
use crate::{devices::Device, get_device};
|
||||
use crate::devices;
|
||||
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::{
|
||||
collections::{hash_map::RandomState, HashMap},
|
||||
fmt,
|
||||
};
|
||||
use sysinfo::{Disk, DiskExt, SystemExt};
|
||||
|
||||
use super::local_info::LocalInfo;
|
||||
|
||||
/// Partitoin of physical (on-premises) drive.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct PhysicalDrivePartition {
|
||||
|
@ -20,7 +24,13 @@ pub struct PhysicalDrivePartition {
|
|||
capacity: u64,
|
||||
fs: String,
|
||||
is_removable: bool,
|
||||
system_names: HashMap<String, String, RandomState>,
|
||||
// system_names: HashMap<String, String>,
|
||||
local_info: HashMap<String, LocalInfo>,
|
||||
}
|
||||
|
||||
pub struct PhysicalDrivePartitionLocal {
|
||||
alias: String,
|
||||
mount_path: path::PathBuf,
|
||||
}
|
||||
|
||||
impl PhysicalDrivePartition {
|
||||
|
@ -38,30 +48,33 @@ impl PhysicalDrivePartition {
|
|||
let fs = disk.file_system();
|
||||
trace!("fs: {:?}", fs);
|
||||
let fs = std::str::from_utf8(fs)?;
|
||||
let local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf());
|
||||
Ok(PhysicalDrivePartition {
|
||||
name: name,
|
||||
kind: format!("{:?}", disk.kind()),
|
||||
capacity: disk.total_space(),
|
||||
fs: fs.to_string(),
|
||||
is_removable: disk.is_removable(),
|
||||
system_names: HashMap::from([(device.name(), alias)]),
|
||||
// system_names: HashMap::from([(device.name(), alias)]),
|
||||
local_info: HashMap::from([(device.name(), local_info)]),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_alias(
|
||||
&mut self,
|
||||
disk: &sysinfo::Disk,
|
||||
config_dir: &std::path::PathBuf
|
||||
config_dir: &std::path::PathBuf,
|
||||
) -> Result<()> {
|
||||
let device = 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 aliases = &mut self.system_names;
|
||||
let new_local_info = LocalInfo::new(alias, disk.mount_point().to_path_buf());
|
||||
let aliases = &mut self.local_info;
|
||||
trace!("aliases: {:?}", aliases);
|
||||
match aliases.insert(device.name(), alias) {
|
||||
Some(v) => println!("Value updated. old val is: {}", v),
|
||||
match aliases.insert(device.name(), new_local_info) {
|
||||
Some(v) => println!("Value updated. old val is: {:?}", v),
|
||||
None => println!("inserted new val"),
|
||||
};
|
||||
trace!("aliases: {:?}", aliases);
|
||||
|
@ -73,6 +86,14 @@ impl StorageExt for PhysicalDrivePartition {
|
|||
fn name(&self) -> &String {
|
||||
&self.name
|
||||
}
|
||||
|
||||
// fn mount_path(&self, &device: &devices::Device, &storages: &HashMap<String, Storage>) -> Result<&path::PathBuf> {
|
||||
// Ok(&self
|
||||
// .local_info
|
||||
// .get(&device.name())
|
||||
// .context(format!("LocalInfo for storage: {} not found", &self.name()))?
|
||||
// .mount_path())
|
||||
// }
|
||||
}
|
||||
|
||||
impl fmt::Display for PhysicalDrivePartition {
|
||||
|
@ -91,12 +112,11 @@ impl fmt::Display for PhysicalDrivePartition {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Interactively select physical storage from available disks in sysinfo.
|
||||
pub fn select_physical_storage(
|
||||
device: Device,
|
||||
storages: &Vec<Storage>,
|
||||
) -> Result<PhysicalDrivePartition> {
|
||||
storages: &HashMap<String, Storage>,
|
||||
) -> Result<(String, PhysicalDrivePartition)> {
|
||||
trace!("select_physical_storage");
|
||||
// get disk info fron sysinfo
|
||||
let mut sys_disks = sysinfo::System::new_all();
|
||||
|
@ -111,13 +131,14 @@ pub fn select_physical_storage(
|
|||
trace!("{}", disk_name);
|
||||
loop {
|
||||
disk_name = Text::new("Name for the disk:").prompt()?;
|
||||
if storages.iter().all(|s| s.name() != &disk_name) {
|
||||
if storages.iter().all(|(k, v)| k != &disk_name) {
|
||||
break;
|
||||
}
|
||||
println!("The name {} is already used.", disk_name);
|
||||
}
|
||||
trace!("selected name: {}", disk_name);
|
||||
PhysicalDrivePartition::try_from_sysinfo_disk(&disk, disk_name, device)
|
||||
let storage = PhysicalDrivePartition::try_from_sysinfo_disk(&disk, disk_name.clone(), device)?;
|
||||
Ok((disk_name, storage))
|
||||
}
|
||||
|
||||
pub fn select_sysinfo_disk(sysinfo: &sysinfo::System) -> Result<&Disk> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue