2022-08-21 06:43:42 +00:00
|
|
|
use crate::error;
|
|
|
|
use crate::tasks;
|
2022-08-21 07:14:26 +00:00
|
|
|
use crate::colour;
|
2022-08-21 06:43:42 +00:00
|
|
|
use crate::tasks::Id;
|
|
|
|
|
2022-08-20 04:01:43 +00:00
|
|
|
use std::fs;
|
|
|
|
use std::path;
|
|
|
|
use std::io;
|
|
|
|
use std::io::{Write, Seek};
|
2022-08-21 06:43:42 +00:00
|
|
|
use std::collections::HashMap;
|
2022-08-20 04:01:43 +00:00
|
|
|
|
2022-08-21 06:43:42 +00:00
|
|
|
use serde_with::{serde_as, DisplayFromStr};
|
2022-08-20 04:01:43 +00:00
|
|
|
|
|
|
|
pub struct State {
|
|
|
|
file : fs::File,
|
|
|
|
pub data : InternalState,
|
|
|
|
}
|
|
|
|
|
2022-08-21 06:43:42 +00:00
|
|
|
#[serde_as]
|
2022-08-20 04:01:43 +00:00
|
|
|
#[derive(serde::Serialize, serde::Deserialize)]
|
|
|
|
pub struct InternalState {
|
|
|
|
pub next_id : Id,
|
2022-08-21 06:43:42 +00:00
|
|
|
#[serde_as(as = "HashMap<DisplayFromStr, _>")]
|
|
|
|
pub index : HashMap<String, Vec<Id>>,
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl State {
|
|
|
|
/// This function should be called after creating or checking that the "notes" folder exists.
|
|
|
|
pub fn load(vault_location : &path::Path) -> Result<Self, error::Error> {
|
|
|
|
let path = vault_location.join("state.toml");
|
|
|
|
|
|
|
|
if path.exists() && path.is_file() {
|
|
|
|
// Read file before opening (and truncating).
|
|
|
|
let contents = fs::read_to_string(&path)?;
|
|
|
|
|
|
|
|
let file = fs::File::options()
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.open(&path)?;
|
|
|
|
|
|
|
|
let data = toml::from_str::<InternalState>(&contents)?;
|
|
|
|
|
|
|
|
Ok(Self {
|
|
|
|
file,
|
|
|
|
data,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
|
2022-08-21 06:43:42 +00:00
|
|
|
// Calculating the next ID if necessary.
|
2022-08-20 04:01:43 +00:00
|
|
|
let mut max_id : i128 = -1;
|
|
|
|
for id in vault_location.join("notes").read_dir()?.filter_map(|p| p.ok()).map(|p| p.path()).filter(|p| p.extension().map(|s| s.to_str()) == Some(Some("toml"))).filter_map(|p| p.file_stem().map(|x| x.to_str().map(|y| y.to_string()))).flatten().filter_map(|p| p.parse::<Id>().ok()) {
|
|
|
|
|
|
|
|
if i128::try_from(id).unwrap() > max_id {
|
|
|
|
max_id = i128::from(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-21 06:43:42 +00:00
|
|
|
// Calculating out the index.
|
|
|
|
let tasks = tasks::Task::load_all(vault_location, true)?;
|
|
|
|
let mut index : HashMap<String, Vec<Id>> = HashMap::with_capacity(tasks.len());
|
|
|
|
for task in tasks {
|
|
|
|
match index.get_mut(&task.data.name) {
|
|
|
|
Some(ids) => {
|
|
|
|
ids.push(task.data.id);
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
index.insert(task.data.name.clone(), vec![task.data.id]);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-20 04:01:43 +00:00
|
|
|
let data = InternalState {
|
|
|
|
next_id : u64::try_from(max_id + 1).unwrap(),
|
2022-08-21 06:43:42 +00:00
|
|
|
index,
|
2022-08-20 04:01:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let mut file = fs::File::options()
|
|
|
|
.write(true)
|
|
|
|
.create(true)
|
|
|
|
.open(&path)?;
|
|
|
|
|
2022-08-22 10:27:17 +00:00
|
|
|
let file_contents = toml::to_string(&data)?;
|
|
|
|
|
2022-08-20 04:01:43 +00:00
|
|
|
file.set_len(0)?;
|
|
|
|
file.seek(io::SeekFrom::Start(0))?;
|
2022-08-22 10:27:17 +00:00
|
|
|
file.write_all(file_contents.as_bytes())?;
|
2022-08-20 04:01:43 +00:00
|
|
|
|
|
|
|
let task = Self {
|
|
|
|
file,
|
|
|
|
data,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(task)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save(self) -> Result<(), error::Error> {
|
|
|
|
|
|
|
|
let Self {
|
|
|
|
mut file,
|
|
|
|
data,
|
|
|
|
} = self;
|
|
|
|
|
2022-08-22 10:27:17 +00:00
|
|
|
let file_contents = toml::to_string(&data)?;
|
|
|
|
|
2022-08-20 04:01:43 +00:00
|
|
|
file.set_len(0)?;
|
|
|
|
file.seek(io::SeekFrom::Start(0))?;
|
2022-08-22 10:27:17 +00:00
|
|
|
file.write_all(file_contents.as_bytes())?;
|
2022-08-20 04:01:43 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2022-08-21 06:43:42 +00:00
|
|
|
|
|
|
|
pub fn index_insert(&mut self, name : String, id : Id) {
|
|
|
|
match self.data.index.get_mut(&name) {
|
|
|
|
Some(ids) => {
|
|
|
|
ids.push(id);
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
self.data.index.insert(name, vec![id]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn index_remove(&mut self, name : String, id : Id) {
|
|
|
|
if let Some(mut ids) = self.data.index.remove(&name) {
|
|
|
|
if let Some(index) = ids.iter().position(|i| i == &id) {
|
|
|
|
ids.swap_remove(index);
|
|
|
|
|
|
|
|
if !ids.is_empty() {
|
|
|
|
self.data.index.insert(name, ids);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-21 07:14:26 +00:00
|
|
|
|
|
|
|
pub fn name_or_id_to_id(&self, name : &String) -> Result<Id, error::Error> {
|
|
|
|
match name.parse::<Id>() {
|
|
|
|
Ok(id) => Ok(id),
|
|
|
|
Err(_) => {
|
|
|
|
match self.data.index.get(name) {
|
|
|
|
Some(ids) => {
|
|
|
|
if ids.len() == 1 {
|
|
|
|
Ok(ids[0])
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
let coloured_ids : Vec<_> =
|
|
|
|
ids.into_iter()
|
|
|
|
.map(|i| colour::id(&i.to_string()))
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
let mut display_ids = String::new();
|
|
|
|
|
|
|
|
for id in coloured_ids {
|
|
|
|
display_ids.push_str(&format!("{}, ", id));
|
|
|
|
}
|
|
|
|
|
|
|
|
if !display_ids.is_empty() {
|
|
|
|
display_ids.pop();
|
|
|
|
display_ids.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(error::Error::Generic(format!("Multiple notes (Ids: [{}]) by that name exist", display_ids)))
|
|
|
|
}
|
|
|
|
},
|
|
|
|
None => Err(error::Error::Generic(format!("A note by the name {} does not exist", colour::task_name(&name)))),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-20 04:01:43 +00:00
|
|
|
}
|