mirror of
https://github.com/Drop-OSS/dropbreak.git
synced 2026-02-03 22:41:19 +01:00
feat: move to async
This commit is contained in:
20
Cargo.toml
20
Cargo.toml
@@ -1,16 +1,19 @@
|
||||
[package]
|
||||
authors = ["Marcel Müller <neikos@neikos.email>"]
|
||||
authors = [
|
||||
"Marcel Müller <neikos@neikos.email>",
|
||||
"DecDuck <declanahofmeyr@gmail.com>",
|
||||
]
|
||||
categories = ["database-implementations"]
|
||||
description = "A modular and configurable database"
|
||||
documentation = "https://docs.rs/rustbreak"
|
||||
edition = "2018"
|
||||
homepage = "https://github.com/TheNeikos/rustbreak"
|
||||
homepage = "https://github.com/Drop-OSS/dropbreak"
|
||||
keywords = ["database", "simple", "fast", "rustbreak"]
|
||||
license = "MPL-2.0"
|
||||
name = "rustbreak"
|
||||
name = "dropbreak"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/TheNeikos/rustbreak"
|
||||
version = "2.0.0"
|
||||
repository = "https://github.com/Drop-OSS/dropbreak"
|
||||
version = "2.0.0-drop"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
@@ -20,6 +23,10 @@ serde = "1"
|
||||
tempfile = "3"
|
||||
thiserror = "1.0.20"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.46.1"
|
||||
features = ["sync", "rt", "macros", "fs", "io-util"]
|
||||
|
||||
[dependencies.ron]
|
||||
optional = true
|
||||
version = "0.6"
|
||||
@@ -49,10 +56,9 @@ lazy_static = "1"
|
||||
serde_derive = "1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
default = ["ron_enc"]
|
||||
ron_enc = ["ron"]
|
||||
bin_enc = ["bincode", "base64"]
|
||||
yaml_enc = ["serde_yaml"]
|
||||
other_errors = ["anyhow"]
|
||||
mmap = ["memmap"]
|
||||
|
||||
|
||||
42
README.md
42
README.md
@@ -1,8 +1,8 @@
|
||||
Rustbreak
|
||||
=========
|
||||
# Dropbreak (Rustbreak)
|
||||
|
||||
[](https://travis-ci.org/TheNeikos/rustbreak)
|
||||
[](https://crates.io/crates/rustbreak)
|
||||
# About this fork
|
||||
|
||||
We forked Rustbreak because it was 1. no longer maintained, and 2. we needed it to be `async`. Feel free to use this fork in your own code, though. No promise of maintenance or warranty is made.
|
||||
|
||||
**[Documentation][doc]**
|
||||
|
||||
@@ -11,8 +11,7 @@ database. It is meant to be fast and simple to use. You add it to your
|
||||
application and it should just work for you. The only thing you will have to
|
||||
take care of is saving.
|
||||
|
||||
When to use it
|
||||
--------------
|
||||
## When to use it
|
||||
|
||||
This library started out because of a need to be able to quickly write an
|
||||
application in rust that needed some persistence while still being able to write
|
||||
@@ -21,24 +20,21 @@ arbitrary data to it.
|
||||
In Ruby there is [Daybreak] however for Rust there was no similar crate, until
|
||||
now!
|
||||
|
||||
When not to use it
|
||||
------------------
|
||||
## When not to use it
|
||||
|
||||
Rustbreak makes several trade-offs to be easy to use and extend, so knowing of these drawbacks is important if
|
||||
Rustbreak makes several trade-offs to be easy to use and extend, so knowing of these drawbacks is important if
|
||||
you wish to use the library:
|
||||
|
||||
- The Database needs to fit into memory (Rustbreak cannot do partial loads/saves, so if the Database exceeds your available memory you will run OOM)
|
||||
- Not all backends support atomic saves, so if your program crashes while it is saving you might save incomplete data (Notably only `PathBackend` supports atomic saves)
|
||||
|
||||
Features
|
||||
--------
|
||||
## Features
|
||||
|
||||
- Simple To Use, Fast, Secure
|
||||
- Threadsafe
|
||||
- Serde compatible storage (ron, bincode, or yaml included)
|
||||
|
||||
Quickstart
|
||||
----------
|
||||
## Quickstart
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
@@ -73,20 +69,19 @@ fn main() -> rustbreak::Result<()> {
|
||||
}
|
||||
```
|
||||
|
||||
Usage
|
||||
-----
|
||||
## Usage
|
||||
|
||||
Usage is quite simple:
|
||||
|
||||
- Create/open a database using one of the Database constructors:
|
||||
- Create a `FileDatabase` with `FileDatabase::from_path`.
|
||||
- Create a `MemoryDatabase` with `MemoryDatabase::memory`.
|
||||
- Create a `MmapDatabase` with `MmapDatabase::mmap` or `MmapDatabase::mmap_with_size` with `mmap` feature.
|
||||
- Create a `Database` with `Database::from_parts`.
|
||||
- Create a `FileDatabase` with `FileDatabase::from_path`.
|
||||
- Create a `MemoryDatabase` with `MemoryDatabase::memory`.
|
||||
- Create a `MmapDatabase` with `MmapDatabase::mmap` or `MmapDatabase::mmap_with_size` with `mmap` feature.
|
||||
- Create a `Database` with `Database::from_parts`.
|
||||
- `Write`/`Read` data from the Database
|
||||
- Don't forget to run `save` periodically, or whenever it makes sense.
|
||||
- You can save in parallel to using the Database. However you will lock
|
||||
write acess while it is being written to storage.
|
||||
- You can save in parallel to using the Database. However you will lock
|
||||
write acess while it is being written to storage.
|
||||
|
||||
```rust
|
||||
# use std::collections::HashMap;
|
||||
@@ -149,6 +144,5 @@ features = ["bin_enc"]
|
||||
|
||||
You can now use `rustbreak::deser::Bincode` as deserialization struct.
|
||||
|
||||
|
||||
[doc]:http://neikos.me/rustbreak/rustbreak/index.html
|
||||
[Daybreak]:https://propublica.github.io/daybreak/
|
||||
[doc]: http://neikos.me/rustbreak/rustbreak/index.html
|
||||
[Daybreak]: https://propublica.github.io/daybreak/
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
// This just reads an example configuration.
|
||||
// If it doesn't find one, it uses your default configuration
|
||||
//
|
||||
// You can create one by writing this file to `/tmp/config.ron`:
|
||||
// ```
|
||||
// ---
|
||||
// user_path: /tmp/nope
|
||||
// allow_overwrite: true
|
||||
// ```
|
||||
//
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
use rustbreak::deser::Ron;
|
||||
use rustbreak::FileDatabase;
|
||||
use std::default::Default;
|
||||
use std::path::PathBuf;
|
||||
|
||||
type DB = FileDatabase<Config, Ron>;
|
||||
|
||||
lazy_static! {
|
||||
static ref CONFIG: DB = {
|
||||
let db = FileDatabase::load_from_path_or_default("/tmp/config.ron")
|
||||
.expect("Create database from path");
|
||||
db.load().expect("Config to load");
|
||||
db
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct Config {
|
||||
user_path: PathBuf,
|
||||
allow_overwrite: bool,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Config {
|
||||
Config {
|
||||
user_path: PathBuf::from("/tmp"),
|
||||
allow_overwrite: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _conf: Config = CONFIG
|
||||
.read(|conf| conf.clone())
|
||||
.expect("Reading configuration");
|
||||
|
||||
let (user_path, allow_overwrite) = CONFIG
|
||||
.read(|conf| (conf.user_path.clone(), conf.allow_overwrite.clone()))
|
||||
.expect("Read config");
|
||||
|
||||
println!(
|
||||
"The current configuration is: {:?} and {}",
|
||||
user_path, allow_overwrite
|
||||
);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use rustbreak::deser::Ron;
|
||||
use rustbreak::FileDatabase;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
enum Country {
|
||||
Italy,
|
||||
UnitedKingdom,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
struct Person {
|
||||
name: String,
|
||||
country: Country,
|
||||
}
|
||||
|
||||
fn do_main() -> Result<(), rustbreak::RustbreakError> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let db = FileDatabase::<HashMap<String, Person>, Ron>::load_from_path_or_default("test.ron")?;
|
||||
|
||||
println!("Writing to Database");
|
||||
db.write(|db| {
|
||||
db.insert(
|
||||
"john".into(),
|
||||
Person {
|
||||
name: String::from("John Andersson"),
|
||||
country: Country::Italy,
|
||||
},
|
||||
);
|
||||
db.insert(
|
||||
"fred".into(),
|
||||
Person {
|
||||
name: String::from("Fred Johnson"),
|
||||
country: Country::UnitedKingdom,
|
||||
},
|
||||
);
|
||||
println!("Entries: \n{:#?}", db);
|
||||
})?;
|
||||
|
||||
println!("Syncing Database");
|
||||
db.save()?;
|
||||
|
||||
println!("Loading Database");
|
||||
db.load()?;
|
||||
|
||||
println!("Reading from Database");
|
||||
db.read(|db| {
|
||||
println!("Results:");
|
||||
println!("{:#?}", db);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = do_main() {
|
||||
eprintln!("An error has occurred at: \n{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/* This file is a complete work in progress! And is meant to illustrate a possible way of
|
||||
* migrating databases with Rustbreak.
|
||||
*
|
||||
* Currently the major challenge is to statically define the upgrade process.
|
||||
*
|
||||
* This is currently done by using the `upgrading_macro`.
|
||||
*
|
||||
* The idea is to read the 'version' tag in the file, load it into a simple tag struct and
|
||||
* read the version from there. This version is then used to dynamically get the corresponding
|
||||
* struct type. This then gets converted to the latest version.
|
||||
*/
|
||||
|
||||
|
||||
extern crate rustbreak;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
use rustbreak::FileDatabase;
|
||||
use rustbreak::deser::Ron;
|
||||
|
||||
mod data {
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub enum Version {
|
||||
First, Second, Third,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Versioning {
|
||||
pub version: Version
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use data::Version;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Players {
|
||||
version: Version,
|
||||
pub players: HashMap<i32, String>,
|
||||
}
|
||||
|
||||
impl Players {
|
||||
pub fn new() -> Players {
|
||||
Players {
|
||||
version: Version::First,
|
||||
players: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v2 {
|
||||
use data::Version;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub level: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Players {
|
||||
version: Version,
|
||||
pub players: HashMap<i32, Player>,
|
||||
}
|
||||
|
||||
impl Players {
|
||||
pub fn new() -> Players {
|
||||
Players {
|
||||
version: Version::Second,
|
||||
players: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v3 {
|
||||
use data::Version;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Player {
|
||||
pub name: String,
|
||||
pub score: i64
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Players {
|
||||
version: Version,
|
||||
pub players: HashMap<i32, Player>,
|
||||
}
|
||||
|
||||
impl Players {
|
||||
pub fn new() -> Players {
|
||||
Players {
|
||||
version: Version::Third,
|
||||
players: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_v1_v2(input: v1::Players) -> v2::Players {
|
||||
let mut new = v2::Players::new();
|
||||
for (key, player) in input.players {
|
||||
new.players.insert(key,
|
||||
v2::Player {
|
||||
name: player,
|
||||
level: 1
|
||||
});
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub fn convert_v2_v3(input: v2::Players) -> v3::Players {
|
||||
let mut new = v3::Players::new();
|
||||
for (key, player) in input.players {
|
||||
new.players.insert(key,
|
||||
v3::Player {
|
||||
name: player.name,
|
||||
score: 0
|
||||
});
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! migration_rules {
|
||||
( $name:ident -> $res:ty { $( $t:ty => $conv:path ),* $(,)* } ) => {
|
||||
fn $name<T: 'static>(input: T) -> Option<$res> {
|
||||
fn inner(mut input: Box<std::any::Any>) -> Option<$res> {
|
||||
println!("We have: {:#?}", input);
|
||||
$(
|
||||
{
|
||||
let mut maybe_in = None;
|
||||
if let Some(out) = input.downcast_ref::<$t>() {
|
||||
maybe_in = Some(Box::new($conv(out.clone())));
|
||||
}
|
||||
|
||||
if let Some(inp) = maybe_in {
|
||||
input = inp;
|
||||
}
|
||||
}
|
||||
)*
|
||||
|
||||
if let Ok(out) = input.downcast::<$res>() {
|
||||
return Some(*out);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
inner(Box::new(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
migration_rules! {
|
||||
update_database -> data::v3::Players {
|
||||
data::v1::Players => data::convert_v1_v2,
|
||||
data::v2::Players => data::convert_v2_v3,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_version() -> data::Version {
|
||||
let db = FileDatabase::<data::Versioning, Ron>::from_path("migration.ron", data::Versioning { version: data::Version::First }).unwrap();
|
||||
db.load().unwrap();
|
||||
db.read(|ver| ver.version).unwrap()
|
||||
}
|
||||
|
||||
fn main() {
|
||||
use data::v3::Players;
|
||||
|
||||
let version = get_version();
|
||||
|
||||
|
||||
// Let's check if there are any updates
|
||||
let updated_data = update_database(database.get_data(false).unwrap()).unwrap();
|
||||
let database = FileDatabase::<Players, Ron>::from_path("migration.ron", updated_data).unwrap();
|
||||
|
||||
println!("{:#?}", database);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
[package]
|
||||
authors = ["Marcel Müller <neikos@neikos.email>"]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.3.3"
|
||||
rocket_codegen = "0.3.3"
|
||||
serde = "1.0.23"
|
||||
serde_derive = "1.0.23"
|
||||
|
||||
[dependencies.rocket_contrib]
|
||||
default-features = false
|
||||
features = ["handlebars_templates"]
|
||||
version = "0.3.3"
|
||||
|
||||
[dependencies.rustbreak]
|
||||
path = "../.."
|
||||
features = ["ron_enc"]
|
||||
@@ -1,26 +0,0 @@
|
||||
(
|
||||
pastes: [
|
||||
(
|
||||
user: "neikos@neikos.email",
|
||||
body: "Hello :)",
|
||||
),
|
||||
(
|
||||
user: "neikos@neikos.email",
|
||||
body: "This works great!",
|
||||
),
|
||||
(
|
||||
user: "neikos@neikos.email",
|
||||
body: "Hehe",
|
||||
),
|
||||
(
|
||||
user: "neikos@neikos.email",
|
||||
body: "Hello World :)",
|
||||
),
|
||||
],
|
||||
users: {
|
||||
"neikos@neikos.email": (
|
||||
username: "neikos@neikos.email",
|
||||
password: "asdf",
|
||||
),
|
||||
},
|
||||
)
|
||||
@@ -1,159 +0,0 @@
|
||||
#![feature(plugin, decl_macro, custom_derive)]
|
||||
#![plugin(rocket_codegen)]
|
||||
|
||||
extern crate rustbreak;
|
||||
extern crate rocket;
|
||||
extern crate rocket_contrib;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use rocket::{State, Outcome};
|
||||
use rocket::http::{Cookies, Cookie};
|
||||
use rocket::request::{self, Request, FromRequest, Form};
|
||||
use rocket::response::Redirect;
|
||||
use rocket_contrib::Template;
|
||||
use rustbreak::FileDatabase;
|
||||
use rustbreak::deser::Ron;
|
||||
|
||||
// We create a type alias so that we always associate the same types to it
|
||||
type DB = FileDatabase<ServerData, Ron>;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct Paste {
|
||||
user: String,
|
||||
body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, FromForm)]
|
||||
struct NewPaste {
|
||||
body: String
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, FromForm)]
|
||||
struct User {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl <'a, 'r> FromRequest<'a, 'r> for User {
|
||||
type Error = ();
|
||||
|
||||
fn from_request(request: &'a Request<'r>) -> request::Outcome<User, ()> {
|
||||
let mut cookies = request.cookies();
|
||||
let db = request.guard::<State<DB>>()?;
|
||||
match cookies.get_private("user_id") {
|
||||
Some(cookie) => {
|
||||
let mut outcome = Outcome::Forward(());
|
||||
let _ = db.read(|db| {
|
||||
if db.users.contains_key(cookie.value()) {
|
||||
outcome = Outcome::Success(db.users.get(cookie.value()).unwrap().clone());
|
||||
}
|
||||
});
|
||||
return outcome;
|
||||
}
|
||||
None => return Outcome::Forward(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
struct ServerData {
|
||||
pastes: Vec<Paste>,
|
||||
users: HashMap<String, User>
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct TemplateData {
|
||||
pastes: Vec<Paste>,
|
||||
logged_in: bool,
|
||||
user: String
|
||||
}
|
||||
|
||||
|
||||
// Routing
|
||||
|
||||
#[get("/")]
|
||||
fn index(db: State<DB>, user: Option<User>) -> Template {
|
||||
let mut data = TemplateData {
|
||||
logged_in: user.is_some(),
|
||||
user: user.map(|u| u.username).unwrap_or_else(|| String::new()),
|
||||
pastes: vec![],
|
||||
};
|
||||
let _ = db.read(|db| {
|
||||
data.pastes = db.pastes.clone();
|
||||
});
|
||||
|
||||
return Template::render("index", &data);
|
||||
}
|
||||
|
||||
#[post("/register", data = "<req_user>")]
|
||||
fn post_register(db: State<DB>, req_user: Form<User>, mut cookies: Cookies) -> Redirect {
|
||||
let user = req_user.into_inner();
|
||||
let _ = db.write(|db| {
|
||||
if db.users.contains_key(&user.username) {
|
||||
return;
|
||||
}
|
||||
db.users.insert(user.username.clone(), user.clone());
|
||||
cookies.add_private(
|
||||
Cookie::build("user_id", user.username.clone()).http_only(true).finish()
|
||||
);
|
||||
});
|
||||
let _ = db.save();
|
||||
|
||||
Redirect::to("/")
|
||||
}
|
||||
|
||||
#[post("/login", data = "<req_user>")]
|
||||
fn post_login(db: State<DB>, req_user: Form<User>, mut cookies: Cookies) -> Redirect {
|
||||
let user = req_user.into_inner();
|
||||
let _ = db.read(|db| {
|
||||
match db.users.get(&user.username) {
|
||||
Some(u) => {
|
||||
if u.password == user.password {
|
||||
cookies.add_private(
|
||||
Cookie::build("user_id", user.username.clone()).http_only(true).finish()
|
||||
);
|
||||
}
|
||||
}
|
||||
None => ()
|
||||
}
|
||||
});
|
||||
|
||||
Redirect::to("/")
|
||||
}
|
||||
|
||||
#[post("/paste", data = "<paste>")]
|
||||
fn post_paste(db: State<DB>, user: User, paste: Form<NewPaste>) -> Redirect {
|
||||
let body : String = paste.into_inner().body.clone();
|
||||
let _ = db.write(|db| {
|
||||
let paste = Paste {
|
||||
body: body,
|
||||
user: user.username.clone(),
|
||||
};
|
||||
db.pastes.push(paste);
|
||||
});
|
||||
let _ = db.save();
|
||||
|
||||
Redirect::to("/")
|
||||
}
|
||||
|
||||
#[get("/dummy")]
|
||||
fn get_dummy() -> &'static str {
|
||||
"Hello World"
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let db : DB = FileDatabase::from_path("server.ron", ServerData {
|
||||
pastes: vec![],
|
||||
users: HashMap::new(),
|
||||
}).unwrap();
|
||||
let _ = db.load();
|
||||
|
||||
|
||||
rocket::ignite()
|
||||
.mount("/", routes![index, post_login, post_paste, post_register, get_dummy])
|
||||
.attach(Template::fairing())
|
||||
.manage(db)
|
||||
.launch();
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Rustbreak Pastes</title>
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{{#if logged_in }}
|
||||
<div class="container">
|
||||
<div>
|
||||
You are logged in as: <strong>{{user}}</strong>
|
||||
</div>
|
||||
<form action="/paste" method="post">
|
||||
<div class="field">
|
||||
<label class="label is-small">Name</label>
|
||||
<div class="control">
|
||||
<textarea class="textarea" name="body" row="4"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control is-small">
|
||||
<button class="button is-link is-small">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<form action="/login" method="post" class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small">Name</label>
|
||||
<div class="control is-small">
|
||||
<input class="input is-small" type="text" placeholder="Username" name="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-small">Password</label>
|
||||
<div class="control is-small">
|
||||
<input class="input is-small" type="password" placeholder="Username" name="password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control is-small">
|
||||
<button class="button is-link is-small">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<form action="/register" method="post" class="column">
|
||||
<div class="field">
|
||||
<label class="label is-small">Name</label>
|
||||
<div class="control is-small">
|
||||
<input class="input is-small" type="text" placeholder="Username" name="username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label is-small">Password</label>
|
||||
<div class="control is-small">
|
||||
<input class="input is-small" type="password" placeholder="Username" name="password">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="control is-small">
|
||||
<button class="button is-small is-link">Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="container">
|
||||
<h1 class="title">Pastes</h1>
|
||||
|
||||
{{#each pastes as |p| ~}}
|
||||
<div class="box">
|
||||
<article class="media">
|
||||
<div class="media-content">
|
||||
<p>
|
||||
<strong>{{p.user}}</strong><br>
|
||||
{{p.body}}
|
||||
<p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{{/each~}}
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="content has-text-centered">
|
||||
<p>
|
||||
Made with love in Germany — Created using Rocket, Handlebars and Rustbreak
|
||||
</p>
|
||||
<a href="https://bulma.io">
|
||||
<img src="https://bulma.io/images/made-with-bulma.png" alt="Made with Bulma" width="128" height="24">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,61 +0,0 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
use rustbreak::deser::{Ron, Yaml};
|
||||
use rustbreak::{backend::FileBackend, FileDatabase};
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
enum Country {
|
||||
Italy,
|
||||
UnitedKingdom,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||
struct Person {
|
||||
name: String,
|
||||
country: Country,
|
||||
}
|
||||
|
||||
fn do_main() -> Result<(), rustbreak::RustbreakError> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let db = FileDatabase::<HashMap<String, Person>, Ron>::load_from_path_or_default("test.ron")?;
|
||||
|
||||
println!("Writing to Database");
|
||||
db.write(|db| {
|
||||
db.insert(
|
||||
"john".into(),
|
||||
Person {
|
||||
name: String::from("John Andersson"),
|
||||
country: Country::Italy,
|
||||
},
|
||||
);
|
||||
db.insert(
|
||||
"fred".into(),
|
||||
Person {
|
||||
name: String::from("Fred Johnson"),
|
||||
country: Country::UnitedKingdom,
|
||||
},
|
||||
);
|
||||
println!("Entries: \n{:#?}", db);
|
||||
})?;
|
||||
|
||||
println!("Syncing Database");
|
||||
db.save()?;
|
||||
|
||||
// Now lets switch it
|
||||
|
||||
let db = db
|
||||
.with_deser(Yaml)
|
||||
.with_backend(FileBackend::from_path_or_create("test.yml").map(|p| p.0)?);
|
||||
db.save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = do_main() {
|
||||
eprintln!("An error has occurred at: \n{}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -21,33 +21,26 @@ use crate::error;
|
||||
/// **Important**: You can only return custom errors if the `other_errors` feature is enabled
|
||||
pub trait Backend {
|
||||
/// Read the all data from the backend.
|
||||
fn get_data(&mut self) -> error::BackendResult<Vec<u8>>;
|
||||
fn get_data(
|
||||
&mut self,
|
||||
) -> impl std::future::Future<Output = error::BackendResult<Vec<u8>>> + Send;
|
||||
|
||||
/// Write the whole slice to the backend.
|
||||
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()>;
|
||||
fn put_data(&mut self, data: &[u8]) -> impl std::future::Future<Output = error::BackendResult<()>> + Send;
|
||||
}
|
||||
|
||||
impl Backend for Box<dyn Backend> {
|
||||
fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
impl<T: Backend> Backend for Box<T>
|
||||
where
|
||||
T: Send,
|
||||
{
|
||||
async fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
use std::ops::DerefMut;
|
||||
self.deref_mut().get_data()
|
||||
self.deref_mut().get_data().await
|
||||
}
|
||||
|
||||
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
use std::ops::DerefMut;
|
||||
self.deref_mut().put_data(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Backend> Backend for Box<T> {
|
||||
fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
use std::ops::DerefMut;
|
||||
self.deref_mut().get_data()
|
||||
}
|
||||
|
||||
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
use std::ops::DerefMut;
|
||||
self.deref_mut().put_data(data)
|
||||
self.deref_mut().put_data(data).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +57,7 @@ pub use path::PathBackend;
|
||||
pub struct FileBackend(std::fs::File);
|
||||
|
||||
impl Backend for FileBackend {
|
||||
fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
async fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
let mut buffer = vec![];
|
||||
@@ -73,7 +66,7 @@ impl Backend for FileBackend {
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
self.0.seek(SeekFrom::Start(0))?;
|
||||
@@ -160,18 +153,20 @@ impl MemoryBackend {
|
||||
}
|
||||
|
||||
impl Backend for MemoryBackend {
|
||||
fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
async fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
println!("Returning data: {:?}", &self.0);
|
||||
Ok(self.0.clone())
|
||||
}
|
||||
|
||||
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
println!("Writing data: {:?}", data);
|
||||
self.0 = data.to_owned();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Backend, FileBackend, MemoryBackend};
|
||||
@@ -323,3 +318,5 @@ mod tests {
|
||||
dir.close().expect("Error while deleting temp directory!");
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
|
||||
use super::Backend;
|
||||
use crate::error;
|
||||
use std::fs::OpenOptions;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::fs::{File, OpenOptions};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
/// A [`Backend`] using a file given the path.
|
||||
///
|
||||
@@ -23,8 +24,8 @@ pub struct PathBackend {
|
||||
impl PathBackend {
|
||||
/// Opens a new [`PathBackend`] for a given path.
|
||||
/// Errors when the file doesn't yet exist.
|
||||
pub fn from_path_or_fail(path: PathBuf) -> error::BackendResult<Self> {
|
||||
OpenOptions::new().read(true).open(path.as_path())?;
|
||||
pub async fn from_path_or_fail(path: PathBuf) -> error::BackendResult<Self> {
|
||||
OpenOptions::new().read(true).open(path.as_path()).await?;
|
||||
Ok(Self { path })
|
||||
}
|
||||
|
||||
@@ -32,41 +33,44 @@ impl PathBackend {
|
||||
/// Creates a file if it doesn't yet exist.
|
||||
///
|
||||
/// Returns the [`PathBackend`] and whether the file already existed.
|
||||
pub fn from_path_or_create(path: PathBuf) -> error::BackendResult<(Self, bool)> {
|
||||
pub async fn from_path_or_create(path: PathBuf) -> error::BackendResult<(Self, bool)> {
|
||||
let exists = path.as_path().is_file();
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path.as_path())?;
|
||||
.open(path.as_path())
|
||||
.await?;
|
||||
Ok((Self { path }, exists))
|
||||
}
|
||||
|
||||
/// Opens a new [`PathBackend`] for a given path.
|
||||
/// Creates a file if it doesn't yet exist, and calls `closure` with it.
|
||||
pub fn from_path_or_create_and<C>(path: PathBuf, closure: C) -> error::BackendResult<Self>
|
||||
pub async fn from_path_or_create_and<C>(path: PathBuf, closure: C) -> error::BackendResult<Self>
|
||||
where
|
||||
C: FnOnce(&mut std::fs::File),
|
||||
C: AsyncFnOnce(&mut File),
|
||||
{
|
||||
let exists = path.as_path().is_file();
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(path.as_path())?;
|
||||
.open(path.as_path())
|
||||
.await?;
|
||||
if !exists {
|
||||
closure(&mut file)
|
||||
closure(&mut file).await
|
||||
}
|
||||
Ok(Self { path })
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for PathBackend {
|
||||
fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
use std::io::Read;
|
||||
|
||||
let mut file = OpenOptions::new().read(true).open(self.path.as_path())?;
|
||||
async fn get_data(&mut self) -> error::BackendResult<Vec<u8>> {
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(self.path.as_path())
|
||||
.await?;
|
||||
let mut buffer = vec![];
|
||||
file.read_to_end(&mut buffer)?;
|
||||
file.read_to_end(&mut buffer).await?;
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
@@ -74,7 +78,7 @@ impl Backend for PathBackend {
|
||||
///
|
||||
/// This won't corrupt the existing database file if the program panics
|
||||
/// during the save.
|
||||
fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
async fn put_data(&mut self, data: &[u8]) -> error::BackendResult<()> {
|
||||
use std::io::Write;
|
||||
|
||||
#[allow(clippy::or_fun_call)] // `Path::new` is a zero cost conversion
|
||||
@@ -89,58 +93,59 @@ impl Backend for PathBackend {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Backend, PathBackend};
|
||||
use std::io::Write;
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_path_backend_existing() {
|
||||
async fn test_path_backend_existing() {
|
||||
let file = NamedTempFile::new().expect("could not create temporary file");
|
||||
let (mut backend, existed) = PathBackend::from_path_or_create(file.path().to_owned())
|
||||
.await
|
||||
.expect("could not create backend");
|
||||
assert!(existed);
|
||||
let data = [4, 5, 1, 6, 8, 1];
|
||||
|
||||
backend.put_data(&data).expect("could not put data");
|
||||
assert_eq!(backend.get_data().expect("could not get data"), data);
|
||||
backend.put_data(&data).await.expect("could not put data");
|
||||
assert_eq!(backend.get_data().await.expect("could not get data"), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_path_backend_new() {
|
||||
async fn test_path_backend_new() {
|
||||
let dir = tempfile::tempdir().expect("could not create temporary directory");
|
||||
let mut file_path = dir.path().to_owned();
|
||||
file_path.push("rustbreak_path_db.db");
|
||||
let (mut backend, existed) =
|
||||
PathBackend::from_path_or_create(file_path).expect("could not create backend");
|
||||
PathBackend::from_path_or_create(file_path).await.expect("could not create backend");
|
||||
assert!(!existed);
|
||||
let data = [4, 5, 1, 6, 8, 1];
|
||||
|
||||
backend.put_data(&data).expect("could not put data");
|
||||
assert_eq!(backend.get_data().expect("could not get data"), data);
|
||||
backend.put_data(&data).await.expect("could not put data");
|
||||
assert_eq!(backend.get_data().await.expect("could not get data"), data);
|
||||
dir.close().expect("Error while deleting temp directory!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_path_backend_nofail() {
|
||||
async fn test_path_backend_nofail() {
|
||||
let file = NamedTempFile::new().expect("could not create temporary file");
|
||||
let file_path = file.path().to_owned();
|
||||
let mut backend = PathBackend::from_path_or_fail(file_path).expect("should not fail");
|
||||
let mut backend = PathBackend::from_path_or_fail(file_path).await.expect("should not fail");
|
||||
let data = [4, 5, 1, 6, 8, 1];
|
||||
|
||||
backend.put_data(&data).expect("could not put data");
|
||||
assert_eq!(backend.get_data().expect("could not get data"), data);
|
||||
backend.put_data(&data).await.expect("could not put data");
|
||||
assert_eq!(backend.get_data().await.expect("could not get data"), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_path_backend_fail_notfound() {
|
||||
async fn test_path_backend_fail_notfound() {
|
||||
let dir = tempfile::tempdir().expect("could not create temporary directory");
|
||||
let mut file_path = dir.path().to_owned();
|
||||
file_path.push("rustbreak_path_db.db");
|
||||
let err =
|
||||
PathBackend::from_path_or_fail(file_path).expect_err("should fail with file not found");
|
||||
PathBackend::from_path_or_fail(file_path).await.expect_err("should fail with file not found");
|
||||
if let crate::error::BackendError::Io(io_err) = &err {
|
||||
assert_eq!(std::io::ErrorKind::NotFound, io_err.kind());
|
||||
} else {
|
||||
@@ -150,40 +155,40 @@ mod tests {
|
||||
}
|
||||
|
||||
// If the file already exists, the closure shouldn't be called.
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_path_backend_create_and_existing_nocall() {
|
||||
async fn test_path_backend_create_and_existing_nocall() {
|
||||
let file = NamedTempFile::new().expect("could not create temporary file");
|
||||
let mut backend = PathBackend::from_path_or_create_and(file.path().to_owned(), |_| {
|
||||
let mut backend = PathBackend::from_path_or_create_and(file.path().to_owned(), async |_| {
|
||||
panic!("Closure called but file already existed");
|
||||
})
|
||||
.expect("could not create backend");
|
||||
.await.expect("could not create backend");
|
||||
let data = [4, 5, 1, 6, 8, 1];
|
||||
|
||||
backend.put_data(&data).expect("could not put data");
|
||||
assert_eq!(backend.get_data().expect("could not get data"), data);
|
||||
backend.put_data(&data).await.expect("could not put data");
|
||||
assert_eq!(backend.get_data().await.expect("could not get data"), data);
|
||||
}
|
||||
|
||||
// If the file does not yet exist, the closure should be called.
|
||||
#[test]
|
||||
#[tokio::test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn test_path_backend_create_and_new() {
|
||||
async fn test_path_backend_create_and_new() {
|
||||
let dir = tempfile::tempdir().expect("could not create temporary directory");
|
||||
let mut file_path = dir.path().to_owned();
|
||||
file_path.push("rustbreak_path_db.db");
|
||||
let mut backend = PathBackend::from_path_or_create_and(file_path, |f| {
|
||||
let mut backend = PathBackend::from_path_or_create_and(file_path, async |f| {
|
||||
f.write_all(b"this is a new file")
|
||||
.expect("could not write to file")
|
||||
.await.expect("could not write to file")
|
||||
})
|
||||
.expect("could not create backend");
|
||||
.await.expect("could not create backend");
|
||||
assert_eq!(
|
||||
backend.get_data().expect("could not get data"),
|
||||
backend.get_data().await.expect("could not get data"),
|
||||
b"this is a new file"
|
||||
);
|
||||
let data = [4, 5, 1, 6, 8, 1];
|
||||
|
||||
backend.put_data(&data).expect("could not put data");
|
||||
assert_eq!(backend.get_data().expect("could not get data"), data);
|
||||
backend.put_data(&data).await.expect("could not put data");
|
||||
assert_eq!(backend.get_data().await.expect("could not get data"), data);
|
||||
dir.close().expect("Error while deleting temp directory!");
|
||||
}
|
||||
}
|
||||
|
||||
242
src/lib.rs
242
src/lib.rs
@@ -203,10 +203,10 @@ pub use crate::deser::DeSerializer;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
use tokio::sync::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||||
|
||||
#[cfg(feature = "mmap")]
|
||||
use crate::backend::MmapStorage;
|
||||
@@ -291,11 +291,11 @@ where
|
||||
/// # func().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn write<T, R>(&self, task: T) -> error::Result<R>
|
||||
pub async fn write<T, R>(&self, task: T) -> error::Result<R>
|
||||
where
|
||||
T: FnOnce(&mut Data) -> R,
|
||||
{
|
||||
let mut lock = self.data.write().map_err(|_| RustbreakError::Poison)?;
|
||||
let mut lock = self.data.write().await;
|
||||
Ok(task(&mut lock))
|
||||
}
|
||||
|
||||
@@ -373,11 +373,11 @@ where
|
||||
/// # func().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn write_safe<T>(&self, task: T) -> error::Result<()>
|
||||
pub async fn write_safe<T>(&self, task: T) -> error::Result<()>
|
||||
where
|
||||
T: FnOnce(&mut Data) + std::panic::UnwindSafe,
|
||||
{
|
||||
let mut lock = self.data.write().map_err(|_| RustbreakError::Poison)?;
|
||||
let mut lock = self.data.write().await;
|
||||
let mut data = lock.clone();
|
||||
std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| {
|
||||
task(&mut data);
|
||||
@@ -404,11 +404,11 @@ where
|
||||
/// any subsequent writes/reads will fail with an
|
||||
/// [`error::RustbreakError::Poison`]. You can only recover from
|
||||
/// this by re-creating the Database Object.
|
||||
pub fn read<T, R>(&self, task: T) -> error::Result<R>
|
||||
pub async fn read<T, R>(&self, task: T) -> error::Result<R>
|
||||
where
|
||||
T: FnOnce(&Data) -> R,
|
||||
{
|
||||
let mut lock = self.data.read().map_err(|_| RustbreakError::Poison)?;
|
||||
let mut lock = self.data.read().await;
|
||||
Ok(task(&mut lock))
|
||||
}
|
||||
|
||||
@@ -448,8 +448,8 @@ where
|
||||
/// # func().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn borrow_data<'a>(&'a self) -> error::Result<RwLockReadGuard<'a, Data>> {
|
||||
self.data.read().map_err(|_| RustbreakError::Poison)
|
||||
pub async fn borrow_data<'a>(&'a self) -> RwLockReadGuard<'a, Data> {
|
||||
self.data.read().await
|
||||
}
|
||||
|
||||
/// Write lock the database and get access to the underlying struct.
|
||||
@@ -500,59 +500,60 @@ where
|
||||
/// # func().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn borrow_data_mut<'a>(&'a self) -> error::Result<RwLockWriteGuard<'a, Data>> {
|
||||
self.data.write().map_err(|_| RustbreakError::Poison)
|
||||
pub async fn borrow_data_mut<'a>(&'a self) -> RwLockWriteGuard<'a, Data> {
|
||||
self.data.write().await
|
||||
}
|
||||
|
||||
/// Load data from backend and return this data.
|
||||
fn load_from_backend(backend: &mut Back, deser: &DeSer) -> error::Result<Data> {
|
||||
let new_data = deser.deserialize(&backend.get_data()?[..])?;
|
||||
async fn load_from_backend(backend: &mut Back, deser: &DeSer) -> error::Result<Data> {
|
||||
let new_data = deser.deserialize(&backend.get_data().await?[..])?;
|
||||
|
||||
Ok(new_data)
|
||||
}
|
||||
|
||||
/// Like [`Self::load`] but returns the write lock to data it used.
|
||||
fn load_get_data_lock(&self) -> error::Result<RwLockWriteGuard<'_, Data>> {
|
||||
let mut backend_lock = self.backend.lock().map_err(|_| RustbreakError::Poison)?;
|
||||
async fn load_get_data_lock(&self) -> error::Result<RwLockWriteGuard<'_, Data>> {
|
||||
let mut backend_lock = self.backend.lock().await;
|
||||
|
||||
let fresh_data = Self::load_from_backend(&mut backend_lock, &self.deser)?;
|
||||
let fresh_data = Self::load_from_backend(&mut backend_lock, &self.deser).await?;
|
||||
drop(backend_lock);
|
||||
|
||||
let mut data_write_lock = self.data.write().map_err(|_| RustbreakError::Poison)?;
|
||||
let mut data_write_lock = self.data.write().await;
|
||||
*data_write_lock = fresh_data;
|
||||
Ok(data_write_lock)
|
||||
}
|
||||
|
||||
/// Load the data from the backend.
|
||||
pub fn load(&self) -> error::Result<()> {
|
||||
self.load_get_data_lock().map(|_| ())
|
||||
pub async fn load(&self) -> error::Result<()> {
|
||||
let _ = self.load_get_data_lock().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like [`Self::save`] but with explicit read (or write) lock to data.
|
||||
fn save_data_locked<L: Deref<Target = Data>>(&self, lock: L) -> error::Result<()> {
|
||||
async fn save_data_locked<L: Deref<Target = Data>>(&self, lock: L) -> error::Result<()> {
|
||||
let ser = self.deser.serialize(lock.deref())?;
|
||||
drop(lock);
|
||||
|
||||
let mut backend = self.backend.lock().map_err(|_| RustbreakError::Poison)?;
|
||||
backend.put_data(&ser)?;
|
||||
let mut backend = self.backend.lock().await;
|
||||
backend.put_data(&ser).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Flush the data structure to the backend.
|
||||
pub fn save(&self) -> error::Result<()> {
|
||||
let data = self.data.read().map_err(|_| RustbreakError::Poison)?;
|
||||
self.save_data_locked(data)
|
||||
pub async fn save(&self) -> error::Result<()> {
|
||||
let data = self.data.read().await;
|
||||
self.save_data_locked(data).await
|
||||
}
|
||||
|
||||
/// Get a clone of the data as it is in memory right now.
|
||||
///
|
||||
/// To make sure you have the latest data, call this method with `load`
|
||||
/// true.
|
||||
pub fn get_data(&self, load: bool) -> error::Result<Data> {
|
||||
pub async fn get_data(&self, load: bool) -> error::Result<Data> {
|
||||
let data = if load {
|
||||
self.load_get_data_lock()?
|
||||
self.load_get_data_lock().await?
|
||||
} else {
|
||||
self.data.write().map_err(|_| RustbreakError::Poison)?
|
||||
self.data.write().await
|
||||
};
|
||||
Ok(data.clone())
|
||||
}
|
||||
@@ -560,11 +561,11 @@ where
|
||||
/// Puts the data as is into memory.
|
||||
///
|
||||
/// To save the data afterwards, call with `save` true.
|
||||
pub fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> {
|
||||
let mut data = self.data.write().map_err(|_| RustbreakError::Poison)?;
|
||||
pub async fn put_data(&self, new_data: Data, save: bool) -> error::Result<()> {
|
||||
let mut data = self.data.write().await;
|
||||
*data = new_data;
|
||||
if save {
|
||||
self.save_data_locked(data)
|
||||
self.save_data_locked(data).await
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
@@ -582,10 +583,8 @@ where
|
||||
/// Break a database into its individual parts.
|
||||
pub fn into_inner(self) -> error::Result<(Data, Back, DeSer)> {
|
||||
Ok((
|
||||
self.data.into_inner().map_err(|_| RustbreakError::Poison)?,
|
||||
self.backend
|
||||
.into_inner()
|
||||
.map_err(|_| RustbreakError::Poison)?,
|
||||
self.data.into_inner(),
|
||||
self.backend.into_inner(),
|
||||
self.deser,
|
||||
))
|
||||
}
|
||||
@@ -632,8 +631,8 @@ where
|
||||
/// # func().unwrap();
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn try_clone(&self) -> error::Result<MemoryDatabase<Data, DeSer>> {
|
||||
let lock = self.data.read().map_err(|_| RustbreakError::Poison)?;
|
||||
pub async fn try_clone(&self) -> error::Result<MemoryDatabase<Data, DeSer>> {
|
||||
let lock = self.data.read().await;
|
||||
|
||||
Ok(Database {
|
||||
data: RwLock::new(lock.clone()),
|
||||
@@ -653,13 +652,13 @@ where
|
||||
{
|
||||
/// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents.
|
||||
pub fn load_from_path<S>(path: S) -> error::Result<Self>
|
||||
pub async fn load_from_path<S>(path: S) -> error::Result<Self>
|
||||
where
|
||||
S: AsRef<std::path::Path>,
|
||||
{
|
||||
let mut backend = FileBackend::from_path_or_fail(path)?;
|
||||
let deser = DeSer::default();
|
||||
let data = Self::load_from_backend(&mut backend, &deser)?;
|
||||
let data = Self::load_from_backend(&mut backend, &deser).await?;
|
||||
|
||||
let db = Self {
|
||||
data: RwLock::new(data),
|
||||
@@ -674,7 +673,7 @@ where
|
||||
/// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents. If the file does not exist, initialise with
|
||||
/// `data`.
|
||||
pub fn load_from_path_or<S>(path: S, data: Data) -> error::Result<Self>
|
||||
pub async fn load_from_path_or<S>(path: S, data: Data) -> error::Result<Self>
|
||||
where
|
||||
S: AsRef<std::path::Path>,
|
||||
{
|
||||
@@ -682,7 +681,7 @@ where
|
||||
let deser = DeSer::default();
|
||||
if !exists {
|
||||
let ser = deser.serialize(&data)?;
|
||||
backend.put_data(&ser)?;
|
||||
backend.put_data(&ser).await?;
|
||||
}
|
||||
|
||||
let db = Self {
|
||||
@@ -692,7 +691,7 @@ where
|
||||
};
|
||||
|
||||
if exists {
|
||||
db.load()?;
|
||||
db.load().await?;
|
||||
}
|
||||
|
||||
Ok(db)
|
||||
@@ -703,7 +702,7 @@ where
|
||||
/// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents. If the file does not exist, `closure` is
|
||||
/// called and the database is initialised with it's return value.
|
||||
pub fn load_from_path_or_else<S, C>(path: S, closure: C) -> error::Result<Self>
|
||||
pub async fn load_from_path_or_else<S, C>(path: S, closure: C) -> error::Result<Self>
|
||||
where
|
||||
S: AsRef<std::path::Path>,
|
||||
C: FnOnce() -> Data,
|
||||
@@ -711,12 +710,12 @@ where
|
||||
let (mut backend, exists) = FileBackend::from_path_or_create(path)?;
|
||||
let deser = DeSer::default();
|
||||
let data = if exists {
|
||||
Self::load_from_backend(&mut backend, &deser)?
|
||||
Self::load_from_backend(&mut backend, &deser).await?
|
||||
} else {
|
||||
let data = closure();
|
||||
|
||||
let ser = deser.serialize(&data)?;
|
||||
backend.put_data(&ser)?;
|
||||
backend.put_data(&ser).await?;
|
||||
|
||||
data
|
||||
};
|
||||
@@ -735,7 +734,7 @@ where
|
||||
/// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path).
|
||||
/// Contents are not loaded. If the file does not exist, it is
|
||||
/// initialised with `data`. Frontend is always initialised with `data`.
|
||||
pub fn create_at_path<S>(path: S, data: Data) -> error::Result<Self>
|
||||
pub async fn create_at_path<S>(path: S, data: Data) -> error::Result<Self>
|
||||
where
|
||||
S: AsRef<std::path::Path>,
|
||||
{
|
||||
@@ -743,7 +742,7 @@ where
|
||||
let deser = DeSer::default();
|
||||
if !exists {
|
||||
let ser = deser.serialize(&data)?;
|
||||
backend.put_data(&ser)?;
|
||||
backend.put_data(&ser).await?;
|
||||
}
|
||||
|
||||
let db = Self {
|
||||
@@ -776,11 +775,11 @@ where
|
||||
/// Create new [`FileDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents. If the file does not exist, initialise with
|
||||
/// `Data::default`.
|
||||
pub fn load_from_path_or_default<S>(path: S) -> error::Result<Self>
|
||||
pub async fn load_from_path_or_default<S>(path: S) -> error::Result<Self>
|
||||
where
|
||||
S: AsRef<std::path::Path>,
|
||||
{
|
||||
Self::load_from_path_or_else(path, Data::default)
|
||||
Self::load_from_path_or_else(path, Data::default).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -794,10 +793,10 @@ where
|
||||
{
|
||||
/// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents.
|
||||
pub fn load_from_path(path: PathBuf) -> error::Result<Self> {
|
||||
let mut backend = PathBackend::from_path_or_fail(path)?;
|
||||
pub async fn load_from_path(path: PathBuf) -> error::Result<Self> {
|
||||
let mut backend = PathBackend::from_path_or_fail(path).await?;
|
||||
let deser = DeSer::default();
|
||||
let data = Self::load_from_backend(&mut backend, &deser)?;
|
||||
let data = Self::load_from_backend(&mut backend, &deser).await?;
|
||||
|
||||
let db = Self {
|
||||
data: RwLock::new(data),
|
||||
@@ -812,12 +811,12 @@ where
|
||||
/// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents. If the file does not exist, initialise with
|
||||
/// `data`.
|
||||
pub fn load_from_path_or(path: PathBuf, data: Data) -> error::Result<Self> {
|
||||
let (mut backend, exists) = PathBackend::from_path_or_create(path)?;
|
||||
pub async fn load_from_path_or(path: PathBuf, data: Data) -> error::Result<Self> {
|
||||
let (mut backend, exists) = PathBackend::from_path_or_create(path).await?;
|
||||
let deser = DeSer::default();
|
||||
if !exists {
|
||||
let ser = deser.serialize(&data)?;
|
||||
backend.put_data(&ser)?;
|
||||
backend.put_data(&ser).await?;
|
||||
}
|
||||
|
||||
let db = Self {
|
||||
@@ -827,7 +826,7 @@ where
|
||||
};
|
||||
|
||||
if exists {
|
||||
db.load()?;
|
||||
db.load().await?;
|
||||
}
|
||||
|
||||
Ok(db)
|
||||
@@ -838,19 +837,19 @@ where
|
||||
/// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents. If the file does not exist, `closure` is
|
||||
/// called and the database is initialised with it's return value.
|
||||
pub fn load_from_path_or_else<C>(path: PathBuf, closure: C) -> error::Result<Self>
|
||||
pub async fn load_from_path_or_else<C>(path: PathBuf, closure: C) -> error::Result<Self>
|
||||
where
|
||||
C: FnOnce() -> Data,
|
||||
{
|
||||
let (mut backend, exists) = PathBackend::from_path_or_create(path)?;
|
||||
let (mut backend, exists) = PathBackend::from_path_or_create(path).await?;
|
||||
let deser = DeSer::default();
|
||||
let data = if exists {
|
||||
Self::load_from_backend(&mut backend, &deser)?
|
||||
Self::load_from_backend(&mut backend, &deser).await?
|
||||
} else {
|
||||
let data = closure();
|
||||
|
||||
let ser = deser.serialize(&data)?;
|
||||
backend.put_data(&ser)?;
|
||||
backend.put_data(&ser).await?;
|
||||
|
||||
data
|
||||
};
|
||||
@@ -869,12 +868,12 @@ where
|
||||
/// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path).
|
||||
/// Contents are not loaded. If the file does not exist, it is
|
||||
/// initialised with `data`. Frontend is always initialised with `data`.
|
||||
pub fn create_at_path(path: PathBuf, data: Data) -> error::Result<Self> {
|
||||
let (mut backend, exists) = PathBackend::from_path_or_create(path)?;
|
||||
pub async fn create_at_path(path: PathBuf, data: Data) -> error::Result<Self> {
|
||||
let (mut backend, exists) = PathBackend::from_path_or_create(path).await?;
|
||||
let deser = DeSer::default();
|
||||
if !exists {
|
||||
let ser = deser.serialize(&data)?;
|
||||
backend.put_data(&ser)?;
|
||||
backend.put_data(&ser).await?;
|
||||
}
|
||||
|
||||
let db = Self {
|
||||
@@ -896,8 +895,8 @@ where
|
||||
/// Create new [`PathDatabase`] from the file at [`Path`](std::path::Path),
|
||||
/// and load the contents. If the file does not exist, initialise with
|
||||
/// `Data::default`.
|
||||
pub fn load_from_path_or_default(path: PathBuf) -> error::Result<Self> {
|
||||
Self::load_from_path_or_else(path, Data::default)
|
||||
pub async fn load_from_path_or_default(path: PathBuf) -> error::Result<Self> {
|
||||
Self::load_from_path_or_else(path, Data::default).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,8 +1008,8 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
type TestData = HashMap<usize, String>;
|
||||
type TestDb<B> = Database<TestData, B, crate::deser::Ron>;
|
||||
@@ -1024,7 +1023,7 @@ mod tests {
|
||||
}
|
||||
|
||||
/// Used to test that `Default::default` isn't called.
|
||||
#[derive(Clone, Debug, Serialize, serde::Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
struct PanicDefault;
|
||||
impl Default for PanicDefault {
|
||||
fn default() -> Self {
|
||||
@@ -1032,131 +1031,150 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_db_and_read() {
|
||||
#[tokio::test]
|
||||
async fn create_db_and_read() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_twice() {
|
||||
#[tokio::test]
|
||||
async fn write_twice() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
db.write(|d| d.insert(3, "Write to db".to_string()))
|
||||
.await
|
||||
.expect("Rustbreak write error");
|
||||
db.write(|d| d.insert(3, "Second write".to_string()))
|
||||
.await
|
||||
.expect("Rustbreak write error");
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Second write",
|
||||
db.read(|d| d.get(&3).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_load() {
|
||||
#[tokio::test]
|
||||
async fn save_load() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
db.save().expect("Rustbreak save error");
|
||||
db.write(|d| d.clear()).expect("Rustbreak write error");
|
||||
db.load().expect("Rustbreak load error");
|
||||
db.save().await.expect("Rustbreak save error");
|
||||
db.write(|d| d.clear())
|
||||
.await
|
||||
.expect("Rustbreak write error");
|
||||
db.load().await.expect("Rustbreak load error");
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writesafe_twice() {
|
||||
#[tokio::test]
|
||||
async fn writesafe_twice() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
db.write_safe(|d| {
|
||||
d.insert(3, "Write to db".to_string());
|
||||
})
|
||||
.await
|
||||
.expect("Rustbreak write error");
|
||||
db.write_safe(|d| {
|
||||
d.insert(3, "Second write".to_string());
|
||||
})
|
||||
.await
|
||||
.expect("Rustbreak write error");
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Second write",
|
||||
db.read(|d| d.get(&3).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writesafe_panic() {
|
||||
#[tokio::test]
|
||||
async fn writesafe_panic() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
let err = db
|
||||
.write_safe(|d| {
|
||||
d.clear();
|
||||
panic!("Panic should be catched")
|
||||
})
|
||||
.await
|
||||
.expect_err("Did not error on panic in safe write!");
|
||||
assert!(matches!(err, RustbreakError::WritePanic));
|
||||
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borrow_data_twice() {
|
||||
#[tokio::test]
|
||||
async fn borrow_data_twice() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
let readlock1 = db.borrow_data().expect("Rustbreak readlock error");
|
||||
let readlock2 = db.borrow_data().expect("Rustbreak readlock error");
|
||||
let readlock1 = db.borrow_data().await;
|
||||
let readlock2 = db.borrow_data().await;
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
readlock1.get(&1).expect("Should be `Some` but was `None`")
|
||||
@@ -1180,89 +1198,103 @@ mod tests {
|
||||
assert_eq!(*readlock1, *readlock2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borrow_data_mut() {
|
||||
#[tokio::test]
|
||||
async fn borrow_data_mut() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
let mut writelock = db.borrow_data_mut().expect("Rustbreak writelock error");
|
||||
let mut writelock = db.borrow_data_mut().await;
|
||||
writelock.insert(3, "Write to db".to_string());
|
||||
drop(writelock);
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Write to db",
|
||||
db.read(|d| d.get(&3).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_data_mem() {
|
||||
#[tokio::test]
|
||||
async fn get_data_mem() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
let data = db.get_data(false).expect("could not get data");
|
||||
let data = db.get_data(false).await.expect("could not get data");
|
||||
assert_eq!(test_data(), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_data_load() {
|
||||
#[tokio::test]
|
||||
async fn get_data_load() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
db.save().expect("Rustbreak save error");
|
||||
db.write(|d| d.clear()).expect("Rustbreak write error");
|
||||
let data = db.get_data(true).expect("could not get data");
|
||||
db.save().await.expect("Rustbreak save error");
|
||||
db.write(|d| d.clear())
|
||||
.await
|
||||
.expect("Rustbreak write error");
|
||||
let data = db.get_data(true).await.expect("could not get data");
|
||||
assert_eq!(test_data(), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_data_mem() {
|
||||
#[tokio::test]
|
||||
async fn put_data_mem() {
|
||||
let db = TestMemDb::memory(TestData::default()).expect("Could not create database");
|
||||
db.put_data(test_data(), false).expect("could not put data");
|
||||
db.put_data(test_data(), false)
|
||||
.await
|
||||
.expect("could not put data");
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
let data = db.get_data(false).expect("could not get data");
|
||||
let data = db.get_data(false).await.expect("could not get data");
|
||||
assert_eq!(test_data(), data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn put_data_save() {
|
||||
#[tokio::test]
|
||||
async fn put_data_save() {
|
||||
let db = TestMemDb::memory(TestData::default()).expect("Could not create database");
|
||||
db.put_data(test_data(), true).expect("could not put data");
|
||||
db.load().expect("Rustbreak load error");
|
||||
db.put_data(test_data(), true)
|
||||
.await
|
||||
.expect("could not put data");
|
||||
db.load().await.expect("Rustbreak load error");
|
||||
assert_eq!(
|
||||
"Hello World",
|
||||
db.read(|d| d.get(&1).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
assert_eq!(
|
||||
"Rustbreak",
|
||||
db.read(|d| d.get(&100).cloned())
|
||||
.await
|
||||
.expect("Rustbreak read error")
|
||||
.expect("Should be `Some` but was `None`")
|
||||
);
|
||||
let data = db.get_data(false).expect("could not get data");
|
||||
let data = db.get_data(false).await.expect("could not get data");
|
||||
assert_eq!(test_data(), data);
|
||||
}
|
||||
|
||||
/*
|
||||
#[test]
|
||||
fn save_and_into_inner() {
|
||||
let db = TestMemDb::memory(test_data()).expect("Could not create database");
|
||||
@@ -1475,4 +1507,6 @@ mod tests {
|
||||
let readlock = db.borrow_data().expect("Rustbreak readlock error");
|
||||
assert_eq!(test_data(), *readlock);
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
use rustbreak::backend::Backend;
|
||||
use rustbreak::deser::{Bincode, DeSerializer, Ron, Yaml};
|
||||
use rustbreak::{Database, FileDatabase, MemoryDatabase, MmapDatabase, PathDatabase};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use tempfile::tempfile;
|
||||
|
||||
type Data = std::collections::HashMap<u64, String>;
|
||||
|
||||
fn conv(mut data: Data) -> Data {
|
||||
data.remove(&2);
|
||||
data.insert(0, "Newly inserted".to_string());
|
||||
data.insert(16, "Convertion succesful".to_string());
|
||||
data
|
||||
}
|
||||
|
||||
fn test_basic_save_load<B: Backend + Debug, S: DeSerializer<Data> + Debug>(
|
||||
db: &Database<Data, B, S>,
|
||||
) {
|
||||
db.write(|db| {
|
||||
db.insert(2, "Hello world!".to_string());
|
||||
})
|
||||
.expect("rustbreak write error");
|
||||
db.write_safe(|db| {
|
||||
db.insert(5, "Hello again".to_string());
|
||||
})
|
||||
.expect("rustbreak write error");
|
||||
db.save().expect("error while saving");
|
||||
let saved_state = db.get_data(false).expect("could not get data");
|
||||
|
||||
// test that loading correctly restores the data
|
||||
db.write(|db| {
|
||||
db.clear();
|
||||
})
|
||||
.expect("rustbreak write error");
|
||||
db.load().expect("rustbreak load error");
|
||||
|
||||
let len = db.read(|db| db.len()).expect("rustbreak read error");
|
||||
assert_eq!(len, 2);
|
||||
|
||||
let second = db
|
||||
.read(|db| db.get(&2).cloned())
|
||||
.expect("rustbreak read error");
|
||||
assert_eq!(second, Some(String::from("Hello world!")));
|
||||
|
||||
let fith = db
|
||||
.read(|db| db.get(&5).cloned())
|
||||
.expect("rustbreak read error");
|
||||
assert_eq!(fith, Some(String::from("Hello again")));
|
||||
|
||||
let data = db.borrow_data().expect("rustbreak borrow error");
|
||||
assert_eq!(&saved_state, data.deref());
|
||||
}
|
||||
|
||||
fn test_multi_borrow<B: Backend + Debug, S: DeSerializer<Data> + Debug>(db: &Database<Data, B, S>) {
|
||||
let data1 = db.borrow_data().expect("rustbreak borrow error");
|
||||
let data2 = db.borrow_data().expect("rustbreak borrow error");
|
||||
let data3 = db.borrow_data().expect("rustbreak borrow error");
|
||||
assert_eq!(data1.deref(), data2.deref());
|
||||
assert_eq!(data1.deref(), data3.deref());
|
||||
}
|
||||
|
||||
fn test_put_data<B: Backend + Debug, S: DeSerializer<Data> + Debug>(db: &Database<Data, B, S>) {
|
||||
let backup = db.get_data(true).expect("could not get data");
|
||||
|
||||
let mut other_state = Data::new();
|
||||
other_state.insert(3, "Foo".to_string());
|
||||
other_state.insert(7, "Bar".to_string());
|
||||
other_state.insert(19, "Bazz".to_string());
|
||||
|
||||
db.put_data(other_state.clone(), true)
|
||||
.expect("could not put data");
|
||||
let data = db.borrow_data().expect("rustbreak borrow error");
|
||||
assert_eq!(&other_state, data.deref());
|
||||
// If we do not explicitly drop `data` here, the subsequent write will freeze
|
||||
drop(data);
|
||||
|
||||
db.write(|db| {
|
||||
db.clear();
|
||||
})
|
||||
.expect("rustbreak write error");
|
||||
db.load().expect("rustbreak load error");
|
||||
|
||||
let data = db.borrow_data().expect("rustbreak borrow error");
|
||||
assert_eq!(&other_state, data.deref());
|
||||
drop(data);
|
||||
|
||||
db.put_data(backup, false).expect("could not put data");
|
||||
}
|
||||
|
||||
fn test_convert_data<B: Backend + Debug, S: DeSerializer<Data> + Debug>(db: Database<Data, B, S>) {
|
||||
let db = db.convert_data(conv).expect("Could not convert data");
|
||||
|
||||
let mut expected_state = Data::new();
|
||||
expected_state.insert(0, "Newly inserted".to_string());
|
||||
expected_state.insert(5, "Hello again".to_string());
|
||||
expected_state.insert(16, "Convertion succesful".to_string());
|
||||
assert_eq!(
|
||||
&expected_state,
|
||||
db.borrow_data().expect("rustbreak borrow error").deref()
|
||||
);
|
||||
}
|
||||
|
||||
fn create_filedb<S: DeSerializer<Data> + Debug>() -> FileDatabase<Data, S> {
|
||||
FileDatabase::from_file(tempfile().expect("could not create file"), Data::default())
|
||||
.expect("could not create database")
|
||||
}
|
||||
|
||||
fn create_filedb_from_path<S: DeSerializer<Data> + Debug>() -> FileDatabase<Data, S> {
|
||||
let file = tempfile::NamedTempFile::new().expect("could not create temporary file");
|
||||
FileDatabase::create_at_path(file.path(), Data::default()).expect("could not create database")
|
||||
}
|
||||
|
||||
fn create_memdb<S: DeSerializer<Data> + Debug>() -> MemoryDatabase<Data, S> {
|
||||
MemoryDatabase::memory(Data::default()).expect("could not create database")
|
||||
}
|
||||
|
||||
fn create_mmapdb<S: DeSerializer<Data> + Debug>() -> MmapDatabase<Data, S> {
|
||||
MmapDatabase::mmap(Data::default()).expect("could not create database")
|
||||
}
|
||||
|
||||
fn create_mmapdb_with_size<S: DeSerializer<Data> + Debug>(size: usize) -> MmapDatabase<Data, S> {
|
||||
MmapDatabase::mmap_with_size(Data::default(), size).expect("could not create database")
|
||||
}
|
||||
|
||||
fn create_pathdb<S: DeSerializer<Data> + Debug>() -> PathDatabase<Data, S> {
|
||||
let file = tempfile::NamedTempFile::new().expect("could not create temporary file");
|
||||
PathDatabase::create_at_path(file.path().to_owned(), Data::default())
|
||||
.expect("could not create database")
|
||||
}
|
||||
|
||||
macro_rules! test_basic_save_load {
|
||||
($name:ident, $db:expr, $enc:ty) => {
|
||||
#[test]
|
||||
#[cfg_attr(miri, ignore)]
|
||||
fn $name() {
|
||||
let db: Database<Data, _, $enc> = $db;
|
||||
test_basic_save_load(&db);
|
||||
test_put_data(&db);
|
||||
test_multi_borrow(&db);
|
||||
test_convert_data(db);
|
||||
}
|
||||
};
|
||||
($name:ident, $db:expr, $enc:ty, miri=true) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let db: Database<Data, _, $enc> = $db;
|
||||
test_basic_save_load(&db);
|
||||
test_put_data(&db);
|
||||
test_multi_borrow(&db);
|
||||
test_convert_data(db);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test_basic_save_load!(file_ron, create_filedb(), Ron);
|
||||
test_basic_save_load!(file_yaml, create_filedb(), Yaml);
|
||||
test_basic_save_load!(file_bincode, create_filedb(), Bincode);
|
||||
|
||||
test_basic_save_load!(filepath_ron, create_filedb_from_path(), Ron);
|
||||
test_basic_save_load!(filepath_yaml, create_filedb_from_path(), Yaml);
|
||||
test_basic_save_load!(filepath_bincode, create_filedb_from_path(), Bincode);
|
||||
|
||||
test_basic_save_load!(mem_ron, create_memdb(), Ron, miri = true);
|
||||
test_basic_save_load!(mem_yaml, create_memdb(), Yaml, miri = true);
|
||||
test_basic_save_load!(mem_bincode, create_memdb(), Bincode, miri = true);
|
||||
|
||||
test_basic_save_load!(mmap_ron, create_mmapdb(), Ron);
|
||||
test_basic_save_load!(mmap_yaml, create_mmapdb(), Yaml);
|
||||
test_basic_save_load!(mmap_bincode, create_mmapdb(), Bincode);
|
||||
|
||||
test_basic_save_load!(mmapsize_ron, create_mmapdb_with_size(10), Ron);
|
||||
test_basic_save_load!(mmapsize_yaml, create_mmapdb_with_size(10), Yaml);
|
||||
test_basic_save_load!(mmapsize_bincode, create_mmapdb_with_size(10), Bincode);
|
||||
|
||||
test_basic_save_load!(path_ron, create_pathdb(), Ron);
|
||||
test_basic_save_load!(path_yaml, create_pathdb(), Yaml);
|
||||
test_basic_save_load!(path_bincode, create_pathdb(), Bincode);
|
||||
Reference in New Issue
Block a user