A file-based task manager

WIP: task stack

+77 -15
+51
src/buffer.rs
···
··· 1 + use std::{ 2 + collections::BinaryHeap, 3 + sync::{Condvar, Mutex}, 4 + }; 5 + struct PooledHandle<'a, T: Ord + Default> { 6 + parent: &'a Pool<T>, 7 + item: Option<T>, 8 + } 9 + 10 + struct Pool<T: Ord + Default>(Mutex<BinaryHeap<T>>, Condvar); 11 + 12 + impl<'a, T: Ord + Default> Pool<T> { 13 + pub fn give(&self, item: T) { 14 + self.0.lock().expect("Poisoned lock").push(item) 15 + } 16 + 17 + /// Retrieves 18 + pub fn take(&self) -> Option<T> { 19 + self.0.lock().expect("Poisoned lock").pop() 20 + } 21 + 22 + pub fn lease(&self) -> PooledHandle<'a, T> { 23 + let guard = self.0.lock().expect("Poisoned lock"); 24 + match guard.pop() { 25 + Some(item) => PooledHandle { 26 + parent: self, 27 + item: Some(item), 28 + }, 29 + None => { 30 + let mut item = None; 31 + while item.is_none() { 32 + let guard = self.1.wait(guard).expect("Poisoned lock"); 33 + item = guard.pop(); 34 + } 35 + 36 + }, 37 + } 38 + } 39 + } 40 + 41 + impl<T: Ord> AsRef<T> for PooledHandle<'_, T> { 42 + fn as_ref(&self) -> &T { 43 + &self.item 44 + } 45 + } 46 + 47 + impl<T: Ord> Drop for PooledHandle<'_, T> { 48 + fn drop(&mut self) { 49 + self.parent.give(self.item) 50 + } 51 + }
+1
src/main.rs
··· 2 mod workspace; 3 mod stack; 4 mod util; 5 use std::path::PathBuf; 6 use std::{env::current_dir, io::Read}; 7 use workspace::Workspace;
··· 2 mod workspace; 3 mod stack; 4 mod util; 5 + mod buffer; 6 use std::path::PathBuf; 7 use std::{env::current_dir, io::Read}; 8 use workspace::Workspace;
+6 -12
src/stack.rs
··· 3 4 use crate::errors::{Error, Result}; 5 use crate::util; 6 - use std::io::{self, BufRead}; 7 - use std::num::ParseIntError; 8 - use std::{fs::File, io::Read, path::PathBuf}; 9 10 use nix::fcntl::{Flock, FlockArg}; 11 12 - use crate::workspace::Id; 13 - 14 - const INDEXFILE: &str = "index"; 15 - const TITLECACHEFILE: &str = "cache"; 16 17 struct StackItem { 18 id: Id, ··· 28 } 29 30 impl StackItem { 31 - fn from_reader(reader: &mut impl BufRead) -> Result<Self> { 32 let mut buf = String::new(); 33 reader.read_line(&mut buf)?; 34 if buf.is_empty() { ··· 38 ))); 39 } 40 let (id, next) = Self::parse(&buf)?; 41 todo!(); 42 } 43 ··· 63 } 64 65 impl TaskStack { 66 - fn from_tskdir(path: &PathBuf) -> Self { 67 - let index = util::flopen(&path.join(INDEXFILE), FlockArg::LockExclusive); 68 - let cache = util::flopen(&path.join(TITLECACHEFILE), FlockArg::LockShared); 69 - 70 todo!() 71 } 72 }
··· 3 4 use crate::errors::{Error, Result}; 5 use crate::util; 6 + use std::io::{self, BufRead, Read}; 7 + use std::{fs::File, path::PathBuf}; 8 9 use nix::fcntl::{Flock, FlockArg}; 10 11 + use crate::workspace::{Id, Workspace}; 12 13 struct StackItem { 14 id: Id, ··· 24 } 25 26 impl StackItem { 27 + fn from_reader(workspace_path: &PathBuf, reader: &mut impl BufRead) -> Result<Self> { 28 let mut buf = String::new(); 29 reader.read_line(&mut buf)?; 30 if buf.is_empty() { ··· 34 ))); 35 } 36 let (id, next) = Self::parse(&buf)?; 37 + let title = util::flopen(workspace_path.join("tasks").join(id), mode) 38 todo!(); 39 } 40 ··· 60 } 61 62 impl TaskStack { 63 + fn from_tskdir(path: &PathBuf) -> Result<Self> { 64 todo!() 65 } 66 }
+1 -1
src/util.rs
··· 6 7 use nix::fcntl::{Flock, FlockArg}; 8 9 - pub fn flopen(path: &PathBuf, mode: FlockArg) -> Result<Flock<File>> { 10 let file = OpenOptions::new() 11 .read(true) 12 .write(true)
··· 6 7 use nix::fcntl::{Flock, FlockArg}; 8 9 + pub fn flopen(path: PathBuf, mode: FlockArg) -> Result<Flock<File>> { 10 let file = OpenOptions::new() 11 .read(true) 12 .write(true)
+18 -2
src/workspace.rs
··· 2 use nix::fcntl::{Flock, FlockArg}; 3 4 use crate::errors::{Error, Result}; 5 use crate::util; 6 use std::fs::File; 7 use std::io::{Read, Seek}; ··· 9 use std::str::FromStr; 10 use std::{fs::OpenOptions, io::Write}; 11 12 /// A unique identifier for a task. When referenced in text, it is prefixed with `tsk-`. 13 pub struct Id(u32); 14 ··· 58 } 59 60 pub fn next_id(&self) -> Result<Id> { 61 - let mut file = util::flopen(&self.path.join("next"), FlockArg::LockExclusive)?; 62 let mut buf = String::new(); 63 file.read_to_string(&mut buf)?; 64 let id = buf.trim().parse::<u32>()?; ··· 75 // TODO: we could improperly increment the id if the task is not written to disk/errors 76 let id = self.next_id()?; 77 let mut file = util::flopen( 78 - &self.path.join("tasks").join(format!("tsk-{}.tsk", id.0)), 79 FlockArg::LockExclusive, 80 )?; 81 file.write_all(format!("{title}\n\n{body}").as_bytes())?; ··· 85 body, 86 file, 87 }) 88 } 89 } 90
··· 2 use nix::fcntl::{Flock, FlockArg}; 3 4 use crate::errors::{Error, Result}; 5 + use crate::stack::TaskStack; 6 use crate::util; 7 use std::fs::File; 8 use std::io::{Read, Seek}; ··· 10 use std::str::FromStr; 11 use std::{fs::OpenOptions, io::Write}; 12 13 + const INDEXFILE: &str = "index"; 14 + const TITLECACHEFILE: &str = "cache"; 15 /// A unique identifier for a task. When referenced in text, it is prefixed with `tsk-`. 16 pub struct Id(u32); 17 ··· 61 } 62 63 pub fn next_id(&self) -> Result<Id> { 64 + let mut file = util::flopen(self.path.join("next"), FlockArg::LockExclusive)?; 65 let mut buf = String::new(); 66 file.read_to_string(&mut buf)?; 67 let id = buf.trim().parse::<u32>()?; ··· 78 // TODO: we could improperly increment the id if the task is not written to disk/errors 79 let id = self.next_id()?; 80 let mut file = util::flopen( 81 + self.path.join("tasks").join(format!("tsk-{}.tsk", id.0)), 82 FlockArg::LockExclusive, 83 )?; 84 file.write_all(format!("{title}\n\n{body}").as_bytes())?; ··· 88 body, 89 file, 90 }) 91 + } 92 + 93 + fn read_stack(&self) -> Result<TaskStack> { 94 + let mut index = String::new(); 95 + let mut cache = String::new(); 96 + let mut index_file = util::flopen(self.path.join(INDEXFILE), FlockArg::LockExclusive)?; 97 + let mut cache_file = util::flopen(self.path.join(TITLECACHEFILE), FlockArg::LockShared)?; 98 + index_file.read_to_string(&mut index)?; 99 + for line in index.lines() { 100 + 101 + } 102 + 103 + todo!(); 104 } 105 } 106