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

View file

@ -16,3 +16,5 @@ git2 = "0.17.2"
dirs = "5.0"
serde = { version = "1.0", features = ["derive"] }
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]
extern crate log;
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 git2::{Commit, IndexEntry, Oid, Repository};
use inquire::{
validator::{StringValidator, Validation},
validator::Validation,
Text,
};
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::io::prelude::*;
use std::io::{self, BufWriter};
use std::{
collections::{hash_map::RandomState, HashMap},
fmt::Debug,
fs::{File, OpenOptions},
};
use std::{env, io::BufReader, path::Path};
use std::{
ffi::OsString,
io::{self, BufWriter},
};
use std::{fs, io::prelude::*};
use sysinfo::{DiskExt, System, SystemExt};
#[derive(Parser)]
@ -39,6 +50,7 @@ enum Commands {
repo_url: Option<String>, // url?
},
/// Manage storages.
Storage(StorageArgs),
/// Print config dir.
@ -57,7 +69,10 @@ struct StorageArgs {
#[derive(Subcommand)]
enum StorageCommands {
Add {},
Add {
#[arg(value_enum)]
storage_type: StorageType,
},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -91,6 +106,12 @@ impl Device {
const DEVICESFILE: &str = "devices.yml";
#[derive(ValueEnum, Clone, Copy, Debug)]
enum StorageType {
Physical,
// Online,
}
/// All storage types.
#[derive(Serialize, Deserialize, Debug)]
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.
#[derive(Serialize, Deserialize, Debug)]
struct PhysicalDrivePartition {
@ -111,33 +142,31 @@ struct PhysicalDrivePartition {
capacity: u64,
fs: String,
is_removable: bool,
aliases: HashMap<String, String, RandomState>,
system_names: HashMap<String, String, RandomState>,
}
impl PhysicalDrivePartition {
/// Try to get Physical drive info from sysinfo.
fn try_from_sysinfo_disk(
disk: sysinfo::Disk,
disk: &sysinfo::Disk,
name: String,
device: Device,
) -> Result<PhysicalDrivePartition, String> {
let alias = match disk.name().to_str() {
Some(s) => s.to_string(),
None => return Err("Failed to convert storage name to valid str.".to_string()),
};
) -> Result<PhysicalDrivePartition> {
let alias = disk
.name()
.to_str()
.context("Failed to convert storage name to valid str.")?
.to_string();
let fs = disk.file_system();
trace!("fs: {:?}", fs);
let fs = match std::str::from_utf8(fs) {
Ok(s) => s,
Err(e) => return Err(e.to_string()),
};
let fs = std::str::from_utf8(fs)?;
Ok(PhysicalDrivePartition {
name: name,
kind: format!("{:?}", disk.kind()),
capacity: disk.total_space(),
fs: fs.to_string(),
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
}
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() {
Some(s) => s.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) {
Some(v) => v,
None => return Err("Failed to insert alias".to_string()),
@ -161,24 +194,21 @@ impl PhysicalDrivePartition {
capacity: self.capacity,
fs: self.fs,
is_removable: self.is_removable,
aliases,
system_names: aliases,
})
}
}
struct BackupLog {}
fn main() -> Result<(), String> {
fn main() -> Result<()> {
let cli = Cli::parse();
env_logger::Builder::new()
.filter_level(cli.verbose.log_level_filter())
.init();
trace!("Start logging...");
let mut config_dir = match dirs::config_local_dir() {
Some(dir) => dir,
None => return Err("Failed to get config dir.".to_string()),
};
let mut config_dir = dirs::config_local_dir().context("Failed to get config dir.")?;
config_dir.push("xdbm");
trace!("Config dir: {:?}", config_dir);
@ -186,13 +216,10 @@ fn main() -> Result<(), String> {
Commands::Init { repo_url } => {
let is_first_device: bool;
// get repo or initialize it
let repo_url = match repo_url {
let repo = match repo_url {
Some(repo_url) => {
trace!("repo: {}", repo_url);
let repo = match Repository::clone(&repo_url, &config_dir) {
Ok(repo) => repo,
Err(e) => return Err(e.to_string()),
};
let repo = Repository::clone(&repo_url, &config_dir)?;
is_first_device = false;
repo
}
@ -201,34 +228,20 @@ fn main() -> Result<(), String> {
println!("Initializing for the first device...");
// create repository
let repo = match Repository::init(&config_dir) {
Ok(repo) => repo,
Err(e) => return Err(e.to_string()),
};
let repo = Repository::init(&config_dir)?;
// set up gitignore
{
let f = match File::create(&config_dir.join(".gitignore")) {
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
let mut buf = BufWriter::new(f);
match buf.write("devname".as_bytes()) {
Ok(_) => trace!("successfully created ignore file"),
Err(e) => return Err(e.to_string()),
};
match buf.flush() {
Ok(_) => (),
Err(e) => return Err(e.to_string()),
};
match add_and_commit(
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.",
) {
Ok(_) => (),
Err(e) => return Err(e.to_string()),
};
)?;
}
is_first_device = true;
repo
@ -241,11 +254,8 @@ fn main() -> Result<(), String> {
// save devname
let devname_path = &config_dir.join("devname");
{
let mut f = match File::create(devname_path) {
Ok(f) => f,
Err(e) => panic!("Failed to create devname file: {}", e),
};
let mut writer = BufWriter::new(f);
let f = File::create(devname_path).context("Failed to create devname file")?;
let writer = BufWriter::new(f);
serde_yaml::to_writer(writer, &device.name).unwrap();
};
@ -257,7 +267,7 @@ fn main() -> Result<(), String> {
get_devices(&config_dir)?
};
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());
trace!("Devices: {:?}", devices);
@ -265,22 +275,61 @@ fn main() -> Result<(), String> {
}
// commit
match add_and_commit(
&repo_url,
add_and_commit(
&repo,
&Path::new(DEVICESFILE),
&format!("Add new devname: {}", &device.name),
) {
Ok(_) => (),
Err(e) => return Err(e.to_string()),
}
)?;
}
Commands::Storage(storage) => {
match storage.command {
StorageCommands::Add {} => {
unimplemented!()
Commands::Storage(storage) => match storage.command {
StorageCommands::Add { storage_type } => {
trace!("Storage Add {:?}", storage_type);
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!()
}
}
},
Commands::Path {} => {
println!("{}", &config_dir.display());
}
@ -291,8 +340,33 @@ fn main() -> Result<(), String> {
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.
fn set_device_name() -> Result<Device, String> {
fn set_device_name() -> Result<Device> {
let validator = |input: &str| {
if input.chars().count() == 0 {
Ok(Validation::Invalid("Need at least 1 character.".into()))
@ -312,7 +386,7 @@ fn set_device_name() -> Result<Device, String> {
}
Err(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`.
fn get_devices(config_dir: &Path) -> Result<Vec<Device>, String> {
fn get_devices(config_dir: &Path) -> Result<Vec<Device>> {
trace!("get_devices");
let f = match File::open(config_dir.join(DEVICESFILE)) {
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
let f = File::open(config_dir.join(DEVICESFILE))?;
let reader = BufReader::new(f);
let yaml: Vec<Device> = match serde_yaml::from_reader(reader) {
Ok(yaml) => yaml,
Err(e) => return Err(e.to_string()),
};
let yaml: Vec<Device> =
serde_yaml::from_reader(reader).context("Failed to parse devices.yml")?;
return Ok(yaml);
}
/// 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");
let f = match OpenOptions::new()
let f = OpenOptions::new()
.create(true)
.write(true)
.open(config_dir.join(DEVICESFILE))
{
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
.open(config_dir.join(DEVICESFILE))?;
let writer = BufWriter::new(f);
match serde_yaml::to_writer(writer, &devices) {
Ok(()) => Ok(()),
Err(e) => Err(e.to_string()),
serde_yaml::to_writer(writer, &devices).map_err(|e| anyhow!(e))
}
/// 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> {
@ -372,6 +515,7 @@ fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> {
/// Add file and commit
fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid, git2::Error> {
trace!("repo state: {:?}", repo.state());
let mut index = repo.index()?;
index.add_path(path)?;
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);
let parent_commit = find_last_commit(&repo)?;
let tree = repo.find_tree(oid)?;
match parent_commit {
let result = match parent_commit {
Some(parent_commit) => repo.commit(
Some("HEAD"),
&signature,
@ -393,5 +537,7 @@ fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid,
&[&parent_commit],
),
None => repo.commit(Some("HEAD"), &signature, &signature, message, &tree, &[]),
}
};
trace!("repo state: {:?}", repo.state());
result
}