From 017e34d392926fbeed4327ff00bbca2f382d0408 Mon Sep 17 00:00:00 2001 From: qwjyh Date: Thu, 7 Dec 2023 04:23:24 +0900 Subject: [PATCH] add manual type for physical drive add --- Cargo.lock | 32 +++---- src/inquire_filepath_completer.rs | 116 +++++++++++++++++++++++ src/main.rs | 72 +++++++++++++- src/storages/physical_drive_partition.rs | 28 +++++- 4 files changed, 225 insertions(+), 23 deletions(-) create mode 100644 src/inquire_filepath_completer.rs diff --git a/Cargo.lock b/Cargo.lock index d3c3022..8d95a43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,30 +33,30 @@ checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -111,9 +111,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.10" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" dependencies = [ "clap_builder", "clap_derive", @@ -131,9 +131,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" dependencies = [ "anstream", "anstyle", @@ -514,9 +514,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "log", @@ -556,9 +556,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.96" +version = "0.9.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b" dependencies = [ "cc", "libc", diff --git a/src/inquire_filepath_completer.rs b/src/inquire_filepath_completer.rs new file mode 100644 index 0000000..36ee53e --- /dev/null +++ b/src/inquire_filepath_completer.rs @@ -0,0 +1,116 @@ +use std::io::ErrorKind; +use inquire::{autocompletion::{Autocomplete, Replacement}, CustomUserError}; + +#[derive(Clone, Default)] +pub struct FilePathCompleter { + input: String, + paths: Vec, + 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::, _>>()?; + + 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, CustomUserError> { + self.update_input(input)?; + + Ok(self.paths.clone()) + } + + fn get_completion( + &mut self, + input: &str, + highlighted_suggestion: Option, + ) -> Result { + 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()), + }, + }) + } +} diff --git a/src/main.rs b/src/main.rs index b868d2a..447ff1e 100644 --- a/src/main.rs +++ b/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::::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(()) diff --git a/src/storages/physical_drive_partition.rs b/src/storages/physical_drive_partition.rs index 7c8d4fb..bd89fce 100644 --- a/src/storages/physical_drive_partition.rs +++ b/src/storages/physical_drive_partition.rs @@ -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)