Some more backend api improvements. TODO: Adapt frontend usage to this new interface

cleanup
isark 1 year ago
parent 033b061fb6
commit 6282f12ce9

@ -1,7 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!! // Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::collections::HashMap;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::{path::PathBuf, sync::Mutex}; use std::{path::PathBuf, sync::Mutex};
@ -10,7 +9,7 @@ use crossbeam::channel::Sender;
use notify::PollWatcher; use notify::PollWatcher;
use overlay::{Event, Overlay}; use overlay::{Event, Overlay};
use plan::Plan; use plan::{Plan, PlanMetadata};
use poe_data::world_area::WorldAreasMap; use poe_data::world_area::WorldAreasMap;
use poe_reader::filter_func; use poe_reader::filter_func;
use poe_reader::{blocking_area_filtered_rx, poe_client_log_receiver}; use poe_reader::{blocking_area_filtered_rx, poe_client_log_receiver};
@ -33,6 +32,12 @@ mod plan;
mod poe_reader; mod poe_reader;
mod storage; mod storage;
lazy_static! {
static ref WORLD_AREAS_MAP: WorldAreasMap = poe_data::world_area::load_world_areas_map(
include_str!("../../data/processed_world_areas.json")
);
}
#[tauri::command] #[tauri::command]
fn set_interactable(interactable: bool, state: tauri::State<Sender<Event>>) { fn set_interactable(interactable: bool, state: tauri::State<Sender<Event>>) {
if interactable { if interactable {
@ -42,12 +47,6 @@ fn set_interactable(interactable: bool, state: tauri::State<Sender<Event>>) {
} }
} }
lazy_static! {
static ref WORLD_AREAS_MAP: WorldAreasMap = poe_data::world_area::load_world_areas_map(
include_str!("../../data/processed_world_areas.json")
);
}
#[tauri::command] #[tauri::command]
fn load_world_areas() -> WorldAreasMap { fn load_world_areas() -> WorldAreasMap {
log::info!("Loading world areas"); log::info!("Loading world areas");
@ -60,35 +59,33 @@ fn load_config(state: tauri::State<Mutex<Storage>>) -> Option<Config> {
} }
#[tauri::command] #[tauri::command]
fn set_config(config: Config, state: tauri::State<Mutex<Storage>>) { fn update_config(config: Config, state: tauri::State<Mutex<Storage>>) {
log::info!("Saved config: {:?}", config); log::info!("Saved config: {:?}", config);
if let Ok(mut storage) = state.lock() { if let Ok(mut storage) = state.lock() {
storage.config = config.clone(); storage.config = config.clone();
storage.save(); storage.save_config();
} }
} }
#[tauri::command] #[tauri::command]
fn load_plan(path: PathBuf, state: tauri::State<Mutex<Storage>>) -> Option<Plan> { fn enumerate_stored_plans() -> Vec<PlanMetadata> {
if let Ok(mut storage) = state.lock() { Storage::enumerate_plans()
if let Some(path_string) = path.to_str() {
storage.last_plan = Some(path_string.to_string());
let plan = Storage::load_plan(storage.last_plan.as_ref()?);
log::info!("got plan: {plan:?}");
return plan;
}
}
None
} }
#[tauri::command] #[tauri::command]
fn save_plan(path: PathBuf, plan: Plan) -> bool { fn load_plan_at_path(path: PathBuf, state: tauri::State<Mutex<Storage>>) -> Option<Plan> {
if let Some(path_string) = path.with_extension("json").to_str() { if !path.exists() {
log::trace!("Attempting to save plan at path {path_string}"); return None;
return Storage::save_plan_at_path(path_string, plan).is_ok();
} }
false let mut storage = match state.lock() {
Ok(storage) => storage,
Err(_) => return None,
};
storage.last_plan = Some(path.clone());
Storage::load_plan_at_path(path)
} }
#[tauri::command] #[tauri::command]
@ -100,40 +97,6 @@ fn save_plan_at_path(path: PathBuf, plan: Plan) -> bool {
false false
} }
#[tauri::command]
fn save_plan_by_name(name: String, plan: Plan) -> bool {
let path = Storage::create_path_for_name(name);
log::trace!("plan: {plan:?}");
if let Some(path) = path {
return save_plan(path.into(), plan);
}
false
}
#[tauri::command]
fn load_stored_plans() -> HashMap<String, Plan> {
Storage::enumerate_plans()
.into_iter()
.map(|name| (name.clone(), Storage::load_by_name(name).unwrap()))
.collect()
}
#[tauri::command]
fn load_previous(name: String) -> Option<Plan> {
let plan = Storage::load_by_name(name);
log::info!("got plan: {plan:?}");
return plan;
}
#[tauri::command]
fn path_for_previous(prev: String) -> Option<String> {
let path = Storage::path_for_name(prev);
log::info!("got path: {path:?}");
return path;
}
#[tauri::command] #[tauri::command]
fn base_plan() -> Plan { fn base_plan() -> Plan {
const BASE_PLAN_STRING: &str = include_str!("../../data/base_plan.json"); const BASE_PLAN_STRING: &str = include_str!("../../data/base_plan.json");
@ -187,14 +150,11 @@ fn main() {
set_interactable, set_interactable,
load_world_areas, load_world_areas,
load_config, load_config,
set_config, update_config,
load_plan, enumerate_stored_plans,
load_stored_plans, load_plan_at_path,
save_plan, save_plan_at_path,
save_plan_by_name,
base_plan, base_plan,
load_previous,
path_for_previous,
]) ])
.system_tray(system_tray) .system_tray(system_tray)
.on_system_tray_event(|app, event| match event { .on_system_tray_event(|app, event| match event {

@ -39,7 +39,6 @@ pub struct Overlay {
pub struct OverlayData { pub struct OverlayData {
// pub auto_hide: bool, // pub auto_hide: bool,
} }
pub type LockedOverlayData = Mutex<OverlayData>;
fn wrap_underlay_rx(rx: MpscReceiver<UnderlayEvent>) -> Receiver<UnderlayEvent> { fn wrap_underlay_rx(rx: MpscReceiver<UnderlayEvent>) -> Receiver<UnderlayEvent> {
let (cb_underlay_tx, cb_underlay_rx) = crossbeam::channel::unbounded(); let (cb_underlay_tx, cb_underlay_rx) = crossbeam::channel::unbounded();

@ -1,21 +1,54 @@
use std::collections::HashMap; use std::{collections::HashMap, path::PathBuf};
use serde::{Deserialize, Serialize}; use serde::{ser::SerializeStruct, Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Plan { pub struct Plan {
plan: Vec<PlanElement>, plan: Vec<PlanElement>,
current: usize, current: usize,
stored_path: Option<String>, #[serde(flatten)]
metadata: PlanMetadata,
}
#[derive(Deserialize, Debug, Clone)]
pub struct PlanMetadata {
#[serde(skip)]
stored_path: Option<PathBuf>,
update_url: Option<String>, update_url: Option<String>,
} }
impl From<PlanMetadata> for Option<Plan> {
fn from(metadata: PlanMetadata) -> Self {
Some(serde_json::from_slice(&std::fs::read(metadata.stored_path?).ok()?).ok()?)
}
}
impl Serialize for PlanMetadata {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct("PlanMetadata", 2)?;
state.serialize_field("update_url", &self.update_url)?;
if let Some(path) = &self.stored_path {
if let Some(name) = path.file_name() {
state.serialize_field("name", name)?;
}
}
state.end()
}
}
impl Plan { impl Plan {
pub fn set_stored_path(&mut self, file: String) { pub fn set_stored_path(&mut self, file: PathBuf) {
self.stored_path = Some(file); self.metadata.stored_path = Some(file);
} }
} }
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PlanElement { pub struct PlanElement {
area_key: String, area_key: String,
notes: String, notes: String,
@ -26,7 +59,7 @@ pub struct PlanElement {
#[serde(default = "PlanElement::edited")] #[serde(default = "PlanElement::edited")]
edited: bool, edited: bool,
anchor_act: Option<u8> anchor_act: Option<u8>,
} }
impl PlanElement { impl PlanElement {
@ -39,12 +72,14 @@ impl PlanElement {
} }
} }
#[allow(dead_code)]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct OldPlanElement { struct OldPlanElement {
area: OldArea, area: OldArea,
note: String, note: String,
} }
#[allow(dead_code)]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct OldPlan { struct OldPlan {
elements: Vec<OldPlanElement>, elements: Vec<OldPlanElement>,
@ -56,7 +91,8 @@ struct OldArea {
_rid: usize, _rid: usize,
} }
pub fn convert_old(path: &str) -> Option<Plan> { #[allow(dead_code)]
pub fn convert_old(path: PathBuf) -> Option<Plan> {
let plan: OldPlan = let plan: OldPlan =
serde_json::from_str(&std::fs::read_to_string(path).expect("Could not convert old")) serde_json::from_str(&std::fs::read_to_string(path).expect("Could not convert old"))
.expect("could not convert old"); .expect("could not convert old");
@ -82,7 +118,9 @@ pub fn convert_old(path: &str) -> Option<Plan> {
anchor_act: None, anchor_act: None,
}) })
.collect::<Vec<PlanElement>>(), .collect::<Vec<PlanElement>>(),
metadata: PlanMetadata {
stored_path: None, stored_path: None,
update_url: None, update_url: None,
},
}) })
} }

