|
|
|
@ -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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|