mirror of
https://github.com/qwjyh/xdbm
synced 2025-06-27 18:29:24 +09:00
add manual type for physical drive add
This commit is contained in:
parent
bf1a5e23fe
commit
017e34d392
4 changed files with 225 additions and 23 deletions
116
src/inquire_filepath_completer.rs
Normal file
116
src/inquire_filepath_completer.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use std::io::ErrorKind;
|
||||
use inquire::{autocompletion::{Autocomplete, Replacement}, CustomUserError};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct FilePathCompleter {
|
||||
input: String,
|
||||
paths: Vec<String>,
|
||||
lcp: String,
|
||||
}
|
||||
|
||||
impl FilePathCompleter {
|
||||
fn update_input(&mut self, input: &str) -> Result<(), CustomUserError> {
|
||||
if input == self.input {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.input = input.to_owned();
|
||||
self.paths.clear();
|
||||
|
||||
let input_path = std::path::PathBuf::from(input);
|
||||
|
||||
let fallback_parent = input_path
|
||||
.parent()
|
||||
.map(|p| {
|
||||
if p.to_string_lossy() == "" {
|
||||
std::path::PathBuf::from(".")
|
||||
} else {
|
||||
p.to_owned()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| std::path::PathBuf::from("."));
|
||||
|
||||
let scan_dir = if input.ends_with('/') {
|
||||
input_path
|
||||
} else {
|
||||
fallback_parent.clone()
|
||||
};
|
||||
|
||||
let entries = match std::fs::read_dir(scan_dir) {
|
||||
Ok(read_dir) => Ok(read_dir),
|
||||
Err(err) if err.kind() == ErrorKind::NotFound => std::fs::read_dir(fallback_parent),
|
||||
Err(err) => Err(err),
|
||||
}?
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let mut idx = 0;
|
||||
let limit = 15;
|
||||
|
||||
while idx < entries.len() && self.paths.len() < limit {
|
||||
let entry = entries.get(idx).unwrap();
|
||||
|
||||
let path = entry.path();
|
||||
let path_str = if path.is_dir() {
|
||||
format!("{}/", path.to_string_lossy())
|
||||
} else {
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
if path_str.starts_with(&self.input) && path_str.len() != self.input.len() {
|
||||
self.paths.push(path_str);
|
||||
}
|
||||
|
||||
idx = idx.saturating_add(1);
|
||||
}
|
||||
|
||||
self.lcp = self.longest_common_prefix();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn longest_common_prefix(&self) -> String {
|
||||
let mut ret: String = String::new();
|
||||
|
||||
let mut sorted = self.paths.clone();
|
||||
sorted.sort();
|
||||
if sorted.is_empty() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
let mut first_word = sorted.first().unwrap().chars();
|
||||
let mut last_word = sorted.last().unwrap().chars();
|
||||
|
||||
loop {
|
||||
match (first_word.next(), last_word.next()) {
|
||||
(Some(c1), Some(c2)) if c1 == c2 => {
|
||||
ret.push(c1);
|
||||
}
|
||||
_ => return ret,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Autocomplete for FilePathCompleter {
|
||||
fn get_suggestions(&mut self, input: &str) -> Result<Vec<String>, CustomUserError> {
|
||||
self.update_input(input)?;
|
||||
|
||||
Ok(self.paths.clone())
|
||||
}
|
||||
|
||||
fn get_completion(
|
||||
&mut self,
|
||||
input: &str,
|
||||
highlighted_suggestion: Option<String>,
|
||||
) -> Result<Replacement, CustomUserError> {
|
||||
self.update_input(input)?;
|
||||
|
||||
Ok(match highlighted_suggestion {
|
||||
Some(suggestion) => Replacement::Some(suggestion),
|
||||
None => match self.lcp.is_empty() {
|
||||
true => Replacement::None,
|
||||
false => Replacement::Some(self.lcp.clone()),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
72
src/main.rs
72
src/main.rs
|
@ -16,10 +16,11 @@ use clap::error::ErrorKind;
|
|||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use clap_verbosity_flag::Verbosity;
|
||||
use git2::{Commit, Oid, Repository};
|
||||
use inquire::{min_length, Confirm, CustomType, Select};
|
||||
use inquire::{validator::Validation, Text};
|
||||
use serde_yaml;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{self, PathBuf};
|
||||
use std::{env, io::BufReader, path::Path};
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
|
@ -29,6 +30,7 @@ use std::{fmt::Debug, fs::File};
|
|||
use std::{fs, io::prelude::*};
|
||||
use sysinfo::{Disk, DiskExt, SystemExt};
|
||||
|
||||
use crate::inquire_filepath_completer::FilePathCompleter;
|
||||
use crate::storages::{
|
||||
directory::Directory, get_storages, local_info, online_storage, physical_drive_partition::*,
|
||||
write_storages, Storage, StorageExt, StorageType, STORAGESFILE,
|
||||
|
@ -62,6 +64,9 @@ enum Commands {
|
|||
|
||||
/// Sync with git repo.
|
||||
Sync {},
|
||||
|
||||
/// Check config files.
|
||||
Check {},
|
||||
}
|
||||
|
||||
#[derive(clap::Args)]
|
||||
|
@ -90,11 +95,11 @@ enum StorageCommands {
|
|||
}
|
||||
|
||||
mod devices;
|
||||
mod inquire_filepath_completer;
|
||||
mod storages;
|
||||
|
||||
struct BackupLog {}
|
||||
|
||||
#[feature(absolute_path)]
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
env_logger::Builder::new()
|
||||
|
@ -200,8 +205,66 @@ fn main() -> Result<()> {
|
|||
let device = get_device(&config_dir)?;
|
||||
let (key, storage) = match storage_type {
|
||||
StorageType::Physical => {
|
||||
// select storage
|
||||
let (key, storage) = select_physical_storage(device, &storages)?;
|
||||
let use_sysinfo = {
|
||||
let options = vec![
|
||||
"Fetch disk information automatically.",
|
||||
"Type disk information manually.",
|
||||
];
|
||||
let ans = Select::new("Do you fetch disk information automatically? (it may take a few minutes)", options)
|
||||
.prompt().context("Failed to get response. Please try again.")?;
|
||||
match ans {
|
||||
"Fetch disk information automatically." => true,
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
let (key, storage) = if use_sysinfo {
|
||||
// select storage
|
||||
select_physical_storage(device, &storages)?
|
||||
} else {
|
||||
let mut name = String::new();
|
||||
loop {
|
||||
name = Text::new("Name for the storage:")
|
||||
.with_validator(min_length!(0, "At least 1 character"))
|
||||
.prompt()
|
||||
.context("Failed to get Name")?;
|
||||
if storages.iter().all(|(k, _v)| k != &name) {
|
||||
break;
|
||||
}
|
||||
println!("The name {} is already used.", name);
|
||||
}
|
||||
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 mount_path: path::PathBuf = PathBuf::from(
|
||||
Text::new("mount path:")
|
||||
.with_autocomplete(FilePathCompleter::default())
|
||||
.prompt()?,
|
||||
);
|
||||
let local_info =
|
||||
local_info::LocalInfo::new("".to_string(), mount_path);
|
||||
(
|
||||
name.clone(),
|
||||
PhysicalDrivePartition::new(
|
||||
name,
|
||||
kind,
|
||||
capacity,
|
||||
fs,
|
||||
is_removable,
|
||||
local_info,
|
||||
&device,
|
||||
),
|
||||
)
|
||||
};
|
||||
println!("storage: {}: {:?}", key, storage);
|
||||
(key, Storage::PhysicalStorage(storage))
|
||||
}
|
||||
|
@ -312,6 +375,7 @@ fn main() -> Result<()> {
|
|||
Commands::Sync {} => {
|
||||
unimplemented!("Sync is not implemented")
|
||||
}
|
||||
Commands::Check {} => todo!(),
|
||||
}
|
||||
full_status(&Repository::open(&config_dir)?)?;
|
||||
Ok(())
|
||||
|
|
|
@ -14,7 +14,7 @@ use std::{
|
|||
};
|
||||
use sysinfo::{Disk, DiskExt, SystemExt};
|
||||
|
||||
use super::local_info::LocalInfo;
|
||||
use super::local_info::{self, LocalInfo};
|
||||
|
||||
/// Partitoin of physical (on-premises) drive.
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
@ -29,6 +29,25 @@ pub struct PhysicalDrivePartition {
|
|||
}
|
||||
|
||||
impl PhysicalDrivePartition {
|
||||
pub fn new(
|
||||
name: String,
|
||||
kind: String,
|
||||
capacity: u64,
|
||||
fs: String,
|
||||
is_removable: bool,
|
||||
local_info: LocalInfo,
|
||||
device: &Device
|
||||
) -> PhysicalDrivePartition {
|
||||
PhysicalDrivePartition {
|
||||
name,
|
||||
kind,
|
||||
capacity,
|
||||
fs,
|
||||
is_removable,
|
||||
local_info: HashMap::from([(device.name(), local_info)]),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get Physical drive info from sysinfo.
|
||||
pub fn try_from_sysinfo_disk(
|
||||
disk: &sysinfo::Disk,
|
||||
|
@ -122,8 +141,11 @@ pub fn select_physical_storage(
|
|||
) -> Result<(String, PhysicalDrivePartition)> {
|
||||
trace!("select_physical_storage");
|
||||
// get disk info fron sysinfo
|
||||
let mut sys_disks = sysinfo::System::new_all();
|
||||
sys_disks.refresh_disks();
|
||||
let mut sys_disks =
|
||||
sysinfo::System::new_with_specifics(sysinfo::RefreshKind::new().with_disks_list());
|
||||
trace!("refresh");
|
||||
// sys_disks.refresh_disks_list();
|
||||
// sys_disks.refresh_disks();
|
||||
trace!("Available disks");
|
||||
for disk in sys_disks.disks() {
|
||||
trace!("{:?}", disk)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue