From 6a5cbcdbea373b17a106a98a09334bcfdbce1c2f Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 23 Aug 2024 12:07:14 +0200 Subject: [PATCH] fix(store): store is now actually saved and loaded --- Cargo.toml | 2 + src/error/mod.rs | 19 +++++++++ src/player/mod.rs | 24 +++++++---- src/player/ui/mod.rs | 10 ++++- src/store/mod.rs | 94 ++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 134 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f652563..4ae49ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ backend-jellyfin = [] [dependencies] anyhow = "1.0.86" clap = { version = "4.5.16", features = ["derive"] } +directories = "5.0.1" eframe = { version = "0.28.1", optional = false } egui = { version = "0.28.1", optional = false } egui_extras = { version = "0.28.1", features = ["image"] } @@ -29,6 +30,7 @@ image = { version = "0.25.2", default-features = true, features = [ ] } libpt = { version = "0.6.0", features = ["cli", "full"] } +rmp-serde = "1.3.0" serde = { version = "1.0.208", features = ["derive"] } strum = { version = "0.26.3", features = ["derive"] } thiserror = "1.0.63" diff --git a/src/error/mod.rs b/src/error/mod.rs index cf846af..1697a34 100644 --- a/src/error/mod.rs +++ b/src/error/mod.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use thiserror::Error; #[derive(Error, Debug)] @@ -6,4 +8,21 @@ pub enum Error { UiError(#[from] eframe::Error), #[error(transparent)] Other(#[from] anyhow::Error), + #[error( + r"The system does not seem to have the usual project dirs, like: +Linux: /home/alice/.config/barapp +MS Windows: C:\Users\Alice\AppData\Roaming\Foo Corp\Bar App\config +Mac: /Users/Alice/Library/Application Support/com.Foo-Corp.Bar-App + +Closing as to not break your files accidentally." + )] + NoProjDir, + #[error(r"The store file '{0}' where Beatbär stores it's metadata is not a regular file, refusing to save or load to keep your files safe.")] + BadStoreFile(PathBuf), + #[error(r"Could not convert the store to the binary format for saving it to the disk.")] + RmpEncode(#[from] rmp_serde::encode::Error), + #[error(r"Could not decode the store from the binary format for loading it from the disk. Is your store file broken?")] + RmpDecode(#[from] rmp_serde::decode::Error), + #[error(r"Error while reading or writing to the disk. Source: {0}")] + IO(#[from] std::io::Error), } diff --git a/src/player/mod.rs b/src/player/mod.rs index aca2b38..1218ab8 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -1,5 +1,6 @@ use clap::Parser; use eframe::CreationContext; +use egui::Stroke; use libpt::cli::args::VerbosityLevel; use libpt::log::{debug, info}; @@ -27,10 +28,15 @@ pub struct Player { kind: Kind, #[clap(skip)] - store: Store, + store: Option, } impl Player { + #[inline] + pub fn store(&self) -> &Store { + &self.store.as_ref().unwrap() + } + pub fn build() -> Result { let mut app = Player::parse(); @@ -63,7 +69,7 @@ impl Player { Ok(app) } pub fn init(&mut self, _cc: &CreationContext) { - // we can use the creation context to do some customizing, but idc right now + self.store = Some(Store::load().expect("could not load store")); } fn set_category(&mut self, kind: Kind) { @@ -80,11 +86,15 @@ impl Player { fn entries(&self) -> Vec { match self.category() { - Kind::Album => self.store.albums(), - Kind::Song => self.store.songs(), - Kind::Playlist => self.store.playlists(), - Kind::Artist => self.store.artists(), - Kind::Genre => self.store.genres(), + Kind::Album => self.store().albums(), + Kind::Song => self.store().songs(), + Kind::Playlist => self.store().playlists(), + Kind::Artist => self.store().artists(), + Kind::Genre => self.store().genres(), } } + + pub fn end(&mut self) -> Result<(), Error> { + self.store().save() + } } diff --git a/src/player/ui/mod.rs b/src/player/ui/mod.rs index ffaa9d6..4307da9 100644 --- a/src/player/ui/mod.rs +++ b/src/player/ui/mod.rs @@ -1,6 +1,6 @@ use egui::{IconData, Sense}; use egui_extras::{Column, TableBuilder}; -use libpt::log::{trace, warn}; +use libpt::log::{error, trace, warn}; use strum::IntoEnumIterator; pub mod entry; @@ -174,4 +174,12 @@ impl eframe::App for Player { }); egui::TopBottomPanel::bottom("bot_panel").show(ctx, Self::bottom_label); } + fn on_exit(&mut self, _gl: Option<&eframe::glow::Context>) { + match self.store().save() { + Ok(_) => (), + Err(e) => { + error!("{e}"); + } + } + } } diff --git a/src/store/mod.rs b/src/store/mod.rs index 83dcfc5..0a46662 100644 --- a/src/store/mod.rs +++ b/src/store/mod.rs @@ -1,24 +1,104 @@ -#[derive(Default)] -pub struct Store {} +use std::fs::File; +use std::io::{BufReader, Write}; +use std::path::PathBuf; + +use libpt::log::{debug, error, info, warn}; +use serde::{Deserialize, Serialize}; + +use crate::error::Error; + +#[derive(Serialize, Deserialize, Default, Debug)] +pub struct Store { + dummy: String, +} impl Store { + pub fn projdir() -> Option { + directories::ProjectDirs::from("de.cscherr", "Beatbär", "Beatbär") + } + pub fn genres(&self) -> Vec { - todo!() + vec![] } pub fn artists(&self) -> Vec { - todo!() + vec![] } pub fn playlists(&self) -> Vec { - todo!() + vec![] } pub fn songs(&self) -> Vec { - todo!() + vec![] } pub fn albums(&self) -> Vec { - todo!() + vec![] + } + + pub fn save(&self) -> Result<(), Error> { + info!("saving the store to file"); + debug!("Store: {self:#?}"); + if let Some(dirs) = Self::projdir() { + let mut store_path: PathBuf = dirs.data_local_dir().into(); + store_path.push("store.msgpack"); + + if !store_path.exists() { + std::fs::create_dir_all( + store_path + .parent() + .expect("beatbär storefile has no parent????"), + )?; + warn!("The Beatbär store at '{}' does not exist. Creating it anew. This is normal if you start Beatbär for the first time.", store_path.as_path().to_string_lossy()); + } else if !store_path.is_file() { + let e = Error::BadStoreFile(store_path); + error!("{e}"); + return Err(e); + } + + let mut file = File::options() + .write(true) + .append(false) + .create(true) + .truncate(true) + .open(store_path)?; + let repr = rmp_serde::to_vec(self)?; + file.write_all(&repr)?; + info!("store file was written"); + Ok(()) + } else { + Err(Error::NoProjDir) + } + } + + pub fn load() -> Result { + info!("loading the store to file"); + if let Some(dirs) = Self::projdir() { + let mut store_path: PathBuf = dirs.data_local_dir().into(); + store_path.push("store.msgpack"); + + if !store_path.exists() { + warn!( + "The Beatbär store at '{}' does not exist. Loading an empty store instead.", + store_path.as_path().to_string_lossy() + ); + return Ok(Self::default()); + } else if !store_path.is_file() { + let e = Error::BadStoreFile(store_path); + error!("{e}"); + return Err(e); + } + + let file = File::options().read(true).write(false).open(store_path)?; + let store = rmp_serde::from_read(file)?; + info!("store file was loaded"); + debug!("Store: {store:#?}"); + Ok(store) + } else { + let e = Error::NoProjDir; + error!("{}", e); + std::process::exit(1); + } } }