data.rs
1 mod dto; 2 3 use std::fs; 4 use std::path::PathBuf; 5 6 use anyhow::{bail, Result}; 7 use fs2::FileExt; 8 use tracing::{debug, info, warn}; 9 10 use crate::util; 11 12 /// Root directory where we keep the lock & data file 13 pub struct DataDir { 14 path: PathBuf, 15 lock_file: fs::File, 16 } 17 18 impl DataDir { 19 pub fn new(path: impl Into<PathBuf>) -> Result<Self> { 20 let path = path.into(); 21 ensure_root_exists(&path)?; 22 23 let lock = util::open_lock_file(&path)?; 24 25 Ok(Self { 26 path, 27 lock_file: lock, 28 }) 29 } 30 31 pub fn with_lock<T>(&mut self, f: impl FnOnce(&mut LockedRoot) -> Result<T>) -> Result<T> { 32 f(&mut LockedRoot::new(&self.path, &mut self.lock_file)?) 33 } 34 } 35 36 fn ensure_root_exists(dir: &PathBuf) -> Result<()> { 37 if !dir.try_exists()? { 38 info!(dir = %dir.display(), "Creating root dir"); 39 fs::create_dir_all(dir)?; 40 } 41 Ok(()) 42 } 43 44 /// A handle passed to `with_lock` argument after root was acquired 45 pub struct LockedRoot<'a> { 46 path: &'a PathBuf, 47 lock_file: &'a mut fs::File, 48 locked: bool, 49 } 50 51 impl<'a> Drop for LockedRoot<'a> { 52 fn drop(&mut self) { 53 if self.locked { 54 let Ok(()) = self.lock_file.unlock() else { 55 warn!("Failed to release the lock file"); 56 return; 57 }; 58 self.locked = false; 59 } 60 } 61 } 62 63 impl<'a> LockedRoot<'a> { 64 fn new(path: &'a PathBuf, lock_file: &'a mut fs::File) -> Result<Self> { 65 let mut locked_root = Self { 66 path, 67 lock_file, 68 locked: false, 69 }; 70 locked_root.lock()?; 71 Ok(locked_root) 72 } 73 74 fn lock(&mut self) -> Result<()> { 75 debug!(path = %self.path.display(), "Acquiring lock..."); 76 if self.lock_file.try_lock_exclusive().is_err() { 77 info!("Lock taken, waiting..."); 78 self.lock_file.lock_exclusive()?; 79 info!("Acquired lock after wait"); 80 }; 81 debug!("Acquired lock"); 82 self.locked = true; 83 Ok(()) 84 } 85 86 fn data_file_path(&self) -> PathBuf { 87 self.path.join("fm-portalloc.json") 88 } 89 90 fn ensure_locked(&self) -> anyhow::Result<()> { 91 if !self.locked { 92 bail!("LockedRoot no longer valid"); 93 } 94 Ok(()) 95 } 96 97 pub fn load_data(&self) -> Result<dto::RootData> { 98 self.ensure_locked()?; 99 let path = self.data_file_path(); 100 if !path.try_exists()? { 101 return Ok(Default::default()); 102 } 103 Ok(serde_json::from_reader::<_, _>(std::fs::File::open(path)?)?) 104 } 105 106 pub fn store_data(&mut self, data: &dto::RootData) -> Result<()> { 107 util::store_json_pretty_to_file(&self.data_file_path(), data) 108 } 109 }