pub struct DatabaseManager { /* private fields */ }Expand description
A manager for a file-system database.
A “database” is essentially just an arbitrary directory in the file system with
subdirectories for specific implementors of DatabaseEntry. Those
subdirectories are named after the implementor (see type_name) and stores
files with the serialized representations of individual instances. The
directories are created on a as-needed base by the DatabaseManager.
The following code defines a database at the path /path/to/db (the “database
root”) and stores two composed DatabaseEntry implementors within it.
use std::ffi::OsStr;
use serde::{Serialize, Deserialize};
use serde_mosaic::*;
#[derive(Serialize, Deserialize, Clone)]
struct Material {
name: String,
cotton_content: f64,
}
#[typetag::serde]
impl DatabaseEntry for Material {
fn name(&self) -> &OsStr {
self.name.as_ref()
}
}
#[derive(Serialize, Deserialize)]
struct Shirt {
owner: String,
#[serde(serialize_with = "serialize_link")]
#[serde(deserialize_with = "deserialize_link")]
material: Material,
size: usize
}
#[typetag::serde]
impl DatabaseEntry for Shirt {
fn name(&self) -> &OsStr {
self.owner.as_ref()
}
}
let pure_cotton = Material {
name: "pure_cotton".into(),
cotton_content: 100.0,
};
let mikes_shirt = Shirt {
owner: "mike".into(),
material: pure_cotton.clone(),
size: 40
};
let joes_shirt = Shirt {
owner: "joe".into(),
material: pure_cotton.clone(),
size: 38
};
let mut dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
dbm.write(&mikes_shirt, &WriteOptions::default()).expect("serialization and writing succeeds");
dbm.write(&joes_shirt, &WriteOptions::default()).expect("serialization and writing succeeds");The first dbm.write creates a directory /path/to/db/Shirt (if it didn’t
exist yet) and a file /path/to/db/Shirt/mike.yaml where the content of the
variable mikes_shirt is stored. Additionally, since pure_cotton should be
linked during serialization and deserialization, a second file
/path/to/db/Material/pure_cotton.yaml (and the corresponding directory) is
created, which contains the serialized representation of pure_cotton.
The second dbm.write now creates a file /path/to/db/Shirt/joe.yaml. Since
/path/to/db/Material/pure_cotton.yaml already exists, no additional file is
created.
The DatabaseManager holds the path to the database directory /path/to/db,
the database format SerdeYaml and a Cache for
reference-counted instances (see the docstring of Cache for more). It is
therefore cheap to create new DatabaseManager instances.
All methods which manipulate files (e.g. read,
write or remove take a
mutable reference of self. This is done in order to prevent race conditions
when operating multi-threaded. If it is necessary to use a DatabaseManager
in multiple threads at once, consider using a Mutex lock
or creating one manager instance per thread (although this prevents sharing the
Cache over the different threads).
Implementations§
Source§impl DatabaseManager
impl DatabaseManager
Sourcepub fn new<P, F>(path: P, format: F) -> Result<Self>
pub fn new<P, F>(path: P, format: F) -> Result<Self>
Creates a new instance of Self with the given path and format. If the
path does not exist in the file system, this function tries to create it.
Returns a std::io::Error if the directory cannot be used as a database
(e.g. due to permission issues).
§Examples
use serde_mosaic::*;
let dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");Sourcepub fn with_boxed_format<P>(path: P, format: Box<dyn Format>) -> Result<Self>
pub fn with_boxed_format<P>(path: P, format: Box<dyn Format>) -> Result<Self>
Like DatabaseManager::new, but takes a boxed Format trait object.
DatabaseManager::new boxes its format argument and then calls this
function. If the format is already available as a boxed trait object
(e.g. because it has been taken from another DatabaseManager instance),
using this function skips an allocation.
Sourcepub fn open<P, F>(path: P, format: F) -> Result<Self>
pub fn open<P, F>(path: P, format: F) -> Result<Self>
Like DatabaseManager::new, but returns an error if the specified path
does not exist.
Sourcepub fn open_with_boxed_format<P>(
path: P,
format: Box<dyn Format>,
) -> Result<Self>
pub fn open_with_boxed_format<P>( path: P, format: Box<dyn Format>, ) -> Result<Self>
Like DatabaseManager::open, but takes a boxed Format instead of
being generic. See DatabaseManager::with_boxed_format for details.
Sourcepub fn data_format(&self) -> &dyn Format
pub fn data_format(&self) -> &dyn Format
Returns a reference to the underlying Format of the database.
Since the Format is internally stored as a trait object, this function
returns a reference to that trait object as well. The trait bounds of
Format guarantee that any implementor also implements the Any trait
and can therefore be downcasted to the concrete type.
§Examples
use std::any::Any;
use serde_mosaic::*;
let dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
let format_ref = dbm.data_format() as &dyn Any; // Possible since Rust 1.86
assert!(format_ref.downcast_ref::<SerdeYaml>().is_some());Sourcepub fn file_ext(&self) -> &OsStr
pub fn file_ext(&self) -> &OsStr
Returns the file extension used by self to write and read files.
This function is a shorthand for dbm.data_format().file_ext().
Sourcepub fn checksum<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<u32>
pub fn checksum<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<u32>
Returns the checksum of a database file specified by the given key. If
the file doesn’t exist, this function returns None.
Sourcepub fn remove_empty_subfolders(&mut self) -> Result<()>
pub fn remove_empty_subfolders(&mut self) -> Result<()>
Removes all empty subfolders within the database path self.dir().
Be aware that the DatabaseManager doesn’t know which folders belong to
the database and which folders do not. For example, the following snippet
would remove an empty folder /path/to/db/foo, even though it wasn’t
created by the database manager:
use std::path::PathBuf;
use serde_mosaic::*;
let unrelated_dir = PathBuf::from("/path/to/db/foo");
assert!(unrelated_dir.exists());
assert!(unrelated_dir.read_dir().expect("read permissions available").next().is_none());
let mut dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists or can be created");
assert!(unrelated_dir.exists());
assert!(unrelated_dir.read_dir().expect("read permissions available").next().is_none());
dbm.remove_empty_subfolders();
assert!(!unrelated_dir.exists());Sourcepub fn remove<'a, T: Into<DatabaseKey<'a>>>(&mut self, key: T) -> Result<()>
pub fn remove<'a, T: Into<DatabaseKey<'a>>>(&mut self, key: T) -> Result<()>
Tries to remove the specified database file from the database.
This function essentially derives the file path from the given key with
DatabaseManager::full_path and then tries to delete the file. If the
file doesn’t exist or can’t be removed, this function returns an error.
Be aware that the DatabaseManager does not know which files “belong” to
the database - if a file fitting the naming scheme has been created in an
unrelated way, it will still be removed.
Sourcepub fn remove_all<O: AsRef<OsStr>>(&mut self, name: O) -> Result<()>
pub fn remove_all<O: AsRef<OsStr>>(&mut self, name: O) -> Result<()>
Searches through all direct subfolders (non-recursively) of self.dir() and
removes all files with the given file name whose file extension matches that
of self.file_ext. Similar to DatabaseManager::remove, this function
does not discriminate between files which were created by self and files
which were created by something else.
Sourcepub fn exists<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> bool
pub fn exists<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> bool
Checks if the database has an entry for the given key.
Under the hood, this function calls self.full_path(key).is_some().
Sourcepub fn full_path<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<PathBuf>
pub fn full_path<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<PathBuf>
Returns the full path of the database entry specified by key, if the entry
exist. If not, returns None.
Sourcepub fn cache_mut(&mut self) -> &mut Cache
pub fn cache_mut(&mut self) -> &mut Cache
Returns a mutable reference to the Cache used within self. This can
be used to manually add entries to the Cache. See the docstrings of
Cache and CacheEntry.
Sourcepub fn write<T: DatabaseEntry>(
&mut self,
instance: &T,
write_options: &WriteOptions,
) -> Result<PathBuf>
pub fn write<T: DatabaseEntry>( &mut self, instance: &T, write_options: &WriteOptions, ) -> Result<PathBuf>
Serializes the given instance into the database according to the given
WriteOptions. If successfull, the path to the written file is returned.
This is the central function to store new entries within the database. As
outlined in the docstring of DatabaseManager, calling this function
can actually result in multiple files being written, if instance is
composed of other DatabaseEntry implementor instances which are
annotated with one of the “link”
attributes for serialization (depending on the
WriteMode of WriteOptions). Using serialization functions from other
packages (as e.g. serde_yaml::to_string) bypasses the entire linking
machinery of this crate and just creates the expected serialized
representations.
Sourcepub fn write_verbose<T: DatabaseEntry>(
&mut self,
instance: &T,
write_options: &WriteOptions,
) -> Result<(PathBuf, WriteInfo)>
pub fn write_verbose<T: DatabaseEntry>( &mut self, instance: &T, write_options: &WriteOptions, ) -> Result<(PathBuf, WriteInfo)>
Like DatabaseManager::write, but returns additional WriteInfo in
case writing to the database was successfull.
The WriteInfo contains the following information:
- Which files were created new.
- Which existing files have been overwritten.
These results heavily depend on the settings within WriteOptions, see
its docstring for more.
Sourcepub fn read<T: DatabaseEntry, O: AsRef<OsStr>>(&mut self, name: O) -> Result<T>
pub fn read<T: DatabaseEntry, O: AsRef<OsStr>>(&mut self, name: O) -> Result<T>
Deserializes an instance of T stored within the file with the given name
from the database and returns it.
This function first derives the full file path name by concatenating
self.dir(), the name of T (see type_name) and by combining name
and self.file_ext to the file name. If this file exists, its content is
then deserialized using Format::deserialize_dyn of self.data_format().
Any encountered links are resolved by reading the corresponding files and
storing the resulting object within the created T instance.
Like DatabaseManager::write, using this function is mandatory in order
to read files with links in them. Using serialization functions from other
packages (as e.g. serde_yaml::from_str) bypasses the entire linking
machinery of this crate and will result in failure if any links are stored
within the files.
Sourcepub fn read_verbose<T: DatabaseEntry, O: AsRef<OsStr>>(
&mut self,
name: O,
) -> Result<(T, ReadInfo)>
pub fn read_verbose<T: DatabaseEntry, O: AsRef<OsStr>>( &mut self, name: O, ) -> Result<(T, ReadInfo)>
Like DatabaseManager::read, but returns additional ReadInfo in case
reading from the database was successfull.
The ReadInfo contains all ChecksumMismatches which happened when a
link contained a checksum which didn’t match the linked file. If such a
mismatch occurs, the file is still read and its contents are deserialized
and replace the link regardless. Therefore, this information is useful to
check if a linked file was changed since the creation of the link (e.g. in
order to determine whether the returned instance of T should be used or
not).
Sourcepub fn from_str<T: DeserializeOwned + 'static, F: Format>(
&mut self,
str: impl AsRef<str>,
) -> Result<T>
pub fn from_str<T: DeserializeOwned + 'static, F: Format>( &mut self, str: impl AsRef<str>, ) -> Result<T>
Deserializes the given string using Format::deserialize from
self.data_format() and resolves any encountered links using the underlying
database.
This function behaves similarily to DatabaseManager::read, except that
the starting point is not a file from the database, but str instead.
Because the Format is stored as a trait object inside self, it needs
to be downcasted into its concrete type F inside this function. Specifying
the wrong type will result in an error.
§Examples
use std::ffi::OsStr;
use std::sync::Arc;
use serde::{Serialize, Deserialize};
use serde_mosaic::*;
#[derive(Serialize, Deserialize, Clone)]
struct Material {
name: String,
cotton_content: f64,
}
#[typetag::serde]
impl DatabaseEntry for Material {
fn name(&self) -> &OsStr {
self.name.as_ref()
}
}
#[derive(Deserialize)]
struct Shirt {
owner: String,
#[serde(deserialize_with = "deserialize_arc_link")]
#[serde(serialize_with = "serialize_arc_link")]
material: Arc<Material>,
size: usize
}
let mut dbm = DatabaseManager::new("/path/to/db", SerdeYaml).expect("directory exists");
let shirt_str = indoc::indoc! {"
---
owner: Sven
material:
name: pure_cotton
size: 46
"};
let shirt = dbm.from_str::<Shirt, SerdeYaml>(&shirt_str).unwrap();
assert_eq!(shirt.material.name, "pure_cotton");Trait Implementations§
Source§impl Clone for DatabaseManager
impl Clone for DatabaseManager
Source§fn clone(&self) -> DatabaseManager
fn clone(&self) -> DatabaseManager
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more