commit 4664b638628d7eff53cfc5550ad0a80e3c5322fe Author: Alexis Delhaie Date: Thu Sep 24 10:47:06 2020 +0200 update/downgrade v1 to V2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..8f164f5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,323 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "blake2b_simd" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "platform-dirs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e6f10c0c97e3d27b298374c2c67a057216c98e0a86c44df6bcd1f02bacbe38" +dependencies = [ + "dirs", +] + +[[package]] +name = "proc-macro2" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + +[[package]] +name = "rust-argon2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "save-updater-src" +version = "0.1.0" +dependencies = [ + "chrono", + "platform-dirs", + "serde", + "serde_json", + "winres", +] + +[[package]] +name = "serde" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[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 = "winres" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4fb510bbfe5b8992ff15f77a2e6fe6cf062878f0eda00c0f44963a807ca5dc" +dependencies = [ + "toml", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4b6686f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "save-updater-src" +version = "0.1.0" +authors = ["Alexis Delhaie "] +edition = "2018" +build = "build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +platform-dirs = "0.2.0" +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +chrono = "0.4.15" + +[build-dependencies] +winres = "0.1" + +[profile.release] +opt-level = 3 diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..d6d4a09 --- /dev/null +++ b/build.rs @@ -0,0 +1,9 @@ +extern crate winres; + +fn main() { + if cfg!(target_os = "windows") { + let mut res = winres::WindowsResource::new(); + res.set_icon("icon.ico"); + res.compile().unwrap(); + } +} \ No newline at end of file diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..736ffc4 Binary files /dev/null and b/icon.ico differ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6727c2d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,155 @@ +mod model_version_1; +mod model_version_2; + +pub use crate::model_version_1::SaveVersion1; +pub use crate::model_version_2::downgrade_to_v1; +pub use crate::model_version_2::upgrade_to_v2; +pub use crate::model_version_2::SaveVersion2; +use platform_dirs::{AppDirs, AppUI}; +use serde_json::Value; +use std::env; +use std::fs::{create_dir_all, File}; +use std::io::prelude::*; +use std::io::{self, BufReader, Read}; +use std::time::SystemTime; + +fn main() { + let args: Vec = env::args().collect(); + if args.len() >= 2 { + let verb = &args[1]; + + match verb.as_ref() { + "update" => update_cmd(), + "help" => help_cmd(false, ""), + "downgrade" => { + if args.len() >= 3 { + let allow_downgrade = confirm( + "Do you really want to downgrade your save? \ + You may experience data lost", + ) + .unwrap(); + if allow_downgrade { + let version: u64 = args[2] + .parse::() + .expect("ERROR: Cannot get version number"); + downgrade_cmd(version); + } else { + println!("Operation aborted"); + } + } else { + help_cmd(true, "ERROR: The version number is missing") + } + } + _ => help_cmd(true, "ERROR: This verb is not allowed"), + } + } else { + help_cmd(true, "ERROR: The verb arg is missing"); + } +} + +fn help_cmd(has_error: bool, err: &str) { + if has_error { + eprintln!("{}", err); + println!(""); + } + println!("Usage: save-updater.exe [args]\n"); + println!("List of verbs:"); + println!(" update Update the Chronos file save"); + println!(" downgrade Downgrade to a version"); + println!(" help Show this screen"); +} + +fn downgrade_cmd(version: u64) { + let json_file = open_save_file().expect("ERROR: Cannot open save file"); + let v: Value = serde_json::from_str(&json_file).expect("ERROR: Cannot parse save file"); + let current_version: u64 = v["version"] + .as_u64() + .expect("ERROR: Error while parsing save version"); + if version == (current_version - 1) && version > 0 { + add_to_backup(&json_file).expect("ERROR: Failed to create backup"); + match version { + 1 => { + let s: SaveVersion2 = + serde_json::from_str(&json_file).expect("ERROR: Cannot parse save file"); + let new_save = downgrade_to_v1(s); + let json = + serde_json::to_string(&new_save).expect("ERROR: Fail to build new save file"); + save_to_file(&json, "data.json").expect("ERROR: Fail to save updated file"); + println!("INFO: Save file has been downgraded"); + } + _ => eprintln!("ERROR: Version not supported"), + } + } else { + eprintln!("ERROR: Only downgrading to the previous version is allowed"); + } +} + +fn update_cmd() { + let json_file = open_save_file().expect("ERROR: Cannot open save file"); + let v: Value = serde_json::from_str(&json_file).expect("ERROR: Cannot parse save file"); + if v["version"] == 1 { + add_to_backup(&json_file).expect("ERROR: Failed to create backup"); + let s: SaveVersion1 = + serde_json::from_str(&json_file).expect("ERROR: Cannot parse save file"); + let new_save = start_upgrade_from_v1(s); + let json = serde_json::to_string(&new_save).expect("ERROR: Fail to build new save file"); + save_to_file(&json, "data.json").expect("ERROR: Fail to save updated file"); + println!("INFO: Save file is up-to-date"); + } else { + println!("Save already up-to-date"); + } +} + +fn open_save_file() -> std::result::Result { + let app_dirs = AppDirs::new(Some("Chronos"), AppUI::CommandLine) + .expect("ERROR: Cannot get application data folder"); + let path = app_dirs.config_dir.join("data").join("data.json"); + if path.exists() { + let file = File::open(path).expect("ERROR: cannot open file"); + let mut buf_reader = BufReader::new(file); + let mut contents = String::new(); + buf_reader + .read_to_string(&mut contents) + .expect("cannot read file"); + Ok(contents) + } else { + Err("Save file not found") + } +} + +fn save_to_file(content: &String, filename: &str) -> std::io::Result<()> { + let app_dirs = AppDirs::new(Some("Chronos"), AppUI::CommandLine) + .expect("ERROR: Cannot get application data folder"); + let path = app_dirs.config_dir.join("data").join(filename); + let mut file = File::create(path)?; + file.write_all(content.as_bytes())?; + Ok(()) +} + +fn confirm(msg: &str) -> std::io::Result { + println!("{}\nType 'yes', other input will be ignore", msg); + let mut buffer = String::new(); + io::stdin().read_line(&mut buffer)?; + Ok(buffer.trim() == "yes") +} + +fn add_to_backup(content: &String) -> std::io::Result<()> { + let app_dirs = AppDirs::new(Some("Chronos"), AppUI::CommandLine) + .expect("ERROR: Cannot get application data folder"); + let path = app_dirs.config_dir.join("data").join("backup"); + create_dir_all(&path)?; + let timestamp = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("ERROR: Failed to get timestamp") + .as_secs(); + let path = path.join(format!("{}.data.json", timestamp)); + let mut file = File::create(path)?; + file.write_all(content.as_bytes())?; + Ok(()) +} + +fn start_upgrade_from_v1(save: SaveVersion1) -> SaveVersion2 { + println!("INFO: Starting upgrading from version 1 to version 2"); + let save = upgrade_to_v2(save); + save +} diff --git a/src/model_version_1.rs b/src/model_version_1.rs new file mode 100644 index 0000000..a86e6dd --- /dev/null +++ b/src/model_version_1.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct SaveVersion1 { + pub template: WeekVersion1, + pub version: u32, + pub weeks: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct WeekVersion1 { + pub monday: DayVersion1, + pub tuesday: DayVersion1, + pub wednesday: DayVersion1, + pub thurday: DayVersion1, + pub friday: DayVersion1, + #[serde(alias = "weekNumber", rename(serialize = "weekNumber"))] + pub week_number: u32, +} + +#[derive(Serialize, Deserialize)] +pub struct DayVersion1 { + #[serde(alias = "break", rename(serialize = "break"))] + pub break_time: u32, + pub end: String, + pub start: String, +} diff --git a/src/model_version_2.rs b/src/model_version_2.rs new file mode 100644 index 0000000..3d19af8 --- /dev/null +++ b/src/model_version_2.rs @@ -0,0 +1,151 @@ +pub use crate::model_version_1::DayVersion1; +pub use crate::model_version_1::SaveVersion1; +pub use crate::model_version_1::WeekVersion1; +use chrono::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct SaveVersion2 { + pub template: WeekVersion2, + pub version: u32, + pub weeks: Vec, +} + +#[derive(Serialize, Deserialize)] +pub struct WeekVersion2 { + pub monday: DayVersion2, + pub tuesday: DayVersion2, + pub wednesday: DayVersion2, + pub thurday: DayVersion2, + pub friday: DayVersion2, + #[serde(alias = "weekNumber", rename(serialize = "weekNumber"))] + pub week_number: u32, +} + +#[derive(Serialize, Deserialize)] +pub struct DayVersion2 { + #[serde(alias = "break", rename(serialize = "break"))] + pub break_time: u32, + pub end: String, + pub start: String, + pub validate: bool, +} + +// Upgrade + +pub fn upgrade_to_v2(s: SaveVersion1) -> SaveVersion2 { + let save = SaveVersion2 { + version: 2, + template: upgrade_template(s.template), + weeks: upgrade_weeks(s.weeks), + }; + save +} + +fn upgrade_template(w: WeekVersion1) -> WeekVersion2 { + println!("INFO: Upgrading template"); + let week = WeekVersion2 { + monday: upgrade_day(w.monday, false), + tuesday: upgrade_day(w.tuesday, false), + wednesday: upgrade_day(w.wednesday, false), + thurday: upgrade_day(w.thurday, false), + friday: upgrade_day(w.friday, false), + week_number: w.week_number, + }; + week +} + +fn upgrade_weeks(ws: Vec) -> Vec { + println!("INFO: Upgrading weeks"); + let mut v: Vec = Vec::new(); + let naive_date_time = Utc::now().naive_utc(); + let week_number = naive_date_time.iso_week().week(); + let dow = naive_date_time.weekday(); + for w in ws { + if w.week_number < week_number { + let week = WeekVersion2 { + monday: upgrade_day(w.monday, true), + tuesday: upgrade_day(w.tuesday, true), + wednesday: upgrade_day(w.wednesday, true), + thurday: upgrade_day(w.thurday, true), + friday: upgrade_day(w.friday, true), + week_number: w.week_number, + }; + v.push(week); + } else if w.week_number == week_number { + let week = WeekVersion2 { + monday: upgrade_day(w.monday, dow.num_days_from_monday() > 0), + tuesday: upgrade_day(w.tuesday, dow.num_days_from_monday() > 1), + wednesday: upgrade_day(w.wednesday, dow.num_days_from_monday() > 2), + thurday: upgrade_day(w.thurday, dow.num_days_from_monday() > 3), + friday: upgrade_day(w.friday, dow.num_days_from_monday() > 4), + week_number: w.week_number, + }; + v.push(week); + } else { + let week = WeekVersion2 { + monday: upgrade_day(w.monday, false), + tuesday: upgrade_day(w.tuesday, false), + wednesday: upgrade_day(w.wednesday, false), + thurday: upgrade_day(w.thurday, false), + friday: upgrade_day(w.friday, false), + week_number: w.week_number, + }; + v.push(week); + } + } + v +} + +fn upgrade_day(d: DayVersion1, new_validate: bool) -> DayVersion2 { + println!("INFO: Upgrading day"); + let day = DayVersion2 { + break_time: d.break_time, + end: d.end, + start: d.start, + validate: new_validate, + }; + day +} + +// Downgrade + +pub fn downgrade_to_v1(s: SaveVersion2) -> SaveVersion1 { + let save = SaveVersion1 { + version: 1, + template: downgrade_week(s.template), + weeks: downgrade_weeks(s.weeks), + }; + save +} + +fn downgrade_week(w: WeekVersion2) -> WeekVersion1 { + let week = WeekVersion1 { + monday: downgrade_day(w.monday), + tuesday: downgrade_day(w.tuesday), + wednesday: downgrade_day(w.wednesday), + thurday: downgrade_day(w.thurday), + friday: downgrade_day(w.friday), + week_number: w.week_number, + }; + week +} + +fn downgrade_weeks(ws: Vec) -> Vec { + println!("INFO: Downgrading weeks"); + let mut v: Vec = Vec::new(); + for w in ws { + v.push(downgrade_week(w)); + } + v +} + +fn downgrade_day(d: DayVersion2) -> DayVersion1 { + println!("INFO: Downgrading day"); + let day = DayVersion1 { + break_time: d.break_time, + end: d.end, + start: d.start, + }; + day +}