@ -1,12 +1,12 @@
use std::{ use std::{
fs::{File, OpenOptions}, fs::OpenOptions,
io::{Read, Seek, SeekFrom}, io::{Read, Seek, SeekFrom},
path::PathBuf, path::PathBuf,
sync::mpsc::{channel, Receiver, Sender}, sync::mpsc::{channel, Receiver, Sender},
time::Duration, time::Duration,
}; };
use notify::{event, Config, PollWatcher, RecursiveMode, Watcher, Event}; use notify::{Config, PollWatcher, RecursiveMode, Watcher, Event};
use regex::Regex; use regex::Regex;
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
@ -31,7 +31,7 @@ fn share_mode(options: &mut OpenOptions) {
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(target_os = "windows"))]
fn share_mode(options: &mut OpenOptions) {} fn share_mode(_options: &mut OpenOptions) {}
impl LogFileReader { impl LogFileReader {
fn new(file: &PathBuf, tx: Sender<String>) -> Result<Self, std::io::Error> { fn new(file: &PathBuf, tx: Sender<String>) -> Result<Self, std::io::Error> {
@ -117,6 +117,7 @@ fn entered_filtered_rx<'a>(rx: &'a Receiver<String>) -> impl Iterator<Item = Str
}) })
} }
#[allow(dead_code)]
/// Returns an iterator of strings with the id /// Returns an iterator of strings with the id
pub fn area_filtered_rx<'a>(rx: &'a Receiver<String>) -> impl Iterator<Item = String> + 'a { pub fn area_filtered_rx<'a>(rx: &'a Receiver<String>) -> impl Iterator<Item = String> + 'a {
// Generating level 68 area "1_SideArea5_6" with seed 4103532853 // Generating level 68 area "1_SideArea5_6" with seed 4103532853

@ -1,16 +1,16 @@
use std::error::Error; use std::error::Error;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use directories::ProjectDirs; use directories::ProjectDirs;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::config::Config; use crate::config::Config;
use crate::plan::{convert_old, Plan}; use crate::plan::{convert_old, Plan, PlanMetadata};
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Storage { pub struct Storage {
pub config: Config, pub config: Config,
pub last_plan: Option<String>, pub last_plan: Option<PathBuf>,
} }
const QUALIFIER: &'static str = "me"; const QUALIFIER: &'static str = "me";
@ -19,23 +19,19 @@ const APPLICATION: &'static str = "Nothing";
const CONFIG_FILE: &'static str = "configuration.json"; const CONFIG_FILE: &'static str = "configuration.json";
const SAVED_PLANS: &'static str = "plans"; const SAVED_PLANS: &'static str = "plans";
fn proj_dir() -> Option<ProjectDirs> { fn mkdir_for_storage() {
ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION) let dir_structure = Storage::plan_dir();
} if let Some(dir_structure) = dir_structure {
fn mkdir() {
if let Some(proj_dir) = proj_dir() {
let dir_structure = proj_dir.data_dir().join(SAVED_PLANS);
std::fs::create_dir_all(dir_structure) std::fs::create_dir_all(dir_structure)
.map_err(|e| log::error!("Could not create directory for storing config and saves")) .map_err(|_e| log::error!("Could not create directory for storing config and saves"))
.ok(); .ok();
} }
} }
impl Default for Storage { impl Default for Storage {
fn default() -> Self { fn default() -> Self {
mkdir(); mkdir_for_storage();
let storage = Self::load(); let storage = Self::load_storage();
match storage { match storage {
Some(storage) => storage, Some(storage) => storage,
None => Self { None => Self {
@ -47,113 +43,125 @@ impl Default for Storage {
} }
impl Storage { impl Storage {
fn load() -> Option<Self> { pub fn proj_dir() -> Option<ProjectDirs> {
let storage: Option<Storage> = serde_json::from_str( ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
&std::fs::read_to_string(proj_dir()?.data_dir().join(CONFIG_FILE)).ok()?, }
)
.ok();
log::info!("Loaded storage: {:?}", storage); pub fn plan_dir() -> Option<PathBuf> {
Some(Self::proj_dir()?.data_dir().join(SAVED_PLANS))
}
storage pub fn config_file() -> Option<PathBuf> {
Some(Self::proj_dir()?.data_dir().join(CONFIG_FILE))
} }
pub fn save(&self) { pub fn save_config(&self) {
if let Ok(content) = serde_json::to_string_pretty(&self) { let content = match serde_json::to_string_pretty(&self) {
if let Some(dir) = proj_dir() { Ok(content) => content,
match std::fs::write(dir.data_dir().join(CONFIG_FILE), content) { Err(_) => return,
};
let config_file = match Self::config_file() {
Some(config_file) => config_file,
None => return,
};
match std::fs::write(&config_file, content) {
Ok(_) => { Ok(_) => {
if let Some(c) = dir.data_dir().join(CONFIG_FILE).to_str() { if let Some(c) = config_file.to_str() {
log::info!("Saved config to {}", c) log::info!("Saved config to {c}");
} }
} }
Err(_) => log::error!("Could not write config"), Err(_) => log::error!("Could not write config"),
} }
} }
}
}
pub fn save_plan_at_path<T: Into<String>>(file: T, plan: Plan) -> Result<(), Box<dyn Error>> { pub fn save_plan_at_path<T: Into<String>>(file: T, plan: Plan) -> Result<(), Box<dyn Error>> {
std::fs::write(&file.into(), serde_json::to_string(&plan)?)?; std::fs::write(&file.into(), serde_json::to_string(&plan)?)?;
Ok(()) Ok(())
} }
pub fn load_plan<T: Into<String>>(file: T) -> Option<Plan> { pub fn load_plan_at_path(path: PathBuf) -> Option<Plan> {
let mut file = file.into(); log::trace!("Loading plan: {path:?}");
let plan_file = Path::new(&file); let plan: Plan = match serde_json::from_str(&std::fs::read_to_string(&path).ok()?).ok() {
//basically copy to our own dir to store if for reuse without contaminating or depending on the original file Some(plan) => plan,
if let Some(path) = plan_file.parent() { None => convert_old(path.clone())?,
if !path.ends_with(SAVED_PLANS) { };
if let Some(proj_dir) = proj_dir() {
if let Some(file_name) = plan_file.file_name() { match Self::save_plan_at_store_path(path.file_name()?.to_str()?, plan.clone()) {
let dest = proj_dir.data_dir().join(SAVED_PLANS).join(file_name); Ok(_) => (),
let copy_result = std::fs::copy(&file, &dest); Err(_e) => {
match copy_result { log::error!("Could not save plan at store path during load");
Ok(_) => { },
file = dest.to_str()?.to_string();
}
Err(e) => log::error!("Could not store plan file {e:?}"),
}
}
}
}
} }
log::info!("Loading plan: {file:?}");
let mut plan = if let Some(plan) = serde_json::from_str(&std::fs::read_to_string(&file).ok()?).ok() {
plan
} else {
log::info!("Attempting to convert old");
let plan = convert_old(&file)?;
std::fs::write(
&file,
serde_json::to_string(&plan)
.map_err(|e| log::error!("Could not serialize converted plan to json {e:?}"))
.ok()?,
)
.map_err(|e| "Could not write converted plan to storage")
.ok();
Some(plan) Some(plan)
}
pub fn save_plan_at_store_path(file_name: &str, mut plan: Plan) -> Result<(), Box<dyn Error>> {
let plan_dir = match Self::plan_dir() {
Some(dir) => dir,
None => return Err("No plan dir".into()),
}; };
if let Some(plan) = &mut plan { let file_path = plan_dir.with_file_name(file_name).with_extension("json");
plan.set_stored_path(file);
}
plan //Disallow overwriting.
if file_path.exists() {
return Err("File already exists".into());
} }
pub fn load_by_name<T: Into<String>>(file: T) -> Option<Plan> { plan.set_stored_path(file_path.clone());
let file = file.into();
let file = proj_dir()?.data_dir().join(SAVED_PLANS).join(file);
log::info!("Loading plan: {file:?}"); std::fs::write(file_path, serde_json::to_string(&plan)?)?;
serde_json::from_str(&std::fs::read_to_string(&file).ok()?).ok() Ok(())
} }
pub fn enumerate_plans() -> Vec<String> { pub fn enumerate_plans() -> Vec<PlanMetadata> {
if let Some(proj_dir) = proj_dir() { let plan_dir = match Self::plan_dir() {
if let Ok(read_dir) = proj_dir.data_dir().join(SAVED_PLANS).read_dir() { Some(dir) => dir,
return read_dir None => return vec![],
.filter_map(|entry| Some(entry.ok()?.path().file_name()?.to_str()?.to_string())) };
.collect::<Vec<String>>();
} let read_dir = match plan_dir.read_dir() {
Ok(read_dir) => read_dir,
Err(_) => return vec![],
};
read_dir
.filter_map(|entry| {
let path = entry.ok()?.path();
if path.extension()? != "json" {
return None;
} }
return vec![]; Self::load_metadata_at_path(path)
})
.collect()
} }
pub fn path_for_name<T: Into<String>>(file: T) -> Option<String> { //TODO: Remove if this turns out to be unnecessary.
let file = file.into(); // pub fn create_path_for_local_plan(name: &str, plan: &Plan) -> Option<String> {
let file = proj_dir()?.data_dir().join(SAVED_PLANS).join(file); // let file: PathBuf = Self::plan_dir()?.join(name).with_extension("json");
Some(file.to_str()?.to_string())
// Some(file.to_str()?.to_string())
// }
fn load_storage() -> Option<Self> {
let storage: Option<Storage> = serde_json::from_str(
&std::fs::read_to_string(Self::proj_dir()?.data_dir().join(CONFIG_FILE)).ok()?,
)
.ok();
log::trace!("Loaded storage: {:?}", storage);
storage
} }
pub fn create_path_for_name<T: Into<String>>(file_name: T) -> Option<String> { fn load_metadata_at_path(path: PathBuf) -> Option<PlanMetadata> {
let file_name = file_name.into(); serde_json::from_str(&std::fs::read_to_string(&path).ok()?).ok()
let file: PathBuf = proj_dir()?.data_dir().join(SAVED_PLANS).join(file_name).with_extension("json");
Some(file.to_str()?.to_string())
} }
} }

@ -9,6 +9,10 @@ export interface PlanInterface {
name?: string; name?: string;
} }
export interface PlanMetadata {
}
export class Plan { export class Plan {
plan: PlanElement[]; plan: PlanElement[];
current: number; current: number;

@ -39,7 +39,7 @@ export class PlanService {
} }
public loadPlanFromPath(path: string): Observable<Plan> { public loadPlanFromPath(path: string): Observable<Plan> {
return from(invoke<Plan>('load_plan', { path })); return from(invoke<Plan>('load_plan_at_path', { path }));
} }
public loadFromUrl(url?: string, name?: string): Observable<Plan> { public loadFromUrl(url?: string, name?: string): Observable<Plan> {

Loading…
Cancel
Save