Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src-tauri/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub enum Error {
path: PathBuf,
source: Box<dyn std::error::Error>,
},
FileWrites(Vec<(PathBuf, Box<dyn std::error::Error>)>),
FileMissing {
path: PathBuf,
},
Expand Down Expand Up @@ -136,6 +137,18 @@ impl Display for Error {
"File could not be written: '{}' ({source})",
path.to_string_lossy()
),
Self::FileWrites(errors) => {
let mut msg = format!("{} file write errors:", errors.len());

errors.iter().for_each(|(path, source)| {
msg.push_str(&format!(
"\nFile could not be written: '{}' ({source})",
path.to_string_lossy()
))
});

msg
}
Self::FileMissing { path } => {
format!("File does not exist: '{}'", path.to_string_lossy())
}
Expand Down
23 changes: 20 additions & 3 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::env;
use tauri::Manager;
use tauri_plugin_dialog::{DialogExt, MessageDialogKind};

use crate::error::Error;
use crate::{error::Error, state::shared_state::AllSharedState};

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
Expand All @@ -38,7 +38,20 @@ pub fn run() {
std::process::exit(1);
};

let lookup_state = match state::LookupState::load_from_storage(app.handle()) {
let ohpkm_store = match state::OhpkmBytesStore::load_from_mons_v2(app.handle()) {
Ok(state) => state,
Err(err) => {
app.dialog()
.message(err.to_string())
.title("OpenHome Failed to Launch - OHPKM load error")
.kind(MessageDialogKind::Error)
.blocking_show();
app.handle().exit(1);
std::process::exit(1);
}
};

let lookup_state_inner = match state::LookupState::load_from_storage(app.handle()) {
Ok(lookup) => lookup,
Err(err) => {
app.dialog()
Expand All @@ -50,7 +63,9 @@ pub fn run() {
std::process::exit(1);
}
};
app.manage(lookup_state);

let shared_state = AllSharedState::from_states(lookup_state_inner, ohpkm_store);
app.manage(shared_state);

let pokedex_state = match state::PokedexState::load_from_storage(app.handle()) {
Ok(pokedex) => pokedex,
Expand Down Expand Up @@ -110,6 +125,8 @@ pub fn run() {
pkm_storage::write_banks,
state::get_lookups,
state::update_lookups,
state::get_ohpkm_store,
state::update_ohpkm_store,
state::get_pokedex,
state::update_pokedex,
state::start_transaction,
Expand Down
73 changes: 25 additions & 48 deletions src-tauri/src/state/lookup.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,58 @@
use std::{collections::HashMap, ops::Deref, sync::Mutex};
use std::collections::HashMap;

use serde::Serialize;
use tauri::Emitter;

use crate::error::{Error, Result};
use crate::error::Result;
use crate::state::shared_state::{self, AllSharedState};
use crate::util;

// type OhpkmBytesLookup = HashMap<String, Vec<u8>>;
type IdentifierLookup = HashMap<String, String>;

#[derive(Default, Serialize)]
pub struct LookupState(pub Mutex<LookupStateInner>);

impl Deref for LookupState {
type Target = Mutex<LookupStateInner>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl LookupState {
pub fn load_from_storage(app_handle: &tauri::AppHandle) -> Result<Self> {
let inner = LookupStateInner::load_from_storage(app_handle)?;
Ok(Self(Mutex::new(inner)))
}
}

#[derive(Default, Serialize, Clone)]
#[derive(Default, Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct LookupStateInner {
// pub openhome: OhpkmBytesLookup,
pub struct LookupState {
gen_12: IdentifierLookup,
gen_345: IdentifierLookup,
}

impl LookupStateInner {
fn load_from_storage(app_handle: &tauri::AppHandle) -> Result<Self> {
impl LookupState {
pub fn from_components(gen_12: IdentifierLookup, gen_345: IdentifierLookup) -> Self {
Self { gen_12, gen_345 }
}

pub fn load_from_storage(app_handle: &tauri::AppHandle) -> Result<Self> {
Ok(Self {
gen_12: util::get_storage_file_json(app_handle, "gen12_lookup.json")?,
gen_345: util::get_storage_file_json(app_handle, "gen345_lookup.json")?,
})
}

fn update_lookups(
&mut self,
app_handle: &tauri::AppHandle,
gen_12: IdentifierLookup,
gen_345: IdentifierLookup,
) -> Result<()> {
self.gen_12.extend(gen_12);
self.gen_345.extend(gen_345);

fn write_to_files(&self, app_handle: &tauri::AppHandle) -> Result<()> {
util::write_storage_file_json(app_handle, "gen12_lookup.json", &self.gen_12)?;
util::write_storage_file_json(app_handle, "gen345_lookup.json", &self.gen_345)?;

app_handle
.emit("lookups_update", self.clone())
.map_err(|err| {
Error::other_with_source("Could not emit 'lookups_update' to frontend", err)
})
util::write_storage_file_json(app_handle, "gen345_lookup.json", &self.gen_345)
}
}

impl shared_state::SharedState for LookupState {
const ID: &'static str = "lookup";
}

#[tauri::command]
pub fn get_lookups(lookup_state: tauri::State<'_, LookupState>) -> Result<LookupStateInner> {
Ok(lookup_state.lock()?.clone())
pub fn get_lookups(shared_state: tauri::State<'_, AllSharedState>) -> Result<LookupState> {
shared_state.clone_lookups()
}

#[tauri::command]
pub fn update_lookups(
app_handle: tauri::AppHandle,
lookup_state: tauri::State<'_, LookupState>,
shared_state: tauri::State<'_, AllSharedState>,
gen_12: IdentifierLookup,
gen_345: IdentifierLookup,
) -> Result<()> {
lookup_state
let new_lookups = LookupState::from_components(gen_12, gen_345);
new_lookups.write_to_files(&app_handle)?;

shared_state
.lock()?
.update_lookups(&app_handle, gen_12, gen_345)
.update_lookups(&app_handle, |_| new_lookups)
}
4 changes: 4 additions & 0 deletions src-tauri/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
mod app_state;
mod lookup;
mod ohpkm_store;
mod pokedex;

pub use app_state::*;
pub use lookup::*;
pub use ohpkm_store::*;
pub use pokedex::*;

pub mod shared_state;
102 changes: 102 additions & 0 deletions src-tauri/src/state/ohpkm_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use crate::error::{Error, Result};
use crate::{state::shared_state, util};
use base64::prelude::*;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use std::{collections::HashMap, fs};

#[derive(Default, Serialize, Deserialize, Clone)]
pub struct OhpkmBytesStore(HashMap<String, Vec<u8>>);

impl OhpkmBytesStore {
fn load_from_directory(path: &Path) -> Result<Self> {
let mon_files = fs::read_dir(path).map_err(|e| Error::file_access(&path, e))?;

let mut map = HashMap::new();
for mon_file_os_str in mon_files.flatten() {
let path = mon_file_os_str.path();
if !path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("ohpkm"))
{
continue;
}

if let Ok(mon_bytes) = util::read_file_bytes(path) {
let mon_identifier = mon_file_os_str
.file_name()
.to_string_lossy()
.trim_end_matches(".ohpkm")
.to_owned();
map.insert(mon_identifier, mon_bytes);
}
}

Ok(Self(map))
}

fn write_to_directory(data: &Self, path: &Path) -> Result<()> {
let mut errors: Vec<(PathBuf, Box<dyn std::error::Error>)> = Vec::new();
data.0.iter().for_each(|(identifier, bytes)| {
let filename = format!("{identifier}.ohpkm");
let file_path = path.join(filename);
if let Err(err) = fs::write(&file_path, bytes) {
errors.push((file_path, Box::new(err)));
};
});

if !errors.is_empty() {
Err(Error::FileWrites(errors))
} else {
Ok(())
}
}

pub fn load_from_mons_v2(app_handle: &tauri::AppHandle) -> Result<Self> {
let mons_v2_dir = util::prepend_appdata_storage_to_path(app_handle, "mons_v2")?;
Self::load_from_directory(&mons_v2_dir)
}

pub fn write_to_mons_v2(data: &Self, app_handle: &tauri::AppHandle) -> Result<()> {
let mons_v2_dir = util::prepend_appdata_storage_to_path(app_handle, "mons_v2")?;
Self::write_to_directory(data, &mons_v2_dir)
}

pub fn to_b64_map(&self) -> HashMap<String, String> {
let mut output: HashMap<String, String> = HashMap::new();
for (k, v) in self.0.clone() {
output.insert(k, BASE64_STANDARD.encode(v));
}

output
}
}

impl shared_state::SharedState for OhpkmBytesStore {
const ID: &'static str = "ohpkm_store";
}

#[tauri::command]
pub fn get_ohpkm_store(
shared_state: tauri::State<'_, shared_state::AllSharedState>,
) -> Result<HashMap<String, String>> {
shared_state.ohpkm_store_b64()
}

#[tauri::command]
pub fn update_ohpkm_store(
app_handle: tauri::AppHandle,
shared_state: tauri::State<'_, shared_state::AllSharedState>,
updates: OhpkmBytesStore,
) -> Result<()> {
OhpkmBytesStore::write_to_mons_v2(&updates, &app_handle)?;
shared_state
.lock()?
.update_ohpkm_store(&app_handle, |old_data| {
let mut new_state = old_data.clone();
updates.0.into_iter().for_each(|(k, v)| {
new_state.0.insert(k, v);
});
new_state
})
}
86 changes: 86 additions & 0 deletions src-tauri/src/state/shared_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Mutex;

use serde::Serialize;
use tauri::Emitter;

use crate::error::{Error, Result};
use crate::state::{LookupState, OhpkmBytesStore};

pub trait SharedState: Clone + Serialize + tauri::ipc::IpcResponse {
const ID: &'static str;
}

pub struct SharedStateWrapper<State: SharedState>(State);

impl<State: SharedState> SharedStateWrapper<State> {
pub fn update<F>(&mut self, app_handle: &tauri::AppHandle, update_function: F) -> Result<()>
where
F: FnOnce(&State) -> State,
{
self.0 = update_function(&self.0);

let event = format!("shared_state_update::{}", State::ID);

app_handle.emit(&event, self.0.clone()).map_err(|err| {
Error::other_with_source(&format!("Could not emit '{event}' to frontend"), err)
})
}
}

pub struct AllSharedStateInner {
lookups: SharedStateWrapper<LookupState>,
ohpkm_store: SharedStateWrapper<OhpkmBytesStore>,
}

impl AllSharedStateInner {
pub fn update_lookups<F>(
&mut self,
app_handle: &tauri::AppHandle,
update_function: F,
) -> Result<()>
where
F: FnOnce(&LookupState) -> LookupState,
{
self.lookups.update(app_handle, update_function)
}

pub fn update_ohpkm_store<F>(
&mut self,
app_handle: &tauri::AppHandle,
update_function: F,
) -> Result<()>
where
F: FnOnce(&OhpkmBytesStore) -> OhpkmBytesStore,
{
self.ohpkm_store.update(app_handle, update_function)
}
}

pub struct AllSharedState(pub Mutex<AllSharedStateInner>);

impl AllSharedState {
pub fn from_states(lookups: LookupState, ohpkm_store: OhpkmBytesStore) -> Self {
Self(Mutex::new(AllSharedStateInner {
lookups: SharedStateWrapper(lookups),
ohpkm_store: SharedStateWrapper(ohpkm_store),
}))
}

pub fn clone_lookups(&self) -> Result<LookupState> {
Ok(self.lock()?.lookups.0.clone())
}

pub fn ohpkm_store_b64(&self) -> Result<HashMap<String, String>> {
Ok(self.lock()?.ohpkm_store.0.to_b64_map())
}
}

impl Deref for AllSharedState {
type Target = Mutex<AllSharedStateInner>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
Loading