lazy_db/
lazy_database.rs

1use crate::*;
2use std::path::{Path, PathBuf};
3use std::fs;
4
5/// Used for reading from a `LazyDB` with less boiler-plate
6#[macro_export]
7macro_rules! search_database {
8    (($ldb:expr) /$($($con:ident)?$(($can:expr))?)/ *) => {(|| {
9        let database = &$ldb;
10        let container = database.as_container()?;
11        $(
12            $(let container = container.child_container(stringify!($con))?;)?
13            $(let container = container.child_container($can)?;)?
14        )*
15        let result: Result<LazyContainer, LDBError> = Ok(container);
16        result
17    })()};
18
19    (($ldb:expr) /$($($con:ident)?$(($can:expr))?)/ *::$($item:ident)?$(($obj:expr))?) => {(|| {
20        let container = search_database!(($ldb) /$($($con)?$(($can))?)/ *)?;
21        $(let result: Result<LazyData, LDBError> = container.read_data(stringify!($item));)?
22        $(let result: Result<LazyData, LDBError> = container.read_data($obj);)?
23        result
24    })()};
25
26    (($ldb:expr) $($item:ident)?$(($obj:expr))?) => {(|| {
27        let database = &$ldb;
28        let container = database.as_container()?;
29        $(let result: Result<LazyData, LDBError> = container.read_data(stringify!($item));)?
30        $(let result: Result<LazyData, LDBError> = container.read_data($obj);)?
31        result
32    })()};
33}
34
35/// Used for reading from a `LazyDB` with less boiler-plate
36#[macro_export]
37macro_rules! write_database {
38    (($ldb:expr) $($item:ident)?$(($obj:expr))? = $func:ident($value:expr)) => {(|| {
39        let database = &$ldb;
40        let container = database.as_container()?;
41        $(LazyData::$func(container.data_writer(stringify!($item))?, $value)?;)?
42        $(LazyData::$func(container.data_writer($obj)?, $value)?;)?
43        Result::<(), LDBError>::Ok(())
44    })()};
45
46    (($ldb:expr) /$($($con:ident)?$(($can:expr))?)/ *::$($item:ident)?$(($obj:expr))? = $func:ident($value:expr)) => {(|| {
47        let mut container = search_database!(($ldb) /$($($con)?$(($can))?)/ *)?;
48
49        $(LazyData::$func(container.data_writer(stringify!($item))?, $value)?;)?
50        $(LazyData::$func(container.data_writer($obj)?, $value)?;)?
51        Result::<(), LDBError>::Ok(())
52    })()}
53}
54
55pub struct LazyDB {
56    path: PathBuf,
57    compressed: bool,
58}
59
60impl LazyDB {
61    /// Initialises a new LazyDB directory at a specified path.
62    /// 
63    /// It will create the path if it doesn't already exist and initialise a metadata file with the current version of `lazy-db` if one doesn't exist already.
64    /// 
65    /// **WARNING:** if you initialise the database this way, you cannot compile it in future without errors being thrown!
66    /// If you want to compile it, then use `LazyDB::init_db` instead.
67    pub fn init(path: impl AsRef<Path>) -> Result<Self, LDBError> {
68        let path = path.as_ref();
69
70        // Check if path exists or not if init it
71        if !path.is_dir() { unwrap_result!((fs::create_dir_all(path)) err => LDBError::IOError(err)) };
72        
73        { // Check if `.meta` file exists if not 
74            let meta = path.join(".meta");
75            if !meta.is_file() {
76                // Write version
77                LazyData::new_binary(
78                    FileWrapper::new_writer(
79                        unwrap_result!((fs::File::create(meta)) err => LDBError::IOError(err))
80                    ), &[VERSION.major, VERSION.minor, VERSION.build],
81                )?;
82            }
83        };
84
85        // Construct Self
86        Ok(Self {
87            path: path.to_path_buf(),
88            compressed: false,
89        })
90    }
91
92    /// Initialise a new compiled `LazyDB` (compressed tarball) at the specified path.
93    ///
94    /// It will create the path if it doesn't already exist and initialise a metadata file with the current version of `lazy-db` if one doesn't exist already.
95    pub fn init_db(path: impl AsRef<Path>) -> Result<Self, LDBError> {
96        let dir_path = path.as_ref().with_extension("modb");
97        let mut this = Self::init(dir_path)?;
98        this.compressed = true;
99        Ok(this)
100    }
101
102    /// Loads a pre-existing LazyDB directory at a specified path.
103    /// 
104    /// Loads LazyDB as `read-write` allowing for modification of the data within it.
105    /// 
106    /// If the LazyDB is invalid, it will return an error.
107    pub fn load_dir(path: impl AsRef<Path>) -> Result<Self, LDBError> {
108        let path = path.as_ref();
109
110        // Checks if path exists
111        if !path.is_dir() { return Err(LDBError::DirNotFound(path.to_path_buf())) };
112
113        // Checks if `.meta` file exists or not
114        let meta = path.join(".meta");
115        if !meta.is_file() { return Err(LDBError::FileNotFound(meta)) };
116
117        // Checks validity of version
118        let read_version = LazyData::load(&meta)?.collect_binary()?;
119        if read_version.len() != 3 { return Err(LDBError::InvalidMetaVersion(meta)) };
120        let read_version = version::Version::new(read_version[0], read_version[1], read_version[2]);
121        if !VERSION.is_compatible(&read_version) { return Err(LDBError::IncompatibleVersion(read_version)) };
122
123        // Constructs Self
124        Ok(Self {
125            path: path.to_path_buf(),
126            compressed: false,
127        })
128    }
129
130    /// Loads a pre-existing LazyDB file (compressed tarball) at a specified path
131    /// 
132    /// Loads LazyDB as `read-write` allowing for modification of the data within it.
133    /// 
134    /// If a directory version of the LazyDatabase exists, it will load the directory version instead of decompiling.
135    /// 
136    /// If the LazyDB is invalid, it will return an error.
137    pub fn load_db(path: impl AsRef<Path>) -> Result<Self, LDBError> {
138        let path = path.as_ref();
139        let mod_path = path.with_extension("modb");
140
141        // Checks if other loaded version exists
142        if mod_path.is_dir() { return Self::load_dir(mod_path) }
143
144        // Decompiles database
145        Self::decompile(path, &mod_path)?;
146        let mut ldb = Self::load_dir(mod_path)?;
147        ldb.compressed = true;
148
149        Ok(ldb)
150    }
151
152    /// Gets the 'root' container of the `LazyDB`
153    #[inline]
154    pub fn as_container(&self) -> Result<LazyContainer, LDBError> {
155        LazyContainer::load(&self.path)
156    }
157
158    #[inline]
159    pub fn path(&self) -> &Path {
160        &self.path
161    }
162
163    /// Compiles a modifiable `LazyDatabase` directory into a compressed tarball (doesn't delete the modifable directory).
164    pub fn compile(&self, out_path: impl AsRef<Path>) -> Result<(), std::io::Error> {
165        use lazy_archive::*; // imports
166        let tar = self.path.with_extension("tmp.tar");
167
168        // Build and compress tarball
169        build_tar(&self.path, &tar)?; // build tar
170        compress_file(&tar, &out_path)?;
171
172        // Clean-up
173        fs::remove_file(tar)?;
174
175        Ok(())
176    }
177
178    /// Decompiles a compressed tarball `LazyDatabase` into a modifiable directory (doesn't remove the compressed tarball)
179    pub fn decompile(path: impl AsRef<Path>, out_path: impl AsRef<Path>) -> Result<(), LDBError> {
180        use lazy_archive::*; // imports
181        let path = path.as_ref();
182
183        // Checks if the path exists
184        if !path.is_file() { return Err(LDBError::FileNotFound(path.to_path_buf())) };
185
186        // Decompress and unpack
187        let tar = path.with_extension("tmp.tar");
188        unwrap_result!((decompress_file(path, &tar)) err => LDBError::IOError(err));
189        unwrap_result!((unpack_tar(&tar, out_path)) err => LDBError::IOError(err));
190
191        // Clean-up
192        unwrap_result!((fs::remove_file(tar)) err => LDBError::IOError(err));
193        
194        Ok(())
195    }
196}
197
198impl Drop for LazyDB {
199    fn drop(&mut self) {
200        if !self.compressed { return }; // If not compressed do nothing
201        let ok = self.compile(self.path.with_extension("ldb")).is_ok();
202        if !ok { return }; // Don't delete if not ok
203        let _ = fs::remove_dir_all(&self.path);
204    }
205}