From 4ff023d2de3c3530eaabb111a734372fbc5c2e51 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Sat, 8 Oct 2022 12:04:03 +0300 Subject: [PATCH] add `geode config setup` for setting up config.json without installer + automatic old config migration --- src/info.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++-- src/profile.rs | 11 +++--- src/util/config.rs | 85 ++++++++++++++++++++++++++++++++++------ 3 files changed, 172 insertions(+), 22 deletions(-) diff --git a/src/info.rs b/src/info.rs index a28cd52..68cc2b7 100644 --- a/src/info.rs +++ b/src/info.rs @@ -1,9 +1,12 @@ +use std::cell::RefCell; +use std::io::BufRead; /** * geode info */ use std::path::{PathBuf}; use crate::config::Config; -use crate::{fail, done}; +use crate::util::config::Profile; +use crate::{fail, done, info}; use crate::NiceUnwrap; use colored::Colorize; use clap::Subcommand; @@ -30,7 +33,10 @@ pub enum Info { }, /// List possible values - List + List, + + /// Setup config (if you have manually installed Geode) + Setup {}, } const CONFIGURABLES: [&str; 3] = [ @@ -102,6 +108,92 @@ pub fn subcommand(config: &mut Config, cmd: Info) { for i in CONFIGURABLES { println!("{}", i); } - } + }, + + Info::Setup {} => { + if config.profiles.is_empty() { + info!("Please enter the path to the Geometry Dash folder:"); + + let path = loop { + let mut buf = String::new(); + match std::io::stdin().lock().read_line(&mut buf) { + Ok(_) => {}, + Err(e) => { + fail!("Unable to read input: {}", e); + continue; + } + }; + + // Verify path is valid + let path = PathBuf::from(buf.trim()); + if !path.is_dir() { + fail!( + "The path must point to the Geometry Dash \ + folder, not the executable" + ); + continue; + } + if path.read_dir().map(|mut files| files.next().is_none()).unwrap_or(false) { + fail!("Given path appears to be empty"); + continue; + } + // todo: maybe do some checksum verification + // to make sure GD 2.113 is in the folder + break path; + }; + + info!("Please enter a name for the profile:"); + let name = loop { + let mut buf = String::new(); + match std::io::stdin().lock().read_line(&mut buf) { + Ok(_) => break buf, + Err(e) => fail!("Unable to read input: {}", e) + }; + }; + + config.profiles.push(RefCell::new( + Profile::new(name.trim().into(), path) + )); + config.current_profile = Some(name.trim().into()); + done!("Profile added"); + } + + if config.sdk_path.is_none() { + info!( + "Please enter the path to the Geode repository folder \ + (https://github.com/geode-sdk/geode):" + ); + config.sdk_path = Some(loop { + let mut buf = String::new(); + match std::io::stdin().lock().read_line(&mut buf) { + Ok(_) => {}, + Err(e) => { + fail!("Unable to read input: {}", e); + continue; + } + }; + + // Verify path is valid + let path = PathBuf::from(buf.trim()); + if !path.is_dir() { + fail!("The path must point to a folder"); + continue; + } + if !path.join("README.md").exists() { + fail!( + "Given path doesn't seem to be the Geode repo, \ + make sure to enter the path to the root (where \ + README.md is)" + ); + continue; + } + break path; + }); + config.sdk_nightly = config.sdk_path.as_ref().unwrap().join("bin/nightly").exists(); + done!("SDK path added"); + } + + done!("Config setup finished"); + }, } } \ No newline at end of file diff --git a/src/profile.rs b/src/profile.rs index 39a14cf..b9ae6f2 100644 --- a/src/profile.rs +++ b/src/profile.rs @@ -69,18 +69,18 @@ pub fn subcommand(config: &mut Config, cmd: Profile) { }, Profile::Switch { profile } => { - if config.get_profile(&profile).is_none() { + if config.get_profile(&Some(profile.to_owned())).is_none() { fail!("Profile '{}' does not exist", profile); - } else if config.current_profile == profile { + } else if config.current_profile == Some(profile.to_owned()) { fail!("'{}' is already the current profile", profile); } else { done!("'{}' is now the current profile", &profile); - config.current_profile = profile; + config.current_profile = Some(profile); } }, Profile::Add { name, location } => { - if config.get_profile(&name).is_some() { + if config.get_profile(&Some(name.to_owned())).is_some() { fail!("A profile named '{}' already exists", name); } else if !is_valid_geode_dir(&location) { fail!("The specified path does not point to a valid Geode installation"); @@ -88,11 +88,10 @@ pub fn subcommand(config: &mut Config, cmd: Profile) { done!("A new profile named '{}' has been created", &name); config.profiles.push(RefCell::new(CfgProfile::new(name, location))); } - }, Profile::Remove { name } => { - if config.get_profile(&name).is_none() { + if config.get_profile(&Some(name.to_owned())).is_none() { fail!("Profile '{}' does not exist", name); } else { config.profiles.retain(|x| x.borrow().name != name); diff --git a/src/util/config.rs b/src/util/config.rs index 23d2ca3..e37e8b3 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::path::PathBuf; -use crate::{fail, warn, done, info}; +use crate::{fail, warn, done, info, fatal}; use crate::NiceUnwrap; #[derive(Serialize, Deserialize, Clone)] @@ -21,7 +21,7 @@ pub struct Profile { #[derive(Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct Config { - pub current_profile: String, + pub current_profile: Option, pub profiles: Vec>, pub default_developer: Option, pub sdk_path: Option, @@ -30,6 +30,49 @@ pub struct Config { other: HashMap, } +// old config.json structures for migration + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct OldConfigInstallation { + pub path: PathBuf, + pub executable: String, +} + +#[derive(Serialize, Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct OldConfig { + pub default_installation: usize, + pub working_installation: Option, + pub installations: Option>, + pub default_developer: Option, +} + +impl OldConfig { + pub fn migrate(&self) -> Config { + let profiles = self.installations.as_ref().map(|insts| { + insts.iter().map(|inst| RefCell::from(Profile { + name: inst.executable + .strip_suffix(".exe") + .unwrap_or(&inst.executable) + .into(), + gd_path: inst.path.clone(), + other: HashMap::new() + })).collect::>() + }).unwrap_or(Vec::new()); + Config { + current_profile: profiles.get( + self.working_installation.unwrap_or(self.default_installation) + ).map(|i| i.borrow().name.clone()), + profiles, + default_developer: self.default_developer.to_owned(), + sdk_path: None, + sdk_nightly: false, + other: HashMap::new() + } + } +} + pub fn geode_root() -> PathBuf { // get data dir per-platform let data_dir: PathBuf; @@ -57,8 +100,12 @@ impl Profile { } impl Config { - pub fn get_profile(&self, name: &str) -> Option<&RefCell> { - self.profiles.iter().filter(|x| x.borrow().name == name).next() + pub fn get_profile(&self, name: &Option) -> Option<&RefCell> { + if let Some(name) = name { + self.profiles.iter().filter(|x| x.borrow().name == name.to_owned()).next() + } else { + None + } } pub fn new() -> Config { @@ -68,7 +115,7 @@ impl Config { info!("At {}", "https://github.com/geode-sdk/installer/releases/latest".bright_cyan()); return Config { - current_profile: String::new(), + current_profile: None, profiles: Vec::new(), default_developer: None, sdk_path: None, @@ -82,7 +129,7 @@ impl Config { let mut output: Config = if !config_json.exists() { // Create new config Config { - current_profile: String::new(), + current_profile: None, profiles: Vec::new(), default_developer: None, sdk_path: None, @@ -90,8 +137,21 @@ impl Config { other: HashMap::::new() } } else { - serde_json::from_str(&std::fs::read_to_string(&config_json).unwrap()) - .nice_unwrap("Unable to parse config.json") + // Parse config + let config_json_str = &std::fs::read_to_string(&config_json) + .nice_unwrap("Unable to read config.json"); + match serde_json::from_str(config_json_str) { + Ok(json) => json, + Err(e) => { + // Try migrating old config + if let Ok(json) = serde_json::from_str::(config_json_str) { + info!("Migrating old config.json"); + json.migrate() + } else { + fatal!("Unable to parse config.json: {}", e); + } + } + } }; output.save(); @@ -99,9 +159,8 @@ impl Config { if output.profiles.is_empty() { warn!("No Geode profiles found! Some operations will be unavailable."); info!("Install Geode using the official installer (https://github.com/geode-sdk/installer/releases/latest)"); - - } else if output.get_profile(&output.current_profile.clone()).is_none() { - output.current_profile = output.profiles[0].borrow().name.clone(); + } else if output.get_profile(&output.current_profile).is_none() { + output.current_profile = Some(output.profiles[0].borrow().name.clone()); } output @@ -115,10 +174,10 @@ impl Config { } pub fn rename_profile(&mut self, old: &str, new: String) { - let profile = self.get_profile(old) + let profile = self.get_profile(&Some(String::from(old))) .nice_unwrap(format!("Profile named '{}' does not exist", old)); - if self.get_profile(&new).is_some() { + if self.get_profile(&Some(new.to_owned())).is_some() { fail!("The name '{}' is already taken!", new); } else { done!("Successfully renamed '{}' to '{}'", old, &new);