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!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::collections::HashMap;
use std::sync::mpsc::Receiver;
use std::{path::PathBuf, sync::Mutex};
@ -10,7 +9,7 @@ use crossbeam::channel::Sender;
use notify::PollWatcher;
use overlay::{Event, Overlay};
use plan::Plan;
use plan::{Plan, PlanMetadata};
use poe_data::world_area::WorldAreasMap;
use poe_reader::filter_func;
use poe_reader::{blocking_area_filtered_rx, poe_client_log_receiver};
@ -33,6 +32,12 @@ mod plan;
mod poe_reader;
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]
fn set_interactable(interactable: bool, state: tauri::State<Sender<Event>>) {
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]
fn load_world_areas() -> WorldAreasMap {
log::info!("Loading world areas");
@ -60,35 +59,33 @@ fn load_config(state: tauri::State<Mutex<Storage>>) -> Option<Config> {
}
#[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);
if let Ok(mut storage) = state.lock() {
storage.config = config.clone();
storage.save();
storage.save_config();
}
}
#[tauri::command]
fn load_plan(path: PathBuf, state: tauri::State<Mutex<Storage>>) -> Option<Plan> {
if let Ok(mut storage) = state.lock() {
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
fn enumerate_stored_plans() -> Vec<PlanMetadata> {
Storage::enumerate_plans()
}
#[tauri::command]
fn save_plan(path: PathBuf, plan: Plan) -> bool {
if let Some(path_string) = path.with_extension("json").to_str() {
log::trace!("Attempting to save plan at path {path_string}");
return Storage::save_plan_at_path(path_string, plan).is_ok();
fn load_plan_at_path(path: PathBuf, state: tauri::State<Mutex<Storage>>) -> Option<Plan> {
if !path.exists() {
return None;
}
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]
@ -100,40 +97,6 @@ fn save_plan_at_path(path: PathBuf, plan: Plan) -> bool {
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]
fn base_plan() -> Plan {
const BASE_PLAN_STRING: &str = include_str!("../../data/base_plan.json");
@ -187,14 +150,11 @@ fn main() {
set_interactable,
load_world_areas,
load_config,
set_config,
load_plan,
load_stored_plans,
save_plan,
save_plan_by_name,
update_config,
enumerate_stored_plans,
load_plan_at_path,
save_plan_at_path,
base_plan,
load_previous,
path_for_previous,
])
.system_tray(system_tray)
.on_system_tray_event(|app, event| match event {

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

@ -1,32 +1,65 @@
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 {
plan: Vec<PlanElement>,
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>,
}
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 {
pub fn set_stored_path(&mut self, file: String) {
self.stored_path = Some(file);
pub fn set_stored_path(&mut self, file: PathBuf) {
self.metadata.stored_path = Some(file);
}
}
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PlanElement {
area_key: String,
notes: String,
#[serde(default = "PlanElement::generate_uuid")]
uuid: uuid::Uuid,
#[serde(default = "PlanElement::edited")]
edited: bool,
anchor_act: Option<u8>
anchor_act: Option<u8>,
}
impl PlanElement {
@ -39,12 +72,14 @@ impl PlanElement {
}
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct OldPlanElement {
area: OldArea,
note: String,
}
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
struct OldPlan {
elements: Vec<OldPlanElement>,
@ -56,7 +91,8 @@ struct OldArea {
_rid: usize,
}
pub fn convert_old(path: &str) -> Option<Plan> {
#[allow(dead_code)]
pub fn convert_old(path: PathBuf) -> Option<Plan> {
let plan: OldPlan =
serde_json::from_str(&std::fs::read_to_string(path).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,
})
.collect::<Vec<PlanElement>>(),
metadata: PlanMetadata {
stored_path: None,
update_url: None,
update_url: None,
},
})
}

@ -1,12 +1,12 @@
use std::{
fs::{File, OpenOptions},
fs::OpenOptions,
io::{Read, Seek, SeekFrom},
path::PathBuf,
sync::mpsc::{channel, Receiver, Sender},
time::Duration,
};
use notify::{event, Config, PollWatcher, RecursiveMode, Watcher, Event};
use notify::{Config, PollWatcher, RecursiveMode, Watcher, Event};
use regex::Regex;
#[cfg(target_os = "windows")]
@ -31,7 +31,7 @@ fn share_mode(options: &mut OpenOptions) {
}
#[cfg(not(target_os = "windows"))]
fn share_mode(options: &mut OpenOptions) {}
fn share_mode(_options: &mut OpenOptions) {}
impl LogFileReader {
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
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

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

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

@ -28,7 +28,7 @@ export class ConfigService {
console.error("Could not load config, should generally not happen :')");
return;
}
const _this = this;
this.cfg = cfg;
// Mostly to wrap setters so we can run the (debounced) sync function on any config changes!

@ -39,7 +39,7 @@ export class PlanService {
}
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> {

Loading…
Cancel
Save