mirror of
https://github.com/lifegpc/game-auto-sync.git
synced 2026-07-08 01:30:49 +08:00
Add files
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,7 +5,6 @@ target/
|
|||||||
|
|
||||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
Cargo.lock
|
|
||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
|||||||
150
Cargo.lock
generated
Normal file
150
Cargo.lock
generated
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "convert_case"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "0.99.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce"
|
||||||
|
dependencies = [
|
||||||
|
"convert_case",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustc_version",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "game-auto-sync"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more",
|
||||||
|
"getopts",
|
||||||
|
"subprocess",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getopts"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.155"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.86"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||||
|
dependencies = [
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subprocess"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
||||||
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "game-auto-sync"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
derive_more = "0.99.18"
|
||||||
|
getopts = "0.2.21"
|
||||||
|
subprocess = "0.2.9"
|
||||||
|
yaml-rust = "0.4.5"
|
||||||
107
src/cfg.rs
Normal file
107
src/cfg.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
use std::{fs::File, io::Read};
|
||||||
|
use yaml_rust::{yaml::Hash, ScanError, Yaml, YamlLoader};
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
|
pub enum ConfigError {
|
||||||
|
YAML(ScanError),
|
||||||
|
IO(std::io::Error),
|
||||||
|
Invalid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
obj: Hash,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub fn from_file_path<P: AsRef<Path> + ?Sized>(path: &P) -> Result<Self, ConfigError> {
|
||||||
|
let mut f = File::open(path.as_ref())?;
|
||||||
|
let mut s = String::new();
|
||||||
|
f.read_to_string(&mut s)?;
|
||||||
|
Self::from_str(&s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_str<S: AsRef<str> + ?Sized>(s: &S) -> Result<Self, ConfigError> {
|
||||||
|
let re = YamlLoader::load_from_str(s.as_ref())?;
|
||||||
|
if re.is_empty() {
|
||||||
|
return Err(ConfigError::Invalid);
|
||||||
|
}
|
||||||
|
let re = re[0].clone();
|
||||||
|
let obj = match re {
|
||||||
|
Yaml::Hash(h) => h,
|
||||||
|
_ => {
|
||||||
|
return Err(ConfigError::Invalid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Self { obj })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get<S: AsRef<str> + ?Sized>(&self, s: &S) -> Option<&Yaml> {
|
||||||
|
let k = Yaml::from_str(s.as_ref());
|
||||||
|
self.obj.get(&k)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_str<S: AsRef<str> + ?Sized>(&self, s: &S) -> Option<&str> {
|
||||||
|
match self.get(s) {
|
||||||
|
Some(y) => match y {
|
||||||
|
Yaml::String(s) => Some(s),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_str_vec<S: AsRef<str> + ?Sized>(&self, s: &S) -> Option<Vec<String>> {
|
||||||
|
match self.get(s) {
|
||||||
|
Some(e) => match e {
|
||||||
|
Yaml::String(s) => Some(vec![s.to_owned()]),
|
||||||
|
Yaml::Array(s) => Some(s
|
||||||
|
.iter()
|
||||||
|
.filter_map(|s| match s {
|
||||||
|
Yaml::String(s) => Some(s.to_owned()),
|
||||||
|
Yaml::Integer(i) => Some(i.to_string()),
|
||||||
|
Yaml::Real(i) => Some(i.to_owned()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_backuper_cfg(&self) -> String {
|
||||||
|
match self.get_str("game_backuper_cfg") {
|
||||||
|
Some(s) => {
|
||||||
|
let mut pb = crate::utils::get_exe_path_else_current();
|
||||||
|
pb.push(s);
|
||||||
|
pb.to_string_lossy().to_string()
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let mut pb = crate::utils::get_exe_path_else_current();
|
||||||
|
pb.push("game_backuper.yml");
|
||||||
|
pb.to_string_lossy().to_string()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_backuper_exe(&self) -> String {
|
||||||
|
match self.get_str("game_backuper_exe") {
|
||||||
|
Some(s) => s.to_owned(),
|
||||||
|
None => String::from("game-backuper"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn game_exe(&self) -> Option<Vec<String>> {
|
||||||
|
self.get_str_vec("game_exe")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn backup_command(&self) -> Option<Vec<String>> {
|
||||||
|
self.get_str_vec("backup_command")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_command(&self) -> Option<Vec<String>> {
|
||||||
|
self.get_str_vec("restore_command")
|
||||||
|
}
|
||||||
|
}
|
||||||
177
src/main.rs
Normal file
177
src/main.rs
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
mod cfg;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use getopts::Options;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use subprocess::ExitStatus;
|
||||||
|
|
||||||
|
pub fn print_usage(prog: &str, opts: &Options) {
|
||||||
|
let brief = format!(
|
||||||
|
"{}
|
||||||
|
{} [options]",
|
||||||
|
"Usage:", prog,
|
||||||
|
);
|
||||||
|
println!("{}", opts.usage(brief.as_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||||
|
enum Error {
|
||||||
|
Popen(subprocess::PopenError),
|
||||||
|
Exited,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Main {
|
||||||
|
_cfg: cfg::Config,
|
||||||
|
_dryrun: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Main {
|
||||||
|
fn new(cfg: cfg::Config, dryrun: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
_cfg: cfg,
|
||||||
|
_dryrun: dryrun,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backup(&self) -> Result<(), Error> {
|
||||||
|
let cml = match self._cfg.backup_command() {
|
||||||
|
Some(cml) => cml,
|
||||||
|
None => {
|
||||||
|
let mut def = vec![self._cfg.game_backuper_exe()];
|
||||||
|
let cfg_path = self._cfg.game_backuper_cfg();
|
||||||
|
if std::fs::exists(&cfg_path).unwrap_or(false) {
|
||||||
|
def.push(String::from("-c"));
|
||||||
|
def.push(cfg_path);
|
||||||
|
}
|
||||||
|
def.push(String::from("backup"));
|
||||||
|
def
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if self._dryrun {
|
||||||
|
println!("Backup command line: {:?}", cml);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let e = Self::call(cml)?;
|
||||||
|
let ok = match &e {
|
||||||
|
ExitStatus::Exited(c) => *c != 0,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !ok {
|
||||||
|
println!("Backup failed: {:?}.", e);
|
||||||
|
return Err(Error::Exited);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(cml: Vec<String>) -> Result<ExitStatus, subprocess::PopenError> {
|
||||||
|
let mut p = subprocess::Popen::create(&cml, subprocess::PopenConfig::default())?;
|
||||||
|
p.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&self) -> Result<(), Error> {
|
||||||
|
let cml = match self._cfg.restore_command() {
|
||||||
|
Some(cml) => cml,
|
||||||
|
None => {
|
||||||
|
let mut def = vec![self._cfg.game_backuper_exe()];
|
||||||
|
let cfg_path = self._cfg.game_backuper_cfg();
|
||||||
|
if std::fs::exists(&cfg_path).unwrap_or(false) {
|
||||||
|
def.push(String::from("-c"));
|
||||||
|
def.push(cfg_path);
|
||||||
|
}
|
||||||
|
def.push(String::from("restore"));
|
||||||
|
def
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if self._dryrun {
|
||||||
|
println!("Restore command line: {:?}", cml);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let e = Self::call(cml)?;
|
||||||
|
let ok = match &e {
|
||||||
|
ExitStatus::Exited(c) => *c != 0,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !ok {
|
||||||
|
println!("Restore failed: {:?}.", e);
|
||||||
|
if !utils::ask_continue() {
|
||||||
|
return Err(Error::Exited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self) -> Result<(), Error> {
|
||||||
|
self.restore()?;
|
||||||
|
self.run_exe()?;
|
||||||
|
self.backup()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_exe(&self) -> Result<(), Error> {
|
||||||
|
let cml = self._cfg.game_exe().unwrap();
|
||||||
|
if self._dryrun {
|
||||||
|
println!("Run command line: {:?}", cml);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
let e = Self::call(cml)?;
|
||||||
|
let ok = match &e {
|
||||||
|
ExitStatus::Exited(c) => *c != 0,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !ok {
|
||||||
|
println!("Run failed: {:?}.", e);
|
||||||
|
if !utils::ask_continue() {
|
||||||
|
return Err(Error::Exited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> ExitCode {
|
||||||
|
println!("Hello, world!");
|
||||||
|
let argv: Vec<String> = std::env::args().collect();
|
||||||
|
let mut opts = Options::new();
|
||||||
|
opts.optflag("h", "help", "Print help message.");
|
||||||
|
opts.optopt("c", "config", "The location of config file.", "FILE");
|
||||||
|
opts.optflag("d", "dryrun", "Run without calling any process.");
|
||||||
|
let result = match opts.parse(&argv[1..]) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(err) => {
|
||||||
|
println!("{}", err.to_string());
|
||||||
|
return ExitCode::from(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if result.opt_present("h") {
|
||||||
|
print_usage(&argv[0], &opts);
|
||||||
|
return ExitCode::from(0);
|
||||||
|
}
|
||||||
|
let cfg_path = result.opt_str("c").unwrap_or_else(|| {
|
||||||
|
let mut pb = utils::get_exe_path_else_current();
|
||||||
|
pb.push("game-auto-sync.yml");
|
||||||
|
pb.to_string_lossy().to_string()
|
||||||
|
});
|
||||||
|
let cfg = match cfg::Config::from_file_path(&cfg_path) {
|
||||||
|
Ok(cfg) => cfg,
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}", e);
|
||||||
|
return ExitCode::from(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if cfg.game_exe().unwrap_or(vec![]).is_empty() {
|
||||||
|
println!("game_exe need be set.");
|
||||||
|
return ExitCode::from(1);
|
||||||
|
}
|
||||||
|
let m = Main::new(cfg, result.opt_present("d"));
|
||||||
|
match m.run() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}", e);
|
||||||
|
return ExitCode::from(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ExitCode::from(0);
|
||||||
|
}
|
||||||
38
src/utils.rs
Normal file
38
src/utils.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Get executable location, if not found, return current directory (./)
|
||||||
|
pub fn get_exe_path_else_current() -> PathBuf {
|
||||||
|
let re = env::current_exe();
|
||||||
|
match re {
|
||||||
|
Ok(pa) => {
|
||||||
|
let mut p = pa.clone();
|
||||||
|
p.pop();
|
||||||
|
p
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let p = Path::new("./");
|
||||||
|
p.to_path_buf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ask_continue() -> bool {
|
||||||
|
print!("Do you want to continue?(y/n)");
|
||||||
|
std::io::stdout().flush().unwrap();
|
||||||
|
let mut d = String::from("");
|
||||||
|
loop {
|
||||||
|
let re = std::io::stdin().read_line(&mut d);
|
||||||
|
if re.is_err() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let d = d.trim().to_lowercase();
|
||||||
|
if d == "y" {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user