1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::collections::HashSet;
use std::fs;
use std::fs::File;
use std::marker::PhantomData;
use std::ops::Deref;
use std::path::{Path, PathBuf};

use fs2::FileExt;

use crate::prelude::*;

const SOMA_DATA_DIR_ENV_NAME: &str = "SOMA_DATA_DIR";
const SOMA_DATA_DIR_NAME: &str = ".soma";

const LOCK_FILE_NAME: &str = "soma.lock";

pub struct DataDirectory {
    root_path: PathBuf,
    lock: File,
    manager_set: HashSet<&'static str>,
}

impl DataDirectory {
    pub fn new() -> SomaResult<Self> {
        let path = {
            if let Some(dir) = std::env::var_os(SOMA_DATA_DIR_ENV_NAME) {
                dir.into()
            } else {
                let mut home = dirs::home_dir().ok_or(SomaError::DataDirectoryAccessDenied)?;
                home.push(SOMA_DATA_DIR_NAME);
                home
            }
        };

        if !path.exists() {
            fs::create_dir_all(&path)?;
            println!("Created Soma data directory at: {}", path.to_string_lossy());
        }

        DataDirectory::initialize_and_lock(path)
    }

    pub fn at_path(path: impl AsRef<Path>) -> SomaResult<Self> {
        fs::create_dir_all(&path)?;
        DataDirectory::initialize_and_lock(path.as_ref().to_owned())
    }

    fn initialize_and_lock(path: PathBuf) -> SomaResult<Self> {
        let lock = File::create(path.join(LOCK_FILE_NAME))?;
        lock.try_lock_exclusive()
            .or(Err(SomaError::DataDirectoryLockFailed))?;

        Ok(DataDirectory {
            root_path: path,
            lock,
            manager_set: HashSet::new(),
        })
    }

    pub fn register<'a, T>(&'a mut self) -> SomaResult<T>
    where
        T: DirectoryManager<'a>,
    {
        if !self.manager_set.insert(T::DIR) {
            panic!("A manager should be registered only once");
        }

        let manager_root = self.root_path.join(T::DIR);
        fs::create_dir_all(&manager_root)?;
        Ok(T::new(Registration::new(self, manager_root))?)
    }
}

impl Drop for DataDirectory {
    fn drop(&mut self) {
        if self.lock.unlock().is_err() {
            eprintln!("Failed to unlock the data directory");
        }
    }
}

pub struct Registration<'a, T>
where
    T: DirectoryManager<'a>,
{
    data_dir: &'a DataDirectory,
    root_path: PathBuf,
    phantom: PhantomData<*const T>,
}

impl<'a, T> Registration<'a, T>
where
    T: DirectoryManager<'a>,
{
    fn new(data_dir: &'a DataDirectory, root_path: PathBuf) -> Self {
        Registration {
            data_dir,
            root_path,
            phantom: PhantomData,
        }
    }

    pub fn data_dir(&self) -> &'a DataDirectory {
        self.data_dir
    }

    pub fn root_path(&self) -> &PathBuf {
        &self.root_path
    }
}

pub trait DirectoryManager<'a>: Deref<Target = Registration<'a, Self>>
where
    Self: Sized + 'a,
{
    const DIR: &'static str;

    fn new(registration: Registration<'a, Self>) -> SomaResult<Self>;
}