/ utils / portalloc / src / data.rs
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  }