moved the enforcing of some invariants to be done on save

This commit is contained in:
aaron-jack-manning 2022-09-10 16:14:05 +10:00
parent 5bb20e6166
commit 5361ea4630
4 changed files with 37 additions and 40 deletions

View File

@ -79,10 +79,3 @@ Then you can run `toru new` to create your first task.
## Backup and Syncing
Toru stores tasks and other metadata locally in the folder of the vault in the interest of making that data easily accessible for backups, syncing across devices, and to be easy to export from. However, because Toru uses sequential IDs, sharing a vault across computers asynchronously, such as with Git, can cause different notes to be in conflict with each other. If all vault metadata is synced across devices completely before use, such conflicts can be avoided.
---
## Planned Changes
- Validate invariants at the point of saving, to create consistency across creating and editing notes.
- Convenient options with the edit command so that editing the raw file isn't the only option

View File

@ -85,10 +85,6 @@ pub fn edit_raw(id : Id, vault_folder : path::PathBuf, editor : &str, state : &m
if edited_task.data.id != task.data.id {
Err(error::Error::Generic(String::from("You cannot change the ID of a task in a direct edit")))
}
// Enforce non numeric name invariant.
else if edited_task.data.name.chars().all(|c| c.is_numeric()) {
Err(error::Error::Generic(String::from("Name must not be purely numeric")))
}
else {
// Dependencies were edited so the graph needs to be updated.
if edited_task.data.dependencies != task.data.dependencies {

View File

@ -119,8 +119,8 @@ fn program() -> Result<(), error::Error> {
match command {
Command::New { name, info, tag, dependency, priority, due } => {
let task = tasks::Task::new(name, info, tag, dependency, priority, due, vault_folder, &mut state)?;
println!("Created task {} (ID: {})", format::task(&task.data.name), format::id(task.data.id));
let id = tasks::Task::new(name.clone(), info, tag, dependency, priority, due, vault_folder, &mut state)?;
println!("Created task {} (ID: {})", format::task(&name), format::id(id));
},
Command::Delete { id_or_name } => {
let id = state.data.index.lookup(&id_or_name)?;

View File

@ -16,7 +16,8 @@ pub type Id = u64;
pub struct Task {
pub path : path::PathBuf,
file : fs::File,
// This should only be None for a new task, in which case it should be written from the path.
file : Option<fs::File>,
pub data : InternalTask,
}
@ -223,12 +224,7 @@ impl TimeEntry {
impl Task {
/// Creates a new task from the input data.
pub fn new(name : String, info : Option<String>, tags : Vec<String>, dependencies : Vec<Id>, priority : Option<Priority>, due : Option<chrono::NaiveDateTime>, vault_folder : &path::Path, state : &mut state::State) -> Result<Self, error::Error> {
// Exclude numeric names in the interest of allowing commands that take in ID or name.
if name.chars().all(|c| c.is_numeric()) {
return Err(error::Error::Generic(String::from("Name must not be purely numeric")));
};
pub fn new(name : String, info : Option<String>, tags : Vec<String>, dependencies : Vec<Id>, priority : Option<Priority>, due : Option<chrono::NaiveDateTime>, vault_folder : &path::Path, state : &mut state::State) -> Result<Id, error::Error> {
// Update the state with the new next Id.
let id = state.data.next_id;
@ -236,11 +232,6 @@ impl Task {
let path = vault_folder.join("tasks").join(&format!("{}.toml", id));
let mut file = fs::File::options()
.write(true)
.create(true)
.open(&path)?;
// Adding to dependency graph appropriately.
state.data.deps.insert_node(id);
if !dependencies.is_empty() {
@ -267,19 +258,17 @@ impl Task {
completed : None,
};
let file_contents = toml::to_string(&data)?;
file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?;
file.write_all(file_contents.as_bytes())?;
state.data.index.insert(data.name.clone(), id);
Ok(Task {
let task = Task {
path,
file,
file : None,
data,
})
};
task.save()?;
Ok(id)
}
/// Loads a task directly from its path, for use with the temporary edit file.
@ -300,7 +289,7 @@ impl Task {
Ok(Self {
path,
file,
file : Some(file),
data,
})
}
@ -360,17 +349,36 @@ impl Task {
/// Saves the in memory task data to the corresponding file.
pub fn save(self) -> Result<(), error::Error> {
// Enforce any additional invariants which need to be checked for both edits and now tasks
// at the point of save.
{
// Exclude numeric names in the interest of allowing commands that take in ID or name.
if self.data.name.chars().all(|c| c.is_numeric()) {
return Err(error::Error::Generic(String::from("Name must not be purely numeric")));
};
}
let Self {
path : _,
mut file,
path,
file,
data,
} = self;
let file_contents = toml::to_string(&data)?;
file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?;
file.write_all(file_contents.as_bytes())?;
// Check if the file exists, if not it is a new task and the file must be written from the
// path.
match file {
Some(mut file) => {
file.set_len(0)?;
file.seek(io::SeekFrom::Start(0))?;
file.write_all(file_contents.as_bytes())?;
},
None => {
fs::write(path, file_contents.as_bytes())?;
}
}
Ok(())
}