2024-02-28 03:17:32 +09:00
|
|
|
//! Storage subcommands.
|
|
|
|
|
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
2024-03-03 06:11:25 +09:00
|
|
|
io::{self, Write},
|
2024-03-10 13:00:28 +09:00
|
|
|
path::{Path, PathBuf},
|
|
|
|
string,
|
2024-02-28 03:17:32 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
use anyhow::{anyhow, Context, Result};
|
2024-03-03 06:11:25 +09:00
|
|
|
use byte_unit::Byte;
|
2024-02-28 03:17:32 +09:00
|
|
|
use clap::{error::ErrorKind, CommandFactory};
|
|
|
|
use git2::Repository;
|
|
|
|
use inquire::{min_length, Confirm, CustomType, Select, Text};
|
2024-03-03 06:11:25 +09:00
|
|
|
use unicode_width::{self, UnicodeWidthStr};
|
2024-02-28 03:17:32 +09:00
|
|
|
|
|
|
|
use crate::{
|
2024-03-10 13:00:28 +09:00
|
|
|
add_and_commit,
|
2024-03-12 16:18:24 +09:00
|
|
|
cmd_args::{Cli, StorageAddCommands},
|
2024-03-03 06:11:25 +09:00
|
|
|
devices::{self, Device},
|
2024-02-28 03:17:32 +09:00
|
|
|
inquire_filepath_completer::FilePathCompleter,
|
|
|
|
storages::{
|
2024-03-12 16:18:24 +09:00
|
|
|
self, directory, local_info,
|
|
|
|
physical_drive_partition::{self, PhysicalDrivePartition},
|
|
|
|
Storage, StorageExt, StorageType, Storages,
|
2024-02-28 03:17:32 +09:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
pub(crate) fn cmd_storage_add(
|
2024-03-12 16:18:24 +09:00
|
|
|
args: StorageAddCommands,
|
2024-02-28 03:17:32 +09:00
|
|
|
repo: Repository,
|
|
|
|
config_dir: &PathBuf,
|
|
|
|
) -> Result<()> {
|
2024-03-12 16:18:24 +09:00
|
|
|
trace!("Storage Add with args: {:?}", args);
|
2024-02-28 03:17:32 +09:00
|
|
|
// Get storages
|
2024-03-10 13:00:28 +09:00
|
|
|
let mut storages = Storages::read(&config_dir)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
trace!("found storages: {:?}", storages);
|
|
|
|
|
2024-03-03 06:11:25 +09:00
|
|
|
let device = devices::get_device(&config_dir)?;
|
2024-03-12 16:18:24 +09:00
|
|
|
let storage = match args {
|
|
|
|
StorageAddCommands::Physical { name, path } => {
|
|
|
|
if !is_unique_name(&name, &storages) {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"The name {} is already used for another storage.",
|
|
|
|
name
|
|
|
|
));
|
|
|
|
}
|
|
|
|
let use_sysinfo = path.is_none();
|
2024-03-12 00:02:32 +09:00
|
|
|
let storage = if use_sysinfo {
|
2024-03-12 16:18:24 +09:00
|
|
|
physical_drive_partition::select_physical_storage(name, device)?
|
2024-02-28 03:17:32 +09:00
|
|
|
} else {
|
2024-03-12 16:18:24 +09:00
|
|
|
manually_construct_physical_drive_partition(name, path.unwrap(), &device)?
|
2024-02-28 03:17:32 +09:00
|
|
|
};
|
2024-03-12 00:02:32 +09:00
|
|
|
println!("storage: {}: {:?}", storage.name(), storage);
|
|
|
|
Storage::PhysicalStorage(storage)
|
2024-02-28 03:17:32 +09:00
|
|
|
}
|
2024-03-12 16:18:24 +09:00
|
|
|
StorageAddCommands::Directory {
|
|
|
|
name,
|
|
|
|
path,
|
|
|
|
notes,
|
|
|
|
alias,
|
|
|
|
} => {
|
|
|
|
if !is_unique_name(&name, &storages) {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"The name {} is already used for another storage.",
|
|
|
|
name
|
|
|
|
));
|
|
|
|
}
|
2024-03-10 13:00:28 +09:00
|
|
|
if storages.list.is_empty() {
|
2024-02-28 03:17:32 +09:00
|
|
|
return Err(anyhow!(
|
2024-03-11 08:22:35 +09:00
|
|
|
"No storages found. Please add at least 1 physical/online storage first to add sub directory."
|
2024-02-28 03:17:32 +09:00
|
|
|
));
|
|
|
|
}
|
|
|
|
trace!("SubDirectory arguments: path: {:?}", path);
|
|
|
|
// Nightly feature std::path::absolute
|
|
|
|
let path = path.canonicalize()?;
|
|
|
|
trace!("canonicalized: path: {:?}", path);
|
|
|
|
|
|
|
|
let storage = directory::Directory::try_from_device_path(
|
2024-03-12 16:18:24 +09:00
|
|
|
name, path, notes, alias, &device, &storages,
|
2024-02-28 03:17:32 +09:00
|
|
|
)?;
|
2024-03-12 00:02:32 +09:00
|
|
|
Storage::SubDirectory(storage)
|
2024-02-28 03:17:32 +09:00
|
|
|
}
|
2024-03-12 16:18:24 +09:00
|
|
|
StorageAddCommands::Online {
|
|
|
|
name,
|
|
|
|
path,
|
|
|
|
provider,
|
|
|
|
capacity,
|
|
|
|
alias,
|
|
|
|
} => {
|
|
|
|
if !is_unique_name(&name, &storages) {
|
|
|
|
return Err(anyhow!(
|
|
|
|
"The name {} is already used for another storage.",
|
|
|
|
name
|
|
|
|
));
|
2024-02-28 03:17:32 +09:00
|
|
|
}
|
|
|
|
let storage = storages::online_storage::OnlineStorage::new(
|
2024-03-12 00:02:32 +09:00
|
|
|
name, provider, capacity, alias, path, &device,
|
2024-02-28 03:17:32 +09:00
|
|
|
);
|
2024-03-12 00:02:32 +09:00
|
|
|
Storage::Online(storage)
|
2024-02-28 03:17:32 +09:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// add to storages
|
2024-03-12 00:02:32 +09:00
|
|
|
let new_storage_name = storage.name().clone();
|
2024-03-12 16:18:24 +09:00
|
|
|
let new_storage_type = storage.typename().to_string();
|
2024-03-10 13:00:28 +09:00
|
|
|
storages.add(storage)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
trace!("updated storages: {:?}", storages);
|
|
|
|
|
|
|
|
// write to file
|
2024-03-10 13:00:28 +09:00
|
|
|
storages.write(&config_dir)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
|
|
|
|
// commit
|
|
|
|
add_and_commit(
|
|
|
|
&repo,
|
|
|
|
&Path::new(storages::STORAGESFILE),
|
2024-03-12 16:18:24 +09:00
|
|
|
&format!(
|
|
|
|
"Add new storage({}): {}",
|
|
|
|
new_storage_type, new_storage_name
|
|
|
|
),
|
2024-02-28 03:17:32 +09:00
|
|
|
)?;
|
|
|
|
|
|
|
|
println!("Added new storage.");
|
|
|
|
trace!("Finished adding storage");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-03-12 16:18:24 +09:00
|
|
|
fn is_unique_name(newname: &String, storages: &Storages) -> bool {
|
|
|
|
storages.list.iter().all(|(name, _)| name != newname)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn manually_construct_physical_drive_partition(
|
|
|
|
name: String,
|
|
|
|
path: PathBuf,
|
|
|
|
device: &Device,
|
|
|
|
) -> Result<PhysicalDrivePartition> {
|
|
|
|
let kind = Text::new("Kind of storage (ex. SSD):")
|
|
|
|
.prompt()
|
|
|
|
.context("Failed to get kind.")?;
|
|
|
|
let capacity: u64 = CustomType::<u64>::new("Capacity (byte):")
|
|
|
|
.with_error_message("Please type number.")
|
|
|
|
.prompt()
|
|
|
|
.context("Failed to get capacity.")?;
|
|
|
|
let fs = Text::new("filesystem:")
|
|
|
|
.prompt()
|
|
|
|
.context("Failed to get fs.")?;
|
|
|
|
let is_removable = Confirm::new("Is removable")
|
|
|
|
.prompt()
|
|
|
|
.context("Failed to get is_removable")?;
|
|
|
|
let alias = Text::new("Alias of the storage for this device")
|
|
|
|
.prompt()
|
|
|
|
.context("Failed to get alias.")?;
|
|
|
|
let local_info = local_info::LocalInfo::new(alias, path);
|
|
|
|
Ok(physical_drive_partition::PhysicalDrivePartition::new(
|
|
|
|
name,
|
|
|
|
kind,
|
|
|
|
capacity,
|
|
|
|
fs,
|
|
|
|
is_removable,
|
|
|
|
local_info,
|
|
|
|
&device,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2024-03-03 06:11:25 +09:00
|
|
|
pub(crate) fn cmd_storage_list(config_dir: &PathBuf, with_note: bool) -> Result<()> {
|
2024-02-28 03:17:32 +09:00
|
|
|
// Get storages
|
2024-03-10 13:00:28 +09:00
|
|
|
let storages = Storages::read(&config_dir)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
trace!("found storages: {:?}", storages);
|
2024-03-12 16:18:24 +09:00
|
|
|
if storages.list.is_empty() {
|
|
|
|
println!("No storages found");
|
|
|
|
return Ok(());
|
|
|
|
}
|
2024-03-03 06:11:25 +09:00
|
|
|
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,
|
2024-03-10 13:00:28 +09:00
|
|
|
storages: &Storages,
|
2024-03-03 06:11:25 +09:00
|
|
|
device: &Device,
|
|
|
|
long_display: bool,
|
|
|
|
) -> Result<()> {
|
|
|
|
let name_width = storages
|
2024-03-10 13:00:28 +09:00
|
|
|
.list
|
2024-03-03 06:11:25 +09:00
|
|
|
.iter()
|
|
|
|
.map(|(_k, v)| v.name().width())
|
|
|
|
.max()
|
|
|
|
.unwrap();
|
|
|
|
trace!("name widths: {}", name_width);
|
2024-03-10 13:00:28 +09:00
|
|
|
for (_k, storage) in &storages.list {
|
2024-03-03 06:11:25 +09:00
|
|
|
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 {
|
2024-03-10 13:00:28 +09:00
|
|
|
s.parent(&storages)?
|
2024-03-03 06:11:25 +09:00
|
|
|
.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 {
|
2024-03-10 13:00:28 +09:00
|
|
|
Storage::PhysicalStorage(s) => s.kind(),
|
|
|
|
Storage::SubDirectory(s) => &s.notes,
|
|
|
|
Storage::Online(s) => &s.provider,
|
2024-03-03 06:11:25 +09:00
|
|
|
};
|
2024-03-10 13:00:28 +09:00
|
|
|
writeln!(writer, " {}", note)?;
|
2024-03-03 06:11:25 +09:00
|
|
|
}
|
2024-02-28 03:17:32 +09:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn cmd_storage_bind(
|
|
|
|
storage_name: String,
|
|
|
|
new_alias: String,
|
|
|
|
mount_point: PathBuf,
|
|
|
|
repo: Repository,
|
|
|
|
config_dir: &PathBuf,
|
|
|
|
) -> Result<()> {
|
2024-03-03 06:11:25 +09:00
|
|
|
let device = devices::get_device(&config_dir)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
// get storages
|
2024-03-10 13:00:28 +09:00
|
|
|
let mut storages = Storages::read(&config_dir)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
let commit_comment = {
|
|
|
|
// find matching storage
|
|
|
|
let storage = &mut storages
|
2024-03-10 13:00:28 +09:00
|
|
|
.list
|
2024-02-28 03:17:32 +09:00
|
|
|
.get_mut(&storage_name)
|
|
|
|
.context(format!("No storage has name {}", storage_name))?;
|
|
|
|
let old_alias = storage
|
|
|
|
.local_info(&device)
|
|
|
|
.context(format!("Failed to get LocalInfo for {}", storage.name()))?
|
|
|
|
.alias()
|
|
|
|
.clone();
|
|
|
|
// TODO: get mount path for directory automatically?
|
|
|
|
storage.bound_on_device(new_alias, mount_point, &device)?;
|
|
|
|
// trace!("storage: {}", &storage);
|
|
|
|
format!("{} to {}", old_alias, storage.name())
|
|
|
|
};
|
|
|
|
trace!("bound new system name to the storage");
|
|
|
|
trace!("storages: {:#?}", storages);
|
|
|
|
|
2024-03-10 13:00:28 +09:00
|
|
|
storages.write(&config_dir)?;
|
2024-02-28 03:17:32 +09:00
|
|
|
// commit
|
|
|
|
add_and_commit(
|
|
|
|
&repo,
|
|
|
|
&Path::new(storages::STORAGESFILE),
|
|
|
|
&format!(
|
|
|
|
"Bound new storage name to physical drive ({})",
|
|
|
|
commit_comment
|
|
|
|
),
|
|
|
|
)?;
|
|
|
|
println!(
|
|
|
|
"Bound new storage name to physical drive ({})",
|
|
|
|
commit_comment
|
|
|
|
);
|
|
|
|
Ok(())
|
|
|
|
}
|
2024-03-10 13:00:28 +09:00
|
|
|
|
|
|
|
fn ask_unique_name(storages: &Storages, target: String) -> Result<String> {
|
|
|
|
let mut disk_name = String::new();
|
|
|
|
loop {
|
|
|
|
disk_name = Text::new(format!("Name for {}:", target).as_str()).prompt()?;
|
2024-03-11 08:22:35 +09:00
|
|
|
if storages.list.iter().all(|(k, _v)| k != &disk_name) {
|
2024-03-10 13:00:28 +09:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
println!("The name {} is already used.", disk_name);
|
|
|
|
}
|
|
|
|
Ok(disk_name)
|
|
|
|
}
|