diff --git a/src/cmd_init.rs b/src/cmd_init.rs new file mode 100644 index 0000000..f68be03 --- /dev/null +++ b/src/cmd_init.rs @@ -0,0 +1,85 @@ +//! Init subcommand. +//! Initialize xdbm for the device. + +use crate::{ + add_and_commit, full_status, get_devices, set_device_name, write_devices, Device, DEVICESFILE, +}; +use anyhow::{anyhow, Context, Result}; +use git2::Repository; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::{self, Path}; + +pub(crate) fn cmd_init(repo_url: Option, config_dir: &path::PathBuf) -> Result<()> { + let is_first_device: bool; + // get repo or initialize it + let repo = match repo_url { + Some(repo_url) => { + trace!("repo: {}", repo_url); + let repo = Repository::clone(&repo_url, &config_dir)?; + is_first_device = false; + repo + } + None => { + trace!("No repo provided"); + println!("Initializing for the first device..."); + + // create repository + let repo = Repository::init(&config_dir)?; + + // set up gitignore + { + let f = File::create(&config_dir.join(".gitignore"))?; + { + let mut buf = BufWriter::new(f); + buf.write("devname".as_bytes())?; + } + add_and_commit(&repo, Path::new(".gitignore"), "Add devname to gitignore.")?; + full_status(&repo)?; + } + is_first_device = true; + repo + } + }; + full_status(&repo)?; + + // set device name + let device = set_device_name()?; + + // save devname + let devname_path = &config_dir.join("devname"); + { + 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(); + }; + full_status(&repo)?; + + // Add new device to devices.yml + { + let mut devices: Vec = if is_first_device { + vec![] + } 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.")); + } + devices.push(device.clone()); + trace!("Devices: {:?}", devices); + write_devices(&config_dir, devices)?; + } + full_status(&repo)?; + + // commit + add_and_commit( + &repo, + &Path::new(DEVICESFILE), + &format!("Add new devname: {}", &device.name()), + )?; + println!("Device added"); + full_status(&repo)?; + Ok(()) +} diff --git a/src/cmd_storage.rs b/src/cmd_storage.rs new file mode 100644 index 0000000..db1ce84 --- /dev/null +++ b/src/cmd_storage.rs @@ -0,0 +1,252 @@ +//! Storage subcommands. + +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +use anyhow::{anyhow, Context, Result}; +use clap::{error::ErrorKind, CommandFactory}; +use git2::Repository; +use inquire::{min_length, Confirm, CustomType, Select, Text}; + +use crate::{ + add_and_commit, ask_unique_name, + cmd_args::Cli, + get_device, + inquire_filepath_completer::FilePathCompleter, + storages::{ + self, directory, get_storages, local_info, physical_drive_partition, Storage, StorageExt, + StorageType, + }, +}; + +pub(crate) fn cmd_storage_add( + storage_type: storages::StorageType, + path: Option, + repo: Repository, + config_dir: &PathBuf, +) -> Result<()> { + trace!("Storage Add {:?}, {:?}", storage_type, path); + // Get storages + // let mut storages: Vec = get_storages(&config_dir)?; + let mut storages: HashMap = get_storages(&config_dir)?; + trace!("found storages: {:?}", storages); + + let device = get_device(&config_dir)?; + let (key, storage) = match storage_type { + StorageType::Physical => { + 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 + physical_drive_partition::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: 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(), + physical_drive_partition::PhysicalDrivePartition::new( + name, + kind, + capacity, + fs, + is_removable, + local_info, + &device, + ), + ) + }; + println!("storage: {}: {:?}", key, storage); + (key, Storage::PhysicalStorage(storage)) + } + StorageType::SubDirectory => { + 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 mut cmd = Cli::command(); + cmd.error( + ErrorKind::MissingRequiredArgument, + " 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_name = ask_unique_name(&storages, "sub-directory".to_string())?; + let notes = Text::new("Notes for this sub-directory:").prompt()?; + let storage = directory::Directory::try_from_device_path( + key_name.clone(), + path, + notes, + &device, + &storages, + )?; + (key_name, Storage::SubDirectory(storage)) + } + StorageType::Online => { + let path = path.unwrap_or_else(|| { + let mut cmd = Cli::command(); + cmd.error( + ErrorKind::MissingRequiredArgument, + " is required with sub-directory", + ) + .exit(); + }); + 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 provider = Text::new("Provider:") + .prompt() + .context("Failed to get provider")?; + let capacity: u64 = CustomType::::new("Capacity (byte):") + .with_error_message("Please type number.") + .prompt() + .context("Failed to get capacity.")?; + let alias = Text::new("Alias:") + .prompt() + .context("Failed to get provider")?; + let storage = storages::online_storage::OnlineStorage::new( + name.clone(), + provider, + capacity, + alias, + path, + &device, + ); + (name, Storage::Online(storage)) + } + }; + + // add to storages + storages.insert(key.clone(), storage); + trace!("updated storages: {:?}", storages); + + // write to file + storages::write_storages(&config_dir, storages)?; + + // commit + add_and_commit( + &repo, + &Path::new(storages::STORAGESFILE), + &format!("Add new storage(physical drive): {}", key), + )?; + + println!("Added new storage."); + trace!("Finished adding storage"); + Ok(()) +} + +pub(crate) fn cmd_storage_list(config_dir: &PathBuf) -> Result<()> { + // Get storages + let storages: HashMap = get_storages(&config_dir)?; + trace!("found storages: {:?}", storages); + let device = get_device(&config_dir)?; + for (k, storage) in &storages { + println!("{}: {}", k, storage); + println!(" {}", storage.mount_path(&device, &storages)?.display()); + // println!("{}: {}", storage.shorttypename(), storage.name()); // TODO + } + Ok(()) +} + +pub(crate) fn cmd_storage_bind( + storage_name: String, + new_alias: String, + mount_point: PathBuf, + repo: Repository, + config_dir: &PathBuf, +) -> Result<()> { + let device = get_device(&config_dir)?; + // get storages + let mut storages: HashMap = get_storages(&config_dir)?; + let commit_comment = { + // find matching storage + let storage = &mut storages + .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); + + storages::write_storages(&config_dir, storages)?; + // 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(()) +} diff --git a/src/main.rs b/src/main.rs index ca61cc4..75a48d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ //! # Main types -//! * [Device]: represents PC. +//! * [Device]: represents PC. module [devices] //! * [Storage]: all storages. module [storages] -//! * [PhysicalDrivePartition]: partition on a physical disk. [storages::physical_drive_partition] -//! * [Directory]: sub-directory of other storages. [storages::directory] +//! * [physical_drive_partition::PhysicalDrivePartition]: partition on a physical disk. module [storages::physical_drive_partition] +//! * [directory::Directory]: sub-directory of other storages. module [storages::directory] +//! * [online_storage::OnlineStorage]: online storage like Google Drive. module [storages::online_storage] //! * [storages::local_info::LocalInfo]: stores [Device] specific common data for [Storage]s. //! @@ -19,23 +20,21 @@ use inquire::{min_length, Confirm, CustomType, Select}; use inquire::{validator::Validation, Text}; use serde_yaml; use std::collections::HashMap; -use std::io::{self, BufWriter}; +use std::path::Path; use std::path::{self, PathBuf}; -use std::{env, io::BufReader, path::Path}; -use std::{fmt::Debug, fs::File}; -use std::{fs, io::prelude::*}; -use sysinfo::{Disk, DiskExt, SystemExt}; -use crate::cmd_args::{Cli, Commands, StorageArgs, StorageCommands}; -use crate::inquire_filepath_completer::FilePathCompleter; -use crate::storages::online_storage::OnlineStorage; +use crate::cmd_args::{Cli, Commands, StorageCommands}; +use crate::cmd_storage; +use crate::storages::online_storage; use crate::storages::{ - directory::Directory, get_storages, local_info, online_storage, physical_drive_partition::*, - write_storages, Storage, StorageExt, StorageType, STORAGESFILE, + directory, get_storages, local_info, physical_drive_partition, write_storages, Storage, + StorageExt, StorageType, STORAGESFILE, }; use devices::{Device, DEVICESFILE, *}; mod cmd_args; +mod cmd_init; +mod cmd_storage; mod devices; mod inquire_filepath_completer; mod storages; @@ -55,82 +54,7 @@ fn main() -> Result<()> { trace!("Config dir: {:?}", config_dir); match cli.command { - Commands::Init { repo_url } => { - let is_first_device: bool; - // get repo or initialize it - let repo = match repo_url { - Some(repo_url) => { - trace!("repo: {}", repo_url); - let repo = Repository::clone(&repo_url, &config_dir)?; - is_first_device = false; - repo - } - None => { - trace!("No repo provided"); - println!("Initializing for the first device..."); - - // create repository - let repo = Repository::init(&config_dir)?; - - // set up gitignore - { - let f = File::create(&config_dir.join(".gitignore"))?; - { - let mut buf = BufWriter::new(f); - buf.write("devname".as_bytes())?; - } - add_and_commit( - &repo, - Path::new(".gitignore"), - "Add devname to gitignore.", - )?; - full_status(&repo)?; - } - is_first_device = true; - repo - } - }; - full_status(&repo)?; - - // set device name - let device = set_device_name()?; - - // save devname - let devname_path = &config_dir.join("devname"); - { - 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(); - }; - full_status(&repo)?; - - // Add new device to devices.yml - { - let mut devices: Vec = if is_first_device { - vec![] - } 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.")); - } - devices.push(device.clone()); - trace!("Devices: {:?}", devices); - write_devices(&config_dir, devices)?; - } - full_status(&repo)?; - - // commit - add_and_commit( - &repo, - &Path::new(DEVICESFILE), - &format!("Add new devname: {}", &device.name()), - )?; - println!("Device added"); - full_status(&repo)?; - } + Commands::Init { repo_url } => cmd_init::cmd_init(repo_url, &config_dir)?, Commands::Storage(storage) => { let repo = Repository::open(&config_dir).context( "Repository doesn't exist. Please run init to initialize the repository.", @@ -138,217 +62,20 @@ fn main() -> Result<()> { trace!("repo state: {:?}", repo.state()); match storage.command { StorageCommands::Add { storage_type, path } => { - trace!("Storage Add {:?}, {:?}", storage_type, path); - // Get storages - // let mut storages: Vec = get_storages(&config_dir)?; - let mut storages: HashMap = get_storages(&config_dir)?; - trace!("found storages: {:?}", storages); - - let device = get_device(&config_dir)?; - let (key, storage) = match storage_type { - StorageType::Physical => { - 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)) - } - StorageType::SubDirectory => { - 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 mut cmd = Cli::command(); - cmd.error( - ErrorKind::MissingRequiredArgument, - " 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_name = ask_unique_name(&storages, "sub-directory".to_string())?; - 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)) - } - StorageType::Online => { - let path = path.unwrap_or_else(|| { - let mut cmd = Cli::command(); - cmd.error( - ErrorKind::MissingRequiredArgument, - " is required with sub-directory", - ) - .exit(); - }); - 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 provider = Text::new("Provider:") - .prompt() - .context("Failed to get provider")?; - let capacity: u64 = CustomType::::new("Capacity (byte):") - .with_error_message("Please type number.") - .prompt() - .context("Failed to get capacity.")?; - let alias = Text::new("Alias:") - .prompt() - .context("Failed to get provider")?; - let storage = OnlineStorage::new( - name.clone(), - provider, - capacity, - alias, - path, - &device, - ); - (name, Storage::Online(storage)) - } - }; - - // 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: HashMap = get_storages(&config_dir)?; - trace!("found storages: {:?}", storages); - let device = get_device(&config_dir)?; - for (k, storage) in &storages { - println!("{}: {}", k, storage); - println!(" {}", storage.mount_path(&device, &storages)?.display()); - // println!("{}: {}", storage.shorttypename(), storage.name()); // TODO - } + cmd_storage::cmd_storage_add(storage_type, path, repo, &config_dir)? } + StorageCommands::List {} => cmd_storage::cmd_storage_list(&config_dir)?, StorageCommands::Bind { storage: storage_name, alias: new_alias, path: mount_point, - } => { - let device = get_device(&config_dir)?; - // get storages - let mut storages: HashMap = get_storages(&config_dir)?; - let commit_comment = { - // find matching storage - let storage = &mut storages - .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); - - write_storages(&config_dir, storages)?; - // commit - add_and_commit( - &repo, - &Path::new(STORAGESFILE), - &format!( - "Bound new storage name to physical drive ({})", - commit_comment - ), - )?; - println!( - "Bound new storage name to physical drive ({})", - commit_comment - ); - } + } => cmd_storage::cmd_storage_bind( + storage_name, + new_alias, + mount_point, + repo, + &config_dir, + )?, } } Commands::Path {} => {