new: setup device for the first device

This commit is contained in:
qwjyh 2023-08-25 23:51:45 +09:00
parent 015734a8e6
commit 92b27b876b
4 changed files with 1348 additions and 0 deletions

1062
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

18
Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "xdbm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "4.4.0", features = ["derive"] }
sysinfo = "0.29.8"
log = "0.4"
clap-verbosity-flag = "2.0.1"
env_logger = "0.10.0"
inquire = "0.6.2"
git2 = "0.17.2"
dirs = "5.0"
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.9"

0
src/init.rs Normal file
View file

268
src/main.rs Normal file
View file

@ -0,0 +1,268 @@
#[macro_use]
extern crate log;
extern crate dirs;
use clap::{Parser, Subcommand};
use clap_verbosity_flag::Verbosity;
use git2::{Commit, IndexEntry, Oid, Repository};
use inquire::{
validator::{StringValidator, Validation},
Text,
};
use serde::{Deserialize, Serialize};
use serde_yaml;
use std::fs::{File, OpenOptions};
use std::io::prelude::*;
use std::io::{self, BufWriter};
use std::{env, io::BufReader, path::Path};
use sysinfo::{System, SystemExt};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
#[command(flatten)]
verbose: Verbosity,
}
#[derive(Subcommand)]
enum Commands {
/// Initialize
Init {
repo: Option<String>, // url?
},
/// Print config dir.
Path {},
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Device {
name: String,
os_name: String,
os_version: String,
hostname: String,
}
const DEVICESFILE: &str = "devices.yml";
struct Storage {}
struct BackupLog {}
impl Device {
fn new(name: String) -> Device {
let sys = System::new();
Device {
name: name,
os_name: sys.name().unwrap_or_else(|| {
warn!("Failed to get OS name. Saving as \"unknown\".");
"unknown".to_string()
}),
os_version: sys.os_version().unwrap_or_else(|| {
warn!("Failed to get OS version. Saving as \"unknown\".");
"unknown".to_string()
}),
hostname: sys.host_name().unwrap_or_else(|| {
warn!("Failed to get hostname. Saving as \"unknown\".");
"unknown".to_string()
}),
}
}
}
fn main() -> Result<(), String> {
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()),
};
config_dir.push("xdbm");
trace!("Config dir: {:?}", config_dir);
match cli.command {
Commands::Init { repo } => {
// get repo or initialize it
let repo = match repo {
Some(repo) => {
trace!("repo: {}", repo);
unimplemented!("Need to use git2"); // TODO
}
None => {
trace!("No repo provided");
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()),
};
// 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 add_and_commit(&repo, Path::new(".gitignore"), "Add devname to gitignore.") {
Ok(_) => (),
Err(e) => return Err(e.to_string()),
};
}
repo
}
};
// set device name
let device = set_device_name()?;
// 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);
serde_yaml::to_writer(writer, &device.name).unwrap();
};
// Add new device to devices.yml
{
// let mut devices = get_devices(&config_dir)?;
let mut devices: Vec<Device> = vec![];
devices.push(device.clone());
trace!("Devices: {:?}", devices);
write_devices(&config_dir, devices)?;
}
// commit
match add_and_commit(
&repo,
&Path::new(DEVICESFILE),
&format!("Add new devname: {}", &device.name),
) {
Ok(_) => (),
Err(e) => return Err(e.to_string()),
}
}
Commands::Path {} => {
println!("{}", &config_dir.display());
}
}
Ok(())
}
/// Set device name interactively.
fn set_device_name() -> Result<Device, String> {
let validator = |input: &str| {
if input.chars().count() == 0 {
Ok(Validation::Invalid("Need at least 1 character.".into()))
} else {
Ok(Validation::Valid)
}
};
let device_name = Text::new("Provide the device(PC) name:")
.with_validator(validator)
.prompt();
let device_name = match device_name {
Ok(device_name) => {
println!("device name: {}", device_name);
device_name
}
Err(err) => {
println!("Error {}", err);
return Err(err.to_string());
}
};
let device = Device::new(device_name);
trace!("Device information: {:?}", device);
trace!("Serialized: \n{}", serde_yaml::to_string(&device).unwrap());
return Ok(device);
}
/// Get `Vec<Device>` from yaml file in `config_dir`.
fn get_devices(config_dir: &Path) -> Result<Vec<Device>, String> {
trace!("get_devices");
let f = match File::open(config_dir.join(DEVICESFILE)) {
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
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()),
};
return Ok(yaml);
}
/// Write `devices` to yaml file in `config_dir`.
fn write_devices(config_dir: &Path, devices: Vec<Device>) -> Result<(), String> {
trace!("write_devices");
let f = match OpenOptions::new().create(true).write(true).open(config_dir.join(DEVICESFILE)) {
Ok(f) => f,
Err(e) => return Err(e.to_string()),
};
let writer = BufWriter::new(f);
match serde_yaml::to_writer(writer, &devices) {
Ok(()) => Ok(()),
Err(e) => Err(e.to_string()),
}
}
fn find_last_commit(repo: &Repository) -> Result<Option<Commit>, git2::Error> {
if repo.is_empty()? {
Ok(None)
} else {
let commit = repo
.head()?
.resolve()?
.peel(git2::ObjectType::Commit)?
.into_commit()
.map_err(|_| git2::Error::from_str("Couldn't find commit"))?;
Ok(Some(commit))
}
}
/// Add file and commit
fn add_and_commit(repo: &Repository, path: &Path, message: &str) -> Result<Oid, git2::Error> {
let mut index = repo.index()?;
index.add_path(path)?;
let oid = index.write_tree()?;
let config = git2::Config::open_default()?;
let signature = git2::Signature::now(
config.get_entry("user.name")?.value().unwrap(),
config.get_entry("user.email")?.value().unwrap(),
)?;
trace!("git signature: {}", signature);
let parent_commit = find_last_commit(&repo)?;
let tree = repo.find_tree(oid)?;
match parent_commit {
Some(parent_commit) => repo.commit(
Some("HEAD"),
&signature,
&signature,
message,
&tree,
&[&parent_commit],
),
None => repo.commit(Some("HEAD"), &signature, &signature, message, &tree, &[]),
}
}