diff --git a/backend/src/api/handler.rs b/backend/src/api/handler.rs index 83a2b9b..3f36ce0 100644 --- a/backend/src/api/handler.rs +++ b/backend/src/api/handler.rs @@ -23,7 +23,7 @@ pub enum ApiMessage { OnChargeChange(f64), // battery fill amount: 0 = empty, 1 = full PowerVibeCheck, WaitForEmptyQueue(Callback<()>), - LoadSettings(u64, String, u64, String), // (path, name, variant, variant name) + LoadSettings(Option<(u64, String)>, Option<(u64, String, u64, String)>), // (legacy(game_id, name), current(path, name, variant, variant name)) LoadVariant(u64, String), // (variant, variant name) -- path and name assumed to be for current profile LoadMainSettings, LoadSystemSettings, @@ -45,13 +45,21 @@ impl core::fmt::Display for ApiMessage { Self::OnChargeChange(x) => write!(f, "OnChargeChange({:?})", x), Self::PowerVibeCheck => write!(f, "PowerVibeCheck"), Self::WaitForEmptyQueue(_) => write!(f, "WaitForEmptyQueue"), - Self::LoadSettings(path, name, variant, variant_name) => write!(f, "LoadSettings({}, {}, {}, {})", path, name, variant, variant_name), - Self::LoadVariant(variant, variant_name) => write!(f, "LoadVariant({}, {})", variant, variant_name), + Self::LoadSettings(path, name, variant, variant_name) => write!( + f, + "LoadSettings({}, {}, {}, {})", + path, name, variant, variant_name + ), + Self::LoadVariant(variant, variant_name) => { + write!(f, "LoadVariant({}, {})", variant, variant_name) + } Self::LoadMainSettings => write!(f, "LoadMainSettings"), Self::LoadSystemSettings => write!(f, "LoadSystemSettings"), Self::GetLimits(_) => write!(f, "GetLimits"), Self::GetProvider(s, _) => write!(f, "GetProvider({})", s), - Self::UploadCurrentVariant(id, user) => write!(f, "UploadCurrentVariant(id: {}, user: {})", id, user), + Self::UploadCurrentVariant(id, user) => { + write!(f, "UploadCurrentVariant(id: {}, user: {})", id, user) + } } } } @@ -392,9 +400,7 @@ impl ApiMessageHandler { messages.push(msg.to_string()); dirty |= self.process(settings, msg); } - if dirty - || dirty_echo - { + if dirty || dirty_echo { dirty_echo = dirty; // echo only once print_messages(&messages); // run on_set @@ -498,7 +504,61 @@ impl ApiMessageHandler { self.on_empty.push(callback); false } - ApiMessage::LoadSettings(id, name, variant_id, variant_name) => { + ApiMessage::LoadSettings(legacy_settings, current_settings) => { + /* Migration steps: + 1. Modify to the frontend to send the game ID in this message (`id` here is the app + ID). + 2. Change game ID to app ID. + 3. (Create and) call function to merge OldSettingsJson with existing FileJson, or + use existing `From for FileJson` if this game doesn't have a + save in the new format yet. + 4. Let the rest of the code below work its magic. + */ + + // ===== migration logic ===== + if legacy_settings.is_some() { + let (legacy_game_id, legacy_name) = legacy_settings.unwrap(); + + let legacy_file_path = format!("{legacy_game_id}.json"); + let legacy_file = crate::persist::OldSettingsJson::open(legacy_file_path) + .expect("should be able to deserialzie legacy savefile format"); // TODO: don't panic on fail. + + if current_settings.is_some() { + // A savefile in both the legacy and current format exist. + let (id, name, variant_id, variant_name) = current_settings.unwrap(); + let path = format!("{}.ron", id); + + // 1. Parse `legacy_file` to into a settings variant. + let migrated_settings_variant: crate::persist::SettingsJson = + legacy_file.into(); + // 2. Insert the variant into the current settings file. + match settings.load_file( + path.into(), + id, + name, + variant_id, + variant_name, + false, + ) { + Ok(success) => log::info!("Loaded settings file? {}", success), + Err(e) => log::warn!("Load file err: {}", e), + } + settings.add_variant() //TODO: This. + } else { + // A savefile in the current format doesn't exist yet. + // TODO: Parse it to the new format and save it. + + let app_id_from_game_id: u64; + let new_path = format!("{app_id_from_game_id}.ron"); + + let migrated_settings: crate::persist::FileJson = legacy_file.into(); + migrated_settings.save(new_path); + } + } else if current_settings.is_some() { + // ... + } + // =========================== + let path = format!("{}.ron", id); if let Err(e) = settings.on_unload() { print_errors("LoadSettings on_unload()", e); diff --git a/backend/src/persist/error.rs b/backend/src/persist/error.rs index 502d199..ca1b41e 100644 --- a/backend/src/persist/error.rs +++ b/backend/src/persist/error.rs @@ -36,6 +36,14 @@ impl From for RonError { } } +impl From for RonError { + fn from(_value: serde_json::Error) -> Self { + RonError::General(ron::Error::Message(String::from( + "TODO: make error handling in migration logic good. Specifically, make it so a json error can be used as a RonError instead of just returning this string", + ))) + } +} + impl From for RonError { fn from(value: ron::error::SpannedError) -> Self { Self::Spanned(value) diff --git a/backend/src/persist/migration.rs b/backend/src/persist/migration.rs new file mode 100644 index 0000000..7931447 --- /dev/null +++ b/backend/src/persist/migration.rs @@ -0,0 +1,4 @@ +pub mod gpu; +pub mod settings; + +pub const APP_ID_UNKNOWN: u64 = u64::MAX; diff --git a/backend/src/persist/migration/gpu.rs b/backend/src/persist/migration/gpu.rs new file mode 100644 index 0000000..6f71a4e --- /dev/null +++ b/backend/src/persist/migration/gpu.rs @@ -0,0 +1,26 @@ +use serde::{Deserialize, Serialize}; + +use crate::persist::{GpuJson, MinMaxJson}; + +#[derive(Serialize, Deserialize, Clone)] +pub struct OldGpuJson { + pub fast_ppt: Option, + pub slow_ppt: Option, + pub clock_limits: Option>, + pub slow_memory: bool, + pub root: Option, +} + +impl From for GpuJson { + fn from(old_gpu: OldGpuJson) -> Self { + Self { + fast_ppt: old_gpu.fast_ppt, + slow_ppt: old_gpu.slow_ppt, + tdp: None, + tdp_boost: None, + clock_limits: old_gpu.clock_limits.clone(), + memory_clock: None, + root: old_gpu.root.clone(), + } + } +} diff --git a/backend/src/persist/migration/settings.rs b/backend/src/persist/migration/settings.rs new file mode 100644 index 0000000..0327a48 --- /dev/null +++ b/backend/src/persist/migration/settings.rs @@ -0,0 +1,67 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +use crate::persist::{ + BatteryJson, CpuJson, DriverJson, FileJson, SerdeError, SettingsJson, LATEST_VERSION, +}; + +use super::gpu::OldGpuJson; + +#[derive(Serialize, Deserialize, Clone)] +pub struct OldOnEventJson { + pub on_save: Option, + pub on_load: Option, + pub on_set: Option, + pub on_resume: Option, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct OldSettingsJson { + pub version: u64, + pub name: String, + pub persistent: bool, + pub cpus: Vec, + pub gpu: OldGpuJson, + pub battery: BatteryJson, + pub provider: Option, + pub events: Option, +} + +impl From for SettingsJson { + fn from(old_settings: OldSettingsJson) -> Self { + Self { + version: old_settings.version, + name: format!("{} (migrated)", old_settings.name.clone()), + variant: 0, + persistent: old_settings.persistent, + cpus: old_settings.cpus.clone(), + gpu: old_settings.gpu.clone().into(), + battery: old_settings.battery.clone(), + provider: old_settings.provider.clone(), + tags: Vec::new(), + } + } +} + +impl From for FileJson { + fn from(old_settings: OldSettingsJson) -> Self { + let mut variants = HashMap::new(); + let variant = SettingsJson::from(old_settings.clone()); + variants.insert(0, variant); + + Self { + version: LATEST_VERSION, + name: old_settings.name.clone(), + app_id: super::APP_ID_UNKNOWN, // `u64::MAX`, sentinel value. + variants, + } + } +} + +impl OldSettingsJson { + pub fn open>(path: P) -> Result { + let mut file = std::fs::File::open(path).map_err(SerdeError::Io)?; + serde_json::from_reader(&mut file).map_err(|e| SerdeError::Serde(e.into())) + } +} diff --git a/backend/src/persist/mod.rs b/backend/src/persist/mod.rs index bfc0b2a..d856fa8 100644 --- a/backend/src/persist/mod.rs +++ b/backend/src/persist/mod.rs @@ -5,6 +5,7 @@ mod error; mod file; mod general; mod gpu; +mod migration; pub use battery::{BatteryEventJson, BatteryJson}; pub use cpu::CpuJson; @@ -12,7 +13,8 @@ pub use driver::DriverJson; pub use file::FileJson; pub use general::{MinMaxJson, SettingsJson}; pub use gpu::GpuJson; +pub use migration::{gpu::*, settings::*}; -pub use error::SerdeError; +pub use error::{RonError, SerdeError}; pub const LATEST_VERSION: u64 = 0;