You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
8.6 KiB
284 lines
8.6 KiB
use std::collections::HashMap;
|
|
use std::error::Error;
|
|
use std::fs::DirEntry;
|
|
use std::path::PathBuf;
|
|
|
|
use directories::ProjectDirs;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::config::Config;
|
|
use crate::plan::{convert_old, Plan, PlanMetadata};
|
|
use crate::time::{RunHistory, RunHistoryMetadata};
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct Storage {
|
|
pub config: Config,
|
|
pub last_plan: Option<PathBuf>,
|
|
}
|
|
|
|
const QUALIFIER: &'static str = "me";
|
|
const ORGANIZATION: &'static str = "isark.poe";
|
|
const APPLICATION: &'static str = "Nothing";
|
|
const CONFIG_FILE: &'static str = "configuration.json";
|
|
const HISTORY_CACHE_FILE: &'static str = "history_cache.json";
|
|
const SAVED_PLANS: &'static str = "plans";
|
|
const SAVED_HISTORIES: &'static str = "histories";
|
|
|
|
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"))
|
|
.ok();
|
|
}
|
|
|
|
let dir_structure: Option<PathBuf> = Storage::history_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"))
|
|
.ok();
|
|
}
|
|
}
|
|
|
|
impl Default for Storage {
|
|
fn default() -> Self {
|
|
mkdir_for_storage();
|
|
let storage = Self::load_storage();
|
|
match storage {
|
|
Some(storage) => storage,
|
|
None => Self {
|
|
config: Default::default(),
|
|
last_plan: None,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Storage {
|
|
pub fn proj_dir() -> Option<ProjectDirs> {
|
|
ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION)
|
|
}
|
|
|
|
pub fn plan_dir() -> Option<PathBuf> {
|
|
Some(Self::proj_dir()?.data_dir().join(SAVED_PLANS))
|
|
}
|
|
|
|
pub fn history_dir() -> Option<PathBuf> {
|
|
Some(Self::proj_dir()?.data_dir().join(SAVED_HISTORIES))
|
|
}
|
|
|
|
pub fn config_file() -> Option<PathBuf> {
|
|
Some(Self::proj_dir()?.data_dir().join(CONFIG_FILE))
|
|
}
|
|
|
|
pub fn history_cache_file() -> Option<PathBuf> {
|
|
Some(Self::proj_dir()?.data_dir().join(HISTORY_CACHE_FILE))
|
|
}
|
|
|
|
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_at_path(path: PathBuf, save_local: bool) -> Option<Plan> {
|
|
let mut plan: Plan = match serde_json::from_str(&std::fs::read_to_string(&path).ok()?).ok() {
|
|
Some(plan) => plan,
|
|
None => convert_old(path.clone())?,
|
|
};
|
|
|
|
if save_local {
|
|
match Self::save_plan_at_store_path(path.file_name()?.to_str()?, plan.clone(), false) {
|
|
Ok(path) => plan.set_stored_path(Some(path)),
|
|
Err(_e) => {
|
|
log::error!("Could not save plan at store path during load");
|
|
}
|
|
}
|
|
}
|
|
|
|
//QoL touch file to put recent plans at top! :D
|
|
match std::fs::File::open(&path) {
|
|
Ok(file) => {
|
|
file.set_modified(std::time::SystemTime::now()).ok();
|
|
}
|
|
Err(_) => (),
|
|
}
|
|
|
|
Some(plan)
|
|
}
|
|
|
|
pub fn save_plan_at_store_path(
|
|
file_name: &str,
|
|
mut plan: Plan,
|
|
allow_overwrite: bool,
|
|
) -> Result<PathBuf, Box<dyn Error>> {
|
|
let plan_dir = match Self::plan_dir() {
|
|
Some(dir) => dir,
|
|
None => return Err("No plan dir".into()),
|
|
};
|
|
|
|
let file_path = plan_dir.join(file_name).with_extension("json");
|
|
|
|
//Disallow overwriting.
|
|
if !allow_overwrite && file_path.exists() {
|
|
return Err("File already exists".into());
|
|
}
|
|
|
|
if allow_overwrite && file_path.exists() {
|
|
log::info!("Overwriting plan : {file_path:?}");
|
|
}
|
|
|
|
std::fs::write(&file_path, serde_json::to_string(&plan)?)?;
|
|
|
|
Ok(file_path.clone())
|
|
}
|
|
|
|
pub fn enumerate_plans() -> Vec<PlanMetadata> {
|
|
let plan_dir = match Self::plan_dir() {
|
|
Some(dir) => dir,
|
|
None => return vec![],
|
|
};
|
|
|
|
let mut read_dir: Vec<DirEntry> = match plan_dir.read_dir() {
|
|
Ok(read_dir) => read_dir.filter_map(|v| {
|
|
log::trace!("Read dir: {:?}", v);
|
|
v.ok()
|
|
}).collect(),
|
|
Err(_) => return vec![],
|
|
};
|
|
|
|
read_dir.sort_by_key(|v| v.metadata().ok()?.modified().ok());
|
|
read_dir.reverse();
|
|
|
|
read_dir
|
|
.iter()
|
|
.filter_map(|entry| {
|
|
let path = entry.path();
|
|
|
|
if path.extension()? != "json" {
|
|
return None;
|
|
}
|
|
|
|
Self::load_metadata_at_path(path)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
//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
|
|
}
|
|
|
|
fn load_metadata_at_path(path: PathBuf) -> Option<PlanMetadata> {
|
|
|
|
let mut plan: PlanMetadata = match serde_json::from_str(&std::fs::read_to_string(&path).ok()?) {
|
|
Ok(plan) => plan,
|
|
Err(e) => {
|
|
log::error!("Could not load metadata at path: {path:?} : {e}");
|
|
return None;
|
|
}
|
|
};
|
|
plan.set_stored_path(Some(path));
|
|
Some(plan)
|
|
}
|
|
|
|
pub fn save_history(history: RunHistory) {
|
|
Self::store_history_at_path(&history);
|
|
Self::store_history_at_cache(history);
|
|
}
|
|
|
|
pub fn load_history_at_uuid(uuid: String) -> Option<RunHistory> {
|
|
serde_json::from_str(&std::fs::read_to_string(Self::history_uuid_to_path(uuid)?).ok()?).ok()
|
|
}
|
|
|
|
pub fn load_cache() -> Option<HashMap<String, RunHistoryMetadata>> {
|
|
let path = match Self::history_cache_file() {
|
|
Some(path) => path,
|
|
None => { log::error!("Could not get path for history cache"); return None; },
|
|
};
|
|
|
|
serde_json::from_str(&std::fs::read_to_string(path).ok()?).ok()
|
|
}
|
|
|
|
fn history_uuid_to_path(uuid: String) -> Option<PathBuf> {
|
|
let history_dir = match Self::history_dir() {
|
|
Some(dir) => dir,
|
|
None => return None,
|
|
};
|
|
|
|
Some(history_dir.join(uuid.to_string()).with_extension("json"))
|
|
}
|
|
|
|
fn store_history_at_path(history: &RunHistory) {
|
|
let path = match Self::history_uuid_to_path(history.uuid()) {
|
|
Some(path) => path,
|
|
None => { log::error!("Could not get path for history"); return; },
|
|
};
|
|
|
|
let serialization_result = match serde_json::to_string(&history) {
|
|
Ok(serialization_result) => serialization_result,
|
|
Err(e) => { log::error!("Could not serialize history: {}", e); return;},
|
|
};
|
|
|
|
std::fs::write(
|
|
path,
|
|
serialization_result,
|
|
).ok();
|
|
}
|
|
|
|
fn store_history_at_cache(history: RunHistory) {
|
|
let mut cache = match Self::load_cache() {
|
|
Some(cache) => cache,
|
|
None => HashMap::new(),
|
|
};
|
|
|
|
cache.insert(history.uuid(), history.into());
|
|
|
|
let path = match Self::history_cache_file() {
|
|
Some(path) => path,
|
|
None => { log::error!("Could not get path for history cache"); return; },
|
|
};
|
|
|
|
let serialization = match serde_json::to_string(&cache) {
|
|
Ok(serialization) => serialization,
|
|
Err(e) => { log::error!("Could not serialize history cache: {}", e); return; },
|
|
};
|
|
|
|
std::fs::write(path, serialization).ok();
|
|
}
|
|
}
|