diff --git a/README.md b/README.md index 7fc78ae..da3b2f8 100755 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/edit.rs b/src/edit.rs index c94f82d..1894c86 100755 --- a/src/edit.rs +++ b/src/edit.rs @@ -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 { diff --git a/src/main.rs b/src/main.rs index d665313..b5515dc 100755 --- a/src/main.rs +++ b/src/main.rs @@ -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)?; diff --git a/src/tasks.rs b/src/tasks.rs index 03d909b..08e1b5d 100755 --- a/src/tasks.rs +++ b/src/tasks.rs @@ -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, 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, tags : Vec, dependencies : Vec, priority : Option, due : Option, vault_folder : &path::Path, state : &mut state::State) -> Result { - - // 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, tags : Vec, dependencies : Vec, priority : Option, due : Option, vault_folder : &path::Path, state : &mut state::State) -> Result { // 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(()) }