new: storage add (git is not working)

This commit is contained in:
qwjyh 2023-08-27 17:49:18 +09:00
parent 2a1854c488
commit db09897b5a
3 changed files with 264 additions and 92 deletions

24
Cargo.lock generated
View file

@ -59,6 +59,12 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "anyhow"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@ -77,6 +83,16 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "byte-unit"
version = "4.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da78b32057b8fdfc352504708feeba7216dcd65a2c9ab02978cbd288d1279b6c"
dependencies = [
"serde",
"utf8-width",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -931,6 +947,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8-width"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"
@ -1050,6 +1072,8 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
name = "xdbm" name = "xdbm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"byte-unit",
"clap", "clap",
"clap-verbosity-flag", "clap-verbosity-flag",
"dirs", "dirs",

View file

@ -16,3 +16,5 @@ git2 = "0.17.2"
dirs = "5.0" dirs = "5.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9" serde_yaml = "0.9"
byte-unit = "4.0.19"
anyhow = "1.0"

View file

@ -1,25 +1,36 @@
//! # Main variables
//! * [Device]: represents PC.
//! * [Storage]: all storages
//! * [PhysicalDrivePartition]: partition on a physical disk.
//!
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate dirs; extern crate dirs;
use clap::{Parser, Subcommand}; use anyhow::{anyhow, Context, Result};
use byte_unit::Byte;
use clap::{Parser, Subcommand, ValueEnum};
use clap_verbosity_flag::Verbosity; use clap_verbosity_flag::Verbosity;
use git2::{Commit, IndexEntry, Oid, Repository}; use git2::{Commit, IndexEntry, Oid, Repository};
use inquire::{ use inquire::{
validator::{StringValidator, Validation}, validator::Validation,
Text, Text,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_yaml; use serde_yaml;
use std::io::prelude::*;
use std::io::{self, BufWriter};
use std::{ use std::{
collections::{hash_map::RandomState, HashMap}, collections::{hash_map::RandomState, HashMap},
fmt::Debug, fmt::Debug,
fs::{File, OpenOptions}, fs::{File, OpenOptions},
}; };
use std::{env, io::BufReader, path::Path}; use std::{env, io::BufReader, path::Path};
use std::{
ffi::OsString,
io::{self, BufWriter},
};
use std::{fs, io::prelude::*};
use sysinfo::{DiskExt, System, SystemExt}; use sysinfo::{DiskExt, System, SystemExt};
#[derive(Parser)] #[derive(Parser)]
@ -39,6 +50,7 @@ enum Commands {
repo_url: Option<String>, // url? repo_url: Option<String>, // url?
}, },
/// Manage storages.
Storage(StorageArgs), Storage(StorageArgs),
/// Print config dir. /// Print config dir.
@ -57,7 +69,10 @@ struct StorageArgs {
#[derive(Subcommand)] #[derive(Subcommand)]
enum StorageCommands { enum StorageCommands {
Add {}, Add {
#[arg(value_enum)]
storage_type: StorageType,
},
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
@ -91,6 +106,12 @@ impl Device {
const DEVICESFILE: &str = "devices.yml"; const DEVICESFILE: &str = "devices.yml";
#[derive(ValueEnum, Clone, Copy, Debug)]
enum StorageType {
Physical,
// Online,
}
/// All storage types. /// All storage types.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
enum Storage { enum Storage {
@ -103,6 +124,16 @@ enum Storage {
// }, // },
} }
impl Storage {
fn name(&self) -> &String {
match self {
Self::PhysicalStorage(s) => s.name(),
}
}
}
const STORAGESFILE: &str = "storages.yml";
/// Partitoin of physical (on-premises) drive. /// Partitoin of physical (on-premises) drive.
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
struct PhysicalDrivePartition { struct PhysicalDrivePartition {
@ -111,33 +142,31 @@ struct PhysicalDrivePartition {
capacity: u64, capacity: u64,
fs: String, fs: String,
is_removable: bool, is_removable: bool,
aliases: HashMap<String, String, RandomState>, system_names: HashMap<String, String, RandomState>,
} }
impl PhysicalDrivePartition { impl PhysicalDrivePartition {
/// Try to get Physical drive info from sysinfo. /// Try to get Physical drive info from sysinfo.
fn try_from_sysinfo_disk( fn try_from_sysinfo_disk(
disk: sysinfo::Disk, disk: &sysinfo::Disk,
name: String, name: String,
device: Device, device: Device,
) -> Result<PhysicalDrivePartition, String> { ) -> Result<PhysicalDrivePartition> {
let alias = match disk.name().to_str() { let alias = disk
Some(s) => s.to_string(), .name()
None => return Err("Failed to convert storage name to valid str.".to_string()), .to_str()
}; .context("Failed to convert storage name to valid str.")?
.to_string();
let fs = disk.file_system(); let fs = disk.file_system();
trace!("fs: {:?}", fs); trace!("fs: {:?}", fs);
let fs = match std::str::from_utf8(fs) { let fs = std::str::from_utf8(fs)?;
Ok(s) => s,
Err(e) => return Err(e.to_string()),
};
Ok(PhysicalDrivePartition { Ok(PhysicalDrivePartition {
name: name, name: name,
kind: format!("{:?}", disk.kind()), kind: format!("{:?}", disk.kind()),
capacity: disk.total_space(), capacity: disk.total_space(),
fs: fs.to_string(), fs: fs.to_string(),
is_removable: disk.is_removable(), is_removable: disk.is_removable(),
aliases: HashMap::from([(device.name, alias)]), system_names: HashMap::from([(device.name, alias)]),
}) })
} }
@ -145,12 +174,16 @@ impl PhysicalDrivePartition {
&self.name &self.name
} }
fn add_alias(self, disk: sysinfo::Disk, device: Device) -> Result<PhysicalDrivePartition, String> { fn add_alias(
self,
disk: sysinfo::Disk,
device: Device,
) -> Result<PhysicalDrivePartition, String> {
let alias = match disk.name().to_str() { let alias = match disk.name().to_str() {
Some(s) => s.to_string(), Some(s) => s.to_string(),
None => return Err("Failed to convert storage name to valid str.".to_string()), None => return Err("Failed to convert storage name to valid str.".to_string()),
}; };
let mut aliases = self.aliases; let mut aliases = self.system_names;
let _ = match aliases.insert(device.name, alias) { let _ = match aliases.insert(device.name, alias) {
Some(v) => v, Some(v) => v,
None => return Err("Failed to insert alias".to_string()), None => return Err("Failed to insert alias".to_string()),
@ -161,24 +194,21 @@ impl PhysicalDrivePartition {
capacity: self.capacity, capacity: self.capacity,
fs: self.fs, fs: self.fs,
is_removable: self.is_removable, is_removable: self.is_removable,
aliases, system_names: aliases,
}) })
} }
} }
struct BackupLog {} struct BackupLog {}
fn main() -> Result<(), String> { fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
env_logger::Builder::new() env_logger::Builder::new()
.filter_level(cli.verbose.log_level_filter()) .filter_level(cli.verbose.log_level_filter())
.init(); .init();
trace!("Start logging..."); trace!("Start logging...");
let mut config_dir = match dirs::config_local_dir() { let mut config_dir = dirs::config_local_dir().context("Failed to get config dir.")?;
Some(dir) => dir,
None => return Err("Failed to get config dir.".to_string()),
};
config_dir.push("xdbm"); config_dir.push("xdbm");
trace!("Config dir: {:?}", config_dir); trace!("Config dir: {:?}", config_dir);
@ -186,13 +216,10 @@ fn main() -> Result<(), String> {
Commands::Init { repo_url } => { Commands::Init { repo_url } => {
let is_first_device: bool; let is_first_device: bool;
// get repo or initialize it // get repo or initialize it
let repo_url = match repo_url { let repo = match repo_url {
Some(repo_url) => { Some(repo_url) => {
trace!("repo: {}", repo_url); trace!("repo: {}", repo_url);
let repo = match Repository::clone(&repo_url, &config_dir) { let repo = Repository::clone(&repo_url, &config_dir)?;
Ok(repo) => repo,
Err(e) => return Err(e.to_string()),
};
is_first_device = false; is_first_device = false;
repo repo
} }
@ -201,34 +228,20 @@ fn main() -> Result<(), String> {
println!("Initializing for the first device..."); println!("Initializing for the first device...");
// create repository // create repository
let repo = match Repository::init(&config_dir) { let repo = Repository::init(&config_dir)?;
Ok(repo) => repo,
Err(e) => return Err(e.to_string()),
};
// set up gitignore // set up gitignore
{ {
let f = match File::create(&config_dir.join(".gitignore")) { let f = File::create(&config_dir.join(".gitignore"))?;
Ok(f) => f, {
Err(e) => return Err(e.to_string()),
};
let mut buf = BufWriter::new(f); let mut buf = BufWriter::new(f);
match buf.write("devname".as_bytes()) { buf.write("devname".as_bytes())?;
Ok(_) => trace!("successfully created ignore file"), }
Err(e) => return Err(e.to_string()), add_and_commit(
};
match buf.flush() {
Ok(_) => (),
Err(e) => return Err(e.to_string()),
};
match add_and_commit(
&repo, &repo,
Path::new(".gitignore"), Path::new(".gitignore"),
"Add devname to gitignore.", "Add devname to gitignore.",
) { )?;
Ok(_) => (),
Err(e) => return Err(e.to_string()),
};
} }
is_first_device = true; is_first_device = true;
repo repo
@ -241,11 +254,8 @@ fn main() -> Result<(), String> {
// save devname // save devname
let devname_path = &config_dir.join("devname"); let devname_path = &config_dir.join("devname");
{ {
let mut f = match File::create(devname_path) { let f = File::create(devname_path).context("Failed to create devname file")?;
Ok(f) => f, let writer = BufWriter::new(f);
Err(e) => panic!("Failed to create devname file: {}", e),
};
let mut writer = BufWriter::new(f);
serde_yaml::to_writer(writer, &device.name).unwrap(); serde_yaml::to_writer(writer, &device.name).unwrap();
}; };
@ -257,7 +267,7 @@ fn main() -> Result<(), String> {
get_devices(&config_dir)? get_devices(&config_dir)?
}; };
if devices.iter().any(|x| x.name == device.name) { if devices.iter().any(|x| x.name == device.name) {
return Err("device name is already used.".to_string()); return Err(anyhow!("device name is already used."));
} }
devices.push(device.clone()); devices.push(device.clone());
trace!("Devices: {:?}", devices); trace!("Devices: {:?}", devices);
@ -265,22 +275,61 @@ fn main() -> Result<(), String> {
} }
// commit // commit
match add_and_commit( add_and_commit(
&repo_url, &repo,
&Path::new(DEVICESFILE), &Path::new(DEVICESFILE),
&format!("Add new devname: {}", &device.name), &format!("Add new devname: {}", &device.name),
) { )?;
Ok(_) => (),
Err(e) => return Err(e.to_string()),
} }
} Commands::Storage(storage) => match storage.command {
Commands::Storage(storage) => { StorageCommands::Add { storage_type } => {
match storage.command { trace!("Storage Add {:?}", storage_type);
StorageCommands::Add {} => { let repo = Repository::open(&config_dir)?;
trace!("repo state: {:?}", repo.state());
match storage_type {
StorageType::Physical => {
// Get storages
let mut storages: Vec<Storage> = if let Some(storages_file) = fs::read_dir(&config_dir)?
.filter(|f| {
f.as_ref().map_or_else(
|_e| false,
|f| {
let storagesfile: OsString = STORAGESFILE.into();
f.path().file_name() == Some(&storagesfile)
},
)
})
.next()
{
trace!("{} found: {:?}", STORAGESFILE, storages_file);
get_storages(&config_dir)?
} else {
trace!("No {} found", STORAGESFILE);
vec![]
};
trace!("found storages: {:?}", storages);
// select storage
let device = get_device(&config_dir)?;
let storage = select_physical_storage(device, &storages)?;
trace!("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))?;
unimplemented!() unimplemented!()
} }
} }
unimplemented!()
} }
},
Commands::Path {} => { Commands::Path {} => {
println!("{}", &config_dir.display()); println!("{}", &config_dir.display());
} }
@ -291,8 +340,33 @@ fn main() -> Result<(), String> {
Ok(()) Ok(())
} }
/// Get devname of the device.
fn get_devname(config_dir: &Path) -> Result<String> {
let f = File::open(config_dir.join("devname")).context("Failed to open devname file")?;
let bufreader = BufReader::new(f);
let devname = bufreader
.lines()
.next()
.context("Couldn't get devname.")??;
trace!("devname: {}", devname);
Ok(devname)
}
/// Get current device.
fn get_device(config_dir: &Path) -> Result<Device> {
let devname = get_devname(config_dir)?;
let devices = get_devices(config_dir)?;
trace!("devname: {}", devname);
trace!("devices: {:?}", devices);
devices
.into_iter()
.filter(|dev| dev.name == devname)
.next()
.context("Couldn't find Device in devices.yml")
}
/// Set device name interactively. /// Set device name interactively.
fn set_device_name() -> Result<Device, String> { fn set_device_name() -> Result<Device> {
let validator = |input: &str| { let validator = |input: &str| {
if input.chars().count() == 0 { if input.chars().count() == 0 {
Ok(Validation::Invalid("Need at least 1 character.".into())) Ok(Validation::Invalid("Need at least 1 character.".into()))
@ -312,7 +386,7 @@ fn set_device_name() -> Result<Device, String> {
} }
Err(err) => { Err(err) => {
println!("Error {}", err); println!("Error {}", err);
return Err(err.to_string()); return Err(anyhow!(err));
} }
}; };
@ -324,36 +398,105 @@ fn set_device_name() -> Result<Device, String> {
} }
/// Get `Vec<Device>` from yaml file in `config_dir`. /// Get `Vec<Device>` from yaml file in `config_dir`.
fn get_devices(config_dir: &Path) -> Result<Vec<Device>, String> { fn get_devices(config_dir: &Path) -> Result<Vec<Device>> {
trace!("get_devices"); trace!("get_devices");
let f = match File::open(config_dir.join(DEVICESFILE)) { let f = File::open(config_dir.join(DEVICESFILE))?;
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
let reader = BufReader::new(f); let reader = BufReader::new(f);
let yaml: Vec<Device> = match serde_yaml::from_reader(reader) { let yaml: Vec<Device> =
Ok(yaml) => yaml, serde_yaml::from_reader(reader).context("Failed to parse devices.yml")?;
Err(e) => return Err(e.to_string()),
};
return Ok(yaml); return Ok(yaml);
} }
/// Write `devices` to yaml file in `config_dir`. /// Write `devices` to yaml file in `config_dir`.
fn write_devices(config_dir: &Path, devices: Vec<Device>) -> Result<(), String> { fn write_devices(config_dir: &Path, devices: Vec<Device>) -> Result<()> {
trace!("write_devices"); trace!("write_devices");
let f = match OpenOptions::new() let f = OpenOptions::new()
.create(true) .create(true)
.write(true) .write(true)
.open(config_dir.join(DEVICESFILE)) .open(config_dir.join(DEVICESFILE))?;
{
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
let writer = BufWriter::new(f); let writer = BufWriter::new(f);
match serde_yaml::to_writer(writer, &devices) { serde_yaml::to_writer(writer, &devices).map_err(|e| anyhow!(e))
Ok(()) => Ok(()), }
Err(e) => Err(e.to_string()),
/// Interactively select physical storage from available disks in sysinfo.
fn select_physical_storage(device: Device, storages: &Vec<Storage>) -> Result<PhysicalDrivePartition> {
trace!("select_physical_storage");
// get disk info fron sysinfo
let mut sys_disks = sysinfo::System::new_all();
sys_disks.refresh_disks();
trace!("Available disks");
for disk in sys_disks.disks() {
trace!("{:?}", disk)
} }
let available_disks = sys_disks
.disks()
.iter()
.enumerate()
.map(|(i, disk)| {
let name = match disk.name().to_str() {
Some(s) => s,
None => "",
};
let fs: &str = std::str::from_utf8(disk.file_system()).unwrap_or("unknown");
let kind = format!("{:?}", disk.kind());
let mount_path = disk.mount_point();
let total_space = Byte::from_bytes(disk.total_space().into())
.get_appropriate_unit(true)
.to_string();
format!(
"{}: {} {} ({}, {}) {}",
i,
name,
total_space,
fs,
kind,
mount_path.display()
)
})
.collect();
// select from list
let disk = inquire::Select::new("Select drive:", available_disks).prompt()?;
let disk_num: usize = disk.split(':').next().unwrap().parse().unwrap();
trace!("disk_num: {}", disk_num);
let (_, disk) = sys_disks
.disks()
.iter()
.enumerate()
.find(|(i, _)| i == &disk_num)
.unwrap();
trace!("selected disk: {:?}", disk);
// name the disk
let mut disk_name = String::new();
trace!("{}", disk_name);
loop {
disk_name = Text::new("Name for the disk:").prompt()?;
if storages.iter().all(|s| {s.name() != &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)
}
/// Get Vec<Storage> from devices.yml([DEVICESFILE])
fn get_storages(config_dir: &Path) -> Result<Vec<Storage>> {
let f = File::open(config_dir.join(STORAGESFILE))?;
let reader = BufReader::new(f);
// for line in reader.lines() {
// trace!("{:?}", line);
// }
// unimplemented!();
let yaml: Vec<Storage> =
serde_yaml::from_reader(reader).context("Failed to read devices.yml")?;
Ok(yaml)
}
/// Write `storages` to yaml file in `config_dir`.
fn write_storages(config_dir: &Path, storages: Vec<Storage>) -> Result<()> {
let f = File::create(config_dir.join(STORAGESFILE))?;
let writer = BufWriter::new(f);
serde_yaml::to_writer(writer, &storages).map_err(|e| anyhow!(e))
} }
fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> { fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> {
@ -372,6 +515,7 @@ fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> {
/// Add file and commit /// Add file and commit
fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid, git2::Error> { fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid, git2::Error> {
trace!("repo state: {:?}", repo.state());
let mut index = repo.index()?; let mut index = repo.index()?;
index.add_path(path)?; index.add_path(path)?;
let oid = index.write_tree()?; let oid = index.write_tree()?;
@ -383,7 +527,7 @@ fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid,
trace!("git signature: {}", signature); trace!("git signature: {}", signature);
let parent_commit = find_last_commit(&repo)?; let parent_commit = find_last_commit(&repo)?;
let tree = repo.find_tree(oid)?; let tree = repo.find_tree(oid)?;
match parent_commit { let result = match parent_commit {
Some(parent_commit) => repo.commit( Some(parent_commit) => repo.commit(
Some("HEAD"), Some("HEAD"),
&signature, &signature,
@ -393,5 +537,7 @@ fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid,
&[&parent_commit], &[&parent_commit],
), ),
None => repo.commit(Some("HEAD"), &signature, &signature, message, &tree, &[]), None => repo.commit(Some("HEAD"), &signature, &signature, message, &tree, &[]),
} };
trace!("repo state: {:?}", repo.state());
result
} }