fedimint_portalloc/
data.rs

1mod dto;
2
3use std::fs;
4use std::path::PathBuf;
5
6use anyhow::{Result, bail};
7use fs2::FileExt;
8use tracing::{debug, info, warn};
9
10use crate::util;
11
12/// Root directory where we keep the lock & data file
13pub struct DataDir {
14    path: PathBuf,
15    lock_file: fs::File,
16}
17
18impl 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
36fn 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
45pub struct LockedRoot<'a> {
46    path: &'a PathBuf,
47    lock_file: &'a mut fs::File,
48    locked: bool,
49}
50
51impl<'a> Drop for LockedRoot<'a> {
52    fn drop(&mut self) {
53        if self.locked {
54            let Ok(()) = FileExt::unlock(self.lock_file) else {
55                warn!("Failed to release the lock file");
56                return;
57            };
58            self.locked = false;
59        }
60    }
61}
62
63impl<'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}