mdl-1.0.4/.gitignore010064400017500001750000000001361332741060200124770ustar0000000000000000target Cargo.lock examples/gtkapp/target examples/gtkapp/Cargo.lock examples/gtkapp/todo.lmdb mdl-1.0.4/Cargo.toml.orig010064400017500001750000000010701335320576700134110ustar0000000000000000[package] name = "mdl" version = "1.0.4" authors = ["Daniel García Moreno "] description = "Data model library to share app state between threads and process and persist the data in the filesystem. Implements a simple way to store structs instances in a LMDB database and other methods like BTreeMap" keywords = ["lmdb", "model", "data", "state"] license = "GPL-3.0" repository = "https://gitlab.gnome.org/danigm/mdl" readme = "README.md" [dependencies] lmdb = "0.8.0" failure = "0.1.2" bincode = "1.0.1" serde_derive = "1.0.79" serde = "1.0.79" mdl-1.0.4/Cargo.toml0000644000000022520000000000000076500ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "mdl" version = "1.0.4" authors = ["Daniel García Moreno "] description = "Data model library to share app state between threads and process and persist the data in the filesystem. Implements a simple way to store structs instances in a LMDB database and other methods like BTreeMap" readme = "README.md" keywords = ["lmdb", "model", "data", "state"] license = "GPL-3.0" repository = "https://gitlab.gnome.org/danigm/mdl" [dependencies.bincode] version = "1.0.1" [dependencies.failure] version = "0.1.2" [dependencies.lmdb] version = "0.8.0" [dependencies.serde] version = "1.0.79" [dependencies.serde_derive] version = "1.0.79" mdl-1.0.4/README.md010064400017500001750000000113701332741173500120000ustar0000000000000000App state library with cache This crate provides functionality to store data and persists to filesystem automatically. The main goal is to have a single object to query for app state and to be able to modify this state. It also provides a simple signaler to be able to subscribe to update /delete signals and perform custom operations on cache model modification. To store the information we use a key-value storage so each model should provide a unique key that identify it. Use NoSQL schema techniques to add relations between models using the key and query easily. The basic `Cache` object uses LMDB as storage so you can access to the same cache from different threads or process. # Basic Usage The simpler way to use is implementing the `Model` trait for your struct, so you can `get`, `store` and `delete`. ```rust extern crate mdl; #[macro_use] extern crate serde_derive; use mdl::Cache; use mdl::Model; use mdl::Continue; #[derive(Serialize, Deserialize, Debug)] struct A { pub p1: String, pub p2: u32, } impl Model for A { fn key(&self) -> String { format!("{}:{}", self.p1, self.p2) } } fn main() { // initializing the cache. This str will be the fs persistence path let db = "/tmp/mydb.lmdb"; let cache = Cache::new(db).unwrap(); // create a new *object* and storing in the cache let a = A{ p1: "hello".to_string(), p2: 42 }; let r = a.store(&cache); assert!(r.is_ok()); // querying the cache by key and getting a new *instance* let a1: A = A::get(&cache, "hello:42").unwrap(); assert_eq!(a1.p1, a.p1); assert_eq!(a1.p2, a.p2); } ``` # Signals To allow easy notifications of changes in the cache, this crate provides a signal system and the `Model` trait provides `store_sig` and `delete_sig` that store or delete and then emit the corresponding signal. There's two signalers implemented, one that can be `Send` between threads and another one that should be in the same thread all the time this allow us to register callbacks for signals and that callbacks should implement `Send` for the `SignalerAsync`. ## Example ```rust extern crate mdl; #[macro_use] extern crate serde_derive; use mdl::SigType; use mdl::SignalerAsync; use mdl::Cache; use mdl::Model; use std::sync::{Arc, Mutex}; use std::{thread, time}; #[derive(Serialize, Deserialize, Debug)] struct B { pub id: u32, pub complex: Vec, } impl Model for B { fn key(&self) -> String { format!("b:{}", self.id) } } fn main() { let db = "/tmp/test.lmdb"; let cache = Cache::new(db).unwrap(); // using the async signaler that run in other thread let sig = SignalerAsync::new(); // starting the signaler loop, this can be stoped // calling sig.stop() or when the signaler drops sig.signal_loop(); let up_c = Arc::new(Mutex::new(0)); let rm_c = Arc::new(Mutex::new(0)); let counter = Arc::new(Mutex::new(0)); let c1 = up_c.clone(); let c2 = rm_c.clone(); let c3 = counter.clone(); // Subscribing to the "b" signal, that's emited always // that an object which key starting with "b" is modified. // We're using the SignalerAsync so this callback will // be called in a different thread, for that reason we're // pasing Arc> to be able to modify the counters let _id = sig.subscribe("b", Box::new(move |sig| { match sig.type_ { SigType::Update => *c1.lock().unwrap() += 1, SigType::Delete => *c2.lock().unwrap() += 1, }; *c3.lock().unwrap() += 1; })); let b = B{ id: 1, complex: vec![] }; // we use the store_sig instead the store to emit the // corresponding signal, if we use the store, the callback // wont be called. let r = b.store_sig(&cache, &sig); assert!(r.is_ok()); let b = B{ id: 2, complex: vec![] }; let r = b.store_sig(&cache, &sig); assert!(r.is_ok()); let r = b.delete_sig(&cache, &sig); assert!(r.is_ok()); // waiting for signal to come let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); assert_eq!(*up_c.lock().unwrap(), 2); assert_eq!(*rm_c.lock().unwrap(), 1); assert_eq!(*counter.lock().unwrap(), 3); } ``` You can use the `Signaler` without a `Model`, it's possible to emit custom signals and subscribe to that signals, for example: ```rust extern crate mdl; use mdl::SigType; use mdl::Signaler; use mdl::SignalerAsync; use std::{thread, time}; fn main() { let sig = SignalerAsync::new(); sig.signal_loop(); let _id = sig.subscribe("my signal", Box::new(move |sig| { println!("my signal is called"); })); let _ = sig.emit(SigType::Update, "my signal"); // waiting for signal to come let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); } ``` mdl-1.0.4/dumpdb.py010075500017500001750000000004141331240303200123270ustar0000000000000000#!/usr/bin/env python import lmdb import sys path = sys.argv[1] db = sys.argv[2] env = lmdb.open(path, max_dbs=200) db = env.open_db(db.encode()) with env.begin() as txn: cursor = txn.cursor(db) cursor.first() for k, v in cursor: print((k, v)) mdl-1.0.4/src/bcache.rs010064400017500001750000000050251332733407400130630ustar0000000000000000use failure::Error; use failure::err_msg; use std::collections::BTreeMap; use std::ops::Bound::{Included, Unbounded}; use std::sync::{Arc, RwLock}; use store::Store; use store::Continue; /// BTreeMap cache. This struct implements the Store trait so it can be used /// to cache Model structs /// A BTreeMap is used to store the data in memory. This struct implements clone /// so it can be shared between threads safely creating a clone #[derive(Clone)] pub struct Cache { db: Arc> >>, } impl Cache { pub fn new() -> Result { Ok(Cache { db: Arc::new(RwLock::new(BTreeMap::new())), }) } } impl Store for Cache { fn push(&self, db: &'static str, key: &str, value: Vec) -> Result<(), Error> { let newk = format!("{}:{}", db, key); match self.db.write() { Ok(ref mut map) => { map.insert(newk, value); Ok(()) }, Err(_err) => Err(err_msg("DB ERROR")), } } fn pull(&self, db: &'static str, key: &str, formatter: F) -> Result where F: Fn(&[u8]) -> Result { let newk = format!("{}:{}", db, key); match self.db.read() { Ok(map) => { let rdata = map.get(&newk).ok_or(err_msg(format!("Not found, pull {}", newk)))?; formatter(rdata) }, Err(_err) => Err(err_msg("DB ERROR")), } } fn iter(&self, db: &'static str, prefix: &str, f: F) -> Result<(), Error> where F: Fn(&[u8]) -> Continue { let newk = format!("{}:{}", db, prefix); let l = newk.len(); match self.db.read() { Ok(map) => { let range = map.range::((Included(&newk), Unbounded)) .filter(|(k, _v)| { k.len() >= l && &k[..l] == &newk }); for (_, v) in range { if let Continue(false) = f(v) { break; } }; Ok(()) }, Err(_err) => Err(err_msg("DB ERROR")), } } fn rm(&self, db: &'static str, key: &str) -> Result<(), Error> { let newk = format!("{}:{}", db, key); match self.db.write() { Ok(ref mut map) => { map.remove(&newk).ok_or(err_msg(format!("Not found, rm {}", newk)))?; Ok(()) }, Err(_err) => Err(err_msg("DB ERROR")), } } } mdl-1.0.4/src/cache.rs010064400017500001750000000076671332733407400127370ustar0000000000000000use failure::Error; use failure::err_msg; use lmdb::Transaction; use lmdb::Cursor; use lmdb::Environment; use lmdb::Database; use lmdb::DatabaseFlags; use lmdb::WriteFlags; use lmdb::RwCursor; use lmdb::RoCursor; use std::path::Path; use std::fs::create_dir_all; use std::collections::HashMap; use std::cell::RefCell; use store::Store; use store::Continue; /// LMDB cache. This struct implements the Store trait so it can be used /// to cache Model structs pub struct Cache { /// LMDB environment pub env: Environment, /// database path in the filesystem pub path: String, /// List of LMDB databases dbs: RefCell>, } impl Cache { pub fn new(path: &str) -> Result { let envpath = Path::new(path); if !envpath.exists() { let _ = create_dir_all(&envpath); } let env = Environment::new() .set_max_dbs(1024) .set_map_size(256 * 1024 * 1024) /* 256 MB */ .open(envpath)?; Ok(Cache { env: env, path: path.to_string(), dbs: RefCell::new(HashMap::new()), }) } pub fn db(&self, name: &'static str) -> Result { // if the db is created, we return the db stored in cache { let dbs = self.dbs.borrow(); if dbs.contains_key(name) { return Ok(dbs[name].clone()); } } // if the db doesn't exists, we create that db and store for the future let db = self.env .create_db(Some(name), DatabaseFlags::default()) .or(Err(err_msg(format!("error opening the db {}", name))))?; self.dbs.borrow_mut().insert(name, db.clone()); Ok(db) } pub fn rw(&self, db: &'static str, op: F) -> Result where F: Fn(RwCursor) -> Result { let db = self.db(db)?; let mut txn = self.env.begin_rw_txn()?; let output; { let cursor = txn.open_rw_cursor(db)?; output = op(cursor); } txn.commit()?; output } pub fn ro(&self, db: &'static str, op: F) -> Result where F: Fn(RoCursor) -> Result { let db = self.db(db)?; let txn = self.env.begin_ro_txn()?; let output; { let cursor = txn.open_ro_cursor(db)?; output = op(cursor); } txn.commit()?; output } } impl Store for Cache { fn push(&self, db: &'static str, key: &str, value: Vec) -> Result<(), Error> { self.rw(db, move |mut cursor| { cursor.put(&key.as_bytes(), &value, WriteFlags::empty())?; Ok(()) }) } fn pull(&self, db: &'static str, key: &str, formatter: F) -> Result where F: Fn(&[u8]) -> Result { self.ro(db, move |cursor| { let k = Some(key.as_ref()); let (_rkey, rdata) = cursor.get(k, None, 15)?; formatter(rdata) }) } fn iter(&self, db: &'static str, prefix: &str, f: F) -> Result<(), Error> where F: Fn(&[u8]) -> Continue { let l = prefix.len(); self.ro(db, move |mut cursor| { let k = Some(prefix.as_ref()); cursor.get(k, None, 17)?; let iter = cursor.iter_from(k.unwrap()) .filter(|(k, _v)| { k.len() >= l && &k[0..l] == prefix[0..l].as_bytes() }); for (_, v) in iter { if let Continue(false) = f(v) { break; } }; Ok(()) })?; Ok(()) } fn rm(&self, db: &'static str, key: &str) -> Result<(), Error> { self.rw(db, move |mut cursor| { cursor.get(Some(key.as_ref()), None, 15)?; cursor.del(WriteFlags::empty())?; Ok(()) }) } } mdl-1.0.4/src/lib.rs010064400017500001750000000135041332740621100124160ustar0000000000000000//! App state library with cache //! //! This crate provides functionality to store data and persists to filesystem //! automatically. The main goal is to have a single object to query for app //! state and to be able to modify this state. //! //! It also provides a simple signaler to be able to subscribe to update /delete //! signals and perform custom operations on cache model modification. //! //! To store the information we use a key-value storage so each model should //! provide a unique key that identify it. Use NoSQL schema techniques to add //! relations between models using the key and query easily. //! //! The basic `Cache` object uses LMDB as storage so you can access to the same //! cache from different threads or process. //! //! # Basic Usage //! //! The simpler way to use is implementing the `Model` trait for your struct, //! so you can `get`, `store` and `delete`. //! //! ```ignore //! extern crate mdl; //! #[macro_use] //! extern crate serde_derive; //! //! use mdl::Cache; //! use mdl::Model; //! use mdl::Continue; //! //! #[derive(Serialize, Deserialize, Debug)] //! struct A { //! pub p1: String, //! pub p2: u32, //! } //! impl Model for A { //! fn key(&self) -> String { //! format!("{}:{}", self.p1, self.p2) //! } //! } //! //! fn main() { //! // initializing the cache. This str will be the fs persistence path //! let db = "/tmp/mydb.lmdb"; //! let cache = Cache::new(db).unwrap(); //! //! // create a new *object* and storing in the cache //! let a = A{ p1: "hello".to_string(), p2: 42 }; //! let r = a.store(&cache); //! assert!(r.is_ok()); //! //! // querying the cache by key and getting a new *instance* //! let a1: A = A::get(&cache, "hello:42").unwrap(); //! assert_eq!(a1.p1, a.p1); //! assert_eq!(a1.p2, a.p2); //! } //! ``` //! //! # Signals //! //! To allow easy notifications of changes in the cache, this crate //! provides a signal system and the `Model` trait provides `store_sig` //! and `delete_sig` that store or delete and then emit the corresponding //! signal. //! //! There's two signalers implemented, one that can be `Send` between //! threads and another one that should be in the same thread all the time //! this allow us to register callbacks for signals and that callbacks //! should implement `Send` for the `SignalerAsync`. //! //! ## Example //! //! ```ignore //! extern crate mdl; //! #[macro_use] //! extern crate serde_derive; //! //! use mdl::SigType; //! use mdl::SignalerAsync; //! use mdl::Cache; //! use mdl::Model; //! //! use std::sync::{Arc, Mutex}; //! use std::{thread, time}; //! //! #[derive(Serialize, Deserialize, Debug)] //! struct B { //! pub id: u32, //! pub complex: Vec, //! } //! impl Model for B { //! fn key(&self) -> String { //! format!("b:{}", self.id) //! } //! } //! //! fn main() { //! let db = "/tmp/test.lmdb"; //! let cache = Cache::new(db).unwrap(); //! // using the async signaler that run in other thread //! let sig = SignalerAsync::new(); //! // starting the signaler loop, this can be stoped //! // calling sig.stop() or when the signaler drops //! sig.signal_loop(); //! //! let up_c = Arc::new(Mutex::new(0)); //! let rm_c = Arc::new(Mutex::new(0)); //! let counter = Arc::new(Mutex::new(0)); //! //! let c1 = up_c.clone(); //! let c2 = rm_c.clone(); //! let c3 = counter.clone(); //! //! // Subscribing to the "b" signal, that's emited always //! // that an object which key starting with "b" is modified. //! // We're using the SignalerAsync so this callback will //! // be called in a different thread, for that reason we're //! // pasing Arc> to be able to modify the counters //! let _id = sig.subscribe("b", Box::new(move |sig| { //! match sig.type_ { //! SigType::Update => *c1.lock().unwrap() += 1, //! SigType::Delete => *c2.lock().unwrap() += 1, //! }; //! //! *c3.lock().unwrap() += 1; //! })); //! //! let b = B{ id: 1, complex: vec![] }; //! // we use the store_sig instead the store to emit the //! // corresponding signal, if we use the store, the callback //! // wont be called. //! let r = b.store_sig(&cache, &sig); //! assert!(r.is_ok()); //! //! let b = B{ id: 2, complex: vec![] }; //! let r = b.store_sig(&cache, &sig); //! assert!(r.is_ok()); //! //! let r = b.delete_sig(&cache, &sig); //! assert!(r.is_ok()); //! //! // waiting for signal to come //! let ten_millis = time::Duration::from_millis(10); //! thread::sleep(ten_millis); //! //! assert_eq!(*up_c.lock().unwrap(), 2); //! assert_eq!(*rm_c.lock().unwrap(), 1); //! assert_eq!(*counter.lock().unwrap(), 3); //! } //! ``` //! //! You can use the `Signaler` without a `Model`, it's possible to emit custom //! signals and subscribe to that signals, for example: //! //! ```ignore //! extern crate mdl; //! use mdl::SigType; //! use mdl::Signaler; //! use mdl::SignalerAsync; //! use std::{thread, time}; //! //! fn main() { //! let sig = SignalerAsync::new(); //! sig.signal_loop(); //! //! let _id = sig.subscribe("my signal", Box::new(move |sig| { //! println!("my signal is called"); //! })); //! //! let _ = sig.emit(SigType::Update, "my signal"); //! //! // waiting for signal to come //! let ten_millis = time::Duration::from_millis(10); //! thread::sleep(ten_millis); //! } //! ``` extern crate lmdb; extern crate failure; extern crate serde; extern crate bincode; pub mod store; pub mod cache; pub mod bcache; pub mod model; pub mod signal; pub use store::Store; pub use store::Continue; pub use cache::Cache; pub use model::Model; pub use bcache::Cache as BCache; pub use signal::Signaler; pub use signal::SignalerAsync; pub use signal::SignalerSync; pub use signal::Signal; pub use signal::SigType; mdl-1.0.4/src/model.rs010064400017500001750000000047701332733407400127640ustar0000000000000000use serde; use failure::Error; use bincode::{serialize, deserialize}; use store::Store; use store::Continue; use signal::Signaler; use signal::SigType; /// Trait to implement Cacheable data Model pub trait Model: serde::Serialize + serde::de::DeserializeOwned { /// key to identify the data object, this should be unique fn key(&self) -> String; /// database name, where to store instances of this struct fn db() -> &'static str { "default" } /// Data Struct serialization fn tob(&self) -> Result, Error> { let encoded: Vec = serialize(self)?; Ok(encoded) } /// Data Struct deserialization fn fromb(data: &[u8]) -> Result { let decoded: Self = deserialize(data)?; Ok(decoded) } /// Persist the struct in the database fn store(&self, store: &S) -> Result<(), Error> { store.push(Self::db(), &self.key(), self.tob()?) } /// Persist the struct in the database and emit the signal to the signaler fn store_sig(&self, store: &S, sig: &G) -> Result<(), Error> { self.store(store) .and_then(|out| { sig.emit(SigType::Update, &self.key())?; Ok(out) }) } /// Deletes the object from the database fn delete(&self, store: &S) -> Result<(), Error> { store.rm(Self::db(), &self.key()) } /// Deletes the object from the database and emit the signal to the signaler fn delete_sig(&self, store: &S, sig: &G) -> Result<(), Error> { self.delete(store) .and_then(|out| { sig.emit(SigType::Delete, &self.key())?; Ok(out) }) } /// Loads the struct from the database fn get(store: &S, key: &str) -> Result { store.pull(Self::db(), key, Self::fromb) } /// Get all objects with this prefix fn all(store: &S, prefix: &str) -> Result, Error> { store.all(Self::db(), prefix, Self::fromb) } /// Iterate over all objects with this prefix fn iter(store: &S, prefix: &str, f: F) -> Result<(), Error> where S: Store, F: Fn(Self) -> Continue { store.iter(Self::db(), prefix, move |data| { match Self::fromb(data) { Ok(obj) => f(obj), _ => Continue(true) } }) } } mdl-1.0.4/src/signal.rs010064400017500001750000000201101333103057400131140ustar0000000000000000use failure::Error; use std::sync::mpsc::{channel, Sender, Receiver}; use std::sync::{Arc, Mutex}; use std::thread; use std::collections::HashMap; use std::fmt; use std::sync::mpsc::TryRecvError; macro_rules! subscribe { ($self: expr, $CallBack: ident, $signal: expr, $f: expr) => {{ let id = *$self.base.id.lock().unwrap(); *$self.base.id.lock().unwrap() += 1; let c = $CallBack { id: id, callback: $f }; let mut guard = $self.callbacks.lock().unwrap(); if guard.contains_key($signal) { guard.get_mut($signal).map(|v| v.push(c)); } else { guard.insert($signal.to_string(), vec![c]); } Ok(id) }} } macro_rules! unsubscribe { ($self: expr, $CallBack: ident, $id: expr) => {{ let mut guard = $self.callbacks.lock().unwrap(); for (_, ref mut v) in guard.iter_mut() { let idx = v.iter().position(|cb: &$CallBack| cb.id() == $id); if let Some(i) = idx { v.remove(i); break; } } }} } /// signal -> [cb1, cb2, cb3, ...] type CBs = Arc> >>; type CBsSync = Arc> >>; // Custom types #[derive(Clone, Debug)] pub enum SigType { Update, Delete, } #[derive(Clone, Debug)] pub struct Signal { pub type_: SigType, pub name: String, } pub struct CallBack { pub id: u32, pub callback: Box, } pub struct CallBackSync { pub id: u32, pub callback: Box, } #[derive(Clone, Debug)] pub struct SigBase { id: Arc>, recv: Arc>>, main: Option>, } #[derive(Clone, Debug)] pub struct SignalerAsync { base: SigBase, callbacks: CBs, } #[derive(Clone, Debug)] pub struct SignalerSync { base: SigBase, callbacks: CBsSync, } // Traits trait CB { fn id(&self) -> u32; fn call(&self, sig: Signal); } pub trait Signaler { fn base<'a>(&'a self) -> &'a SigBase; /// emit a signal that trigger all callbacks subscribed to this signal fn emit(&self, t: SigType, signal: &str) -> Result<(), Error> { if let Some(ref tx) = self.base().main { let tx = tx.clone(); let n = signal.to_string(); thread::spawn(move || { let _ = tx.send(Signal{ type_: t, name: n }); }); } Ok(()) } } // struct methods impl SigBase { pub fn new() -> SigBase { let (tx, rv) = channel::(); let main = Some(tx); let recv = Arc::new(Mutex::new(rv)); let id = Arc::new(Mutex::new(1)); SigBase{id, recv, main} } } impl SignalerAsync { pub fn new() -> SignalerAsync { let callbacks = Arc::new(Mutex::new(HashMap::new())); let base = SigBase::new(); SignalerAsync { base, callbacks } } pub fn stop(&mut self) { self.base.main = None; } /// subscribe a callback to a signal /// This callback will be called with all signals that starts with the /// `signal` string, for example, if you subscribe a callback to the signal /// "custom-signal", this callback will have the following behaviour: /// /// signal | f is called /// -------------------------------+------------- /// "custom-signal" | true /// "custom-signal2" | true /// "custom-signal-with more text" | true /// "custom" | false /// "custom-signa" | false /// "other signal" | false /// /// This method returns the callback id that can be used to unsubscribe pub fn subscribe(&self, signal: &str, f: Box) -> Result { subscribe!(self, CallBack, signal, f) } /// Unsubscribe a callback by id. Use the id returned in the subscribe /// method to remove this signal pub fn unsubscribe(&self, id: u32) { unsubscribe!(self, CallBack, id); } pub fn clear_signal(&self, signal: &str) { let mut guard = self.callbacks.lock().unwrap(); guard.remove(signal); } pub fn signal_loop(&self) { let cbs = self.callbacks.clone(); let recv = self.base.recv.clone(); thread::spawn(move || { event_loop(&*recv.lock().unwrap(), cbs); }); } } impl SignalerSync { pub fn new() -> SignalerSync { let callbacks = Arc::new(Mutex::new(HashMap::new())); let base = SigBase::new(); SignalerSync { base, callbacks } } pub fn stop(&mut self) { self.base.main = None; } /// subscribe a callback to a signal /// This callback will be called with all signals that starts with the /// `signal` string, for example, if you subscribe a callback to the signal /// "custom-signal", this callback will have the following behaviour: /// /// signal | f is called /// -------------------------------+------------- /// "custom-signal" | true /// "custom-signal2" | true /// "custom-signal-with more text" | true /// "custom" | false /// "custom-signa" | false /// "other signal" | false /// /// This method returns the callback id that can be used to unsubscribe pub fn subscribe(&self, signal: &str, f: Box) -> Result { subscribe!(self, CallBackSync, signal, f) } /// Unsubscribe a callback by id. Use the id returned in the subscribe /// method to remove this signal pub fn unsubscribe(&self, id: u32) { unsubscribe!(self, CallBackSync, id); } pub fn clear_signal(&self, signal: &str) { let mut guard = self.callbacks.lock().unwrap(); guard.remove(signal); } pub fn signal_loop_sync(&self) -> bool { let recv = self.base.recv.lock().unwrap(); match recv.try_recv() { Ok(ref signal) => { let mut cbs = self.callbacks.lock().unwrap(); signal_recv(signal, &mut *cbs); true } Err(TryRecvError::Empty) => { true } Err(_) => { let mut cbs = self.callbacks.lock().unwrap(); clear(&mut *cbs); false } } } } // Trait implementation impl fmt::Debug for CallBack { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "callback: {}", self.id) } } impl fmt::Debug for CallBackSync { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "callback-sync: {}", self.id) } } impl CB for CallBack { fn id(&self) -> u32 { self.id } fn call(&self, sig: Signal) { (*self.callback)(sig); } } impl CB for CallBackSync { fn id(&self) -> u32 { self.id } fn call(&self, sig: Signal) { (*self.callback)(sig); } } impl Signaler for SignalerAsync { fn base<'a>(&'a self) -> &'a SigBase { &self.base } } impl Signaler for SignalerSync { fn base<'a>(&'a self) -> &'a SigBase { &self.base } } // static functions fn event_loop(receiver: &Receiver, cbs: Arc> >>) { loop { match receiver.recv() { Ok(ref signal) => { let mut cbs = cbs.lock().unwrap(); signal_recv(signal, &mut *cbs); } Err(_) => { let mut cbs = cbs.lock().unwrap(); clear(&mut *cbs); break; } }; } } fn signal_recv(signal: &Signal, cbs: &mut HashMap>) { for (ref k, ref v) in cbs.iter() { if !&signal.name[..].starts_with(&k[..]) { continue; } for c in v.iter() { c.call(signal.clone()); } } } fn clear(cbs: &mut HashMap>) { for (_, v) in cbs.iter_mut() { v.clear(); } } mdl-1.0.4/src/store.rs010064400017500001750000000033321332733407400130110ustar0000000000000000use std::cell::RefCell; use std::rc::Rc; use failure::Error; use failure::err_msg; pub struct Continue(pub bool); /// Trait that defines a Store that can be implemented to save Model objects /// in memory, filesystem or the network pub trait Store { /// Stores the value in the database with the corresponding key fn push(&self, db: &'static str, key: &str, value: Vec) -> Result<(), Error>; /// Retrieves the value in the database with the corresponding key /// Returns an error if the key doesn't exists fn pull(&self, db: &'static str, key: &str, formatter: F) -> Result where F: Fn(&[u8]) -> Result; /// Iterates over all objects that starts with the prefix and run /// the function f. If f returns Continue(false) the iteration stops fn iter(&self, db: &'static str, prefix: &str, f: F) -> Result<(), Error> where F: Fn(&[u8]) -> Continue; /// Retrieves all items in the database that starts with the prefix key fn all(&self, db: &'static str, prefix: &str, formatter: F) -> Result, Error> where F: Fn(&[u8]) -> Result { let output: Rc>> = Rc::new(RefCell::new(vec![])); let out = output.clone(); self.iter(db, prefix, move |data| { if let Ok(obj) = formatter(data) { out.borrow_mut().push(obj); } Continue(true) })?; Ok(Rc::try_unwrap(output) .map_err(|_| err_msg(format!("error reading from db")))? .into_inner()) } /// Remove the corresponding data in the database by key fn rm(&self, db: &'static str, key: &str) -> Result<(), Error>; } mdl-1.0.4/tests/bcacheable_test.rs010064400017500001750000000070501332656637100153270ustar0000000000000000extern crate mdl; #[macro_use] extern crate serde_derive; use mdl::BCache as Cache; use mdl::Model; use mdl::Continue; #[derive(Serialize, Deserialize, Debug)] struct A { pub p1: String, pub p2: u32, } impl Model for A { fn key(&self) -> String { format!("{}:{}", self.p1, self.p2) } } #[derive(Serialize, Deserialize, Debug)] struct B { pub id: u32, pub complex: Vec, } impl Model for B { fn key(&self) -> String { format!("b:{}", self.id) } } #[test] fn basic_struct_test() { let cache = Cache::new().unwrap(); let a = A{ p1: "hello".to_string(), p2: 42 }; let r = a.store(&cache); assert!(r.is_ok()); let a1: A = A::get(&cache, "hello:42").unwrap(); assert_eq!(a1.p1, a.p1); assert_eq!(a1.p2, a.p2); } #[test] fn delete_test() { let cache = Cache::new().unwrap(); println!("STORING A"); let a = A{ p1: "hello".to_string(), p2: 42 }; let r = a.store(&cache); assert!(r.is_ok()); println!("STORED A"); let r = A::get(&cache, "hello:42"); assert!(r.is_ok()); let r = a.delete(&cache); assert!(r.is_ok()); let r = A::get(&cache, "hello:42"); assert!(r.is_err()); } #[test] fn iterate_test() { let cache = Cache::new().unwrap(); for i in 1..10 { let a = A{ p1: "hello".to_string(), p2: i }; let r = a.store(&cache); assert!(r.is_ok()); } //inserting other objects in cache for i in 1..10 { let b = B{ id: i, complex: vec![] }; let r = b.store(&cache); assert!(r.is_ok()); } //and now more A objects for i in 10..20 { let a = A{ p1: "hello".to_string(), p2: i }; let r = a.store(&cache); assert!(r.is_ok()); } let r = A::get(&cache, "hello:1"); assert!(r.is_ok()); assert_eq!(r.unwrap().p2, 1); let r = B::get(&cache, "b:1"); assert!(r.is_ok()); assert_eq!(r.unwrap().id, 1); // Iterate over all A elements let mut v = A::all(&cache, "hello").unwrap(); v.sort_by_key(|a| a.p2); for (i, a) in v.iter().enumerate() { assert_eq!(a.p2, (i+1) as u32); } // Iterate over all B elements let mut v = B::all(&cache, "b").unwrap(); v.sort_by_key(|b| b.id); for (i, b) in v.iter().enumerate() { assert_eq!(b.id, (i+1) as u32); } } #[test] fn iterate_write_test() { let cache = Cache::new().unwrap(); //inserting other objects in cache for i in 1..10 { let b = B{ id: i, complex: vec![] }; let r = b.store(&cache); assert!(r.is_ok()); } // Iterate over all B elements let all = B::all(&cache, "b").unwrap(); for mut b in all { b.complex.push("UPDATED".to_string()); b.store(&cache).unwrap(); } // Iterate over all B elements B::iter(&cache, "b", |b| { assert_eq!(b.complex.len(), 1); Continue(true) }).unwrap(); } #[test] fn thread_test() { use std::thread; let cache = Cache::new().unwrap(); let b = B{ id: 1, complex: vec![] }; let _ = b.store(&cache); let cache2 = cache.clone(); let join_handle: thread::JoinHandle<_> = thread::spawn(move || { let mut b = B::get(&cache2, "b:1").unwrap(); assert_eq!(b.complex.len(), 0); b.complex.push("modified".to_string()); let _ = b.store(&cache2); }); // waiting for the thread to finish join_handle.join().unwrap(); let b = B::get(&cache, "b:1").unwrap(); assert_eq!(b.id, 1); assert_eq!(b.complex.len(), 1); assert_eq!(&b.complex[0][..], "modified"); } mdl-1.0.4/tests/cacheable_test.rs010064400017500001750000000077311332656641100151660ustar0000000000000000extern crate mdl; #[macro_use] extern crate serde_derive; use mdl::Cache; use mdl::Model; use mdl::Continue; use std::fs::remove_dir_all; static DB: &'static str = "/tmp/test.lmdb"; #[derive(Serialize, Deserialize, Debug)] struct A { pub p1: String, pub p2: u32, } impl Model for A { fn key(&self) -> String { format!("{}:{}", self.p1, self.p2) } } #[derive(Serialize, Deserialize, Debug)] struct B { pub id: u32, pub complex: Vec, } impl Model for B { fn key(&self) -> String { format!("b:{}", self.id) } } #[test] fn basic_struct_test() { let db = &format!("{}-basic", DB); let cache = Cache::new(db).unwrap(); let a = A{ p1: "hello".to_string(), p2: 42 }; let r = a.store(&cache); assert!(r.is_ok()); let a1: A = A::get(&cache, "hello:42").unwrap(); assert_eq!(a1.p1, a.p1); assert_eq!(a1.p2, a.p2); let _ = remove_dir_all(db); } #[test] fn delete_test() { let db = &format!("{}-delete", DB); let cache = Cache::new(db).unwrap(); let a = A{ p1: "hello".to_string(), p2: 42 }; let r = a.store(&cache); assert!(r.is_ok()); let r = A::get(&cache, "hello:42"); assert!(r.is_ok()); let r = a.delete(&cache); assert!(r.is_ok()); let r = A::get(&cache, "hello:42"); assert!(r.is_err()); let _ = remove_dir_all(db); } #[test] fn iterate_test() { let db = &format!("{}-it", DB); let cache = Cache::new(db).unwrap(); for i in 1..10 { let a = A{ p1: "hello".to_string(), p2: i }; let r = a.store(&cache); assert!(r.is_ok()); } //inserting other objects in cache for i in 1..10 { let b = B{ id: i, complex: vec![] }; let r = b.store(&cache); assert!(r.is_ok()); } //and now more A objects for i in 10..20 { let a = A{ p1: "hello".to_string(), p2: i }; let r = a.store(&cache); assert!(r.is_ok()); } let r = A::get(&cache, "hello:1"); assert!(r.is_ok()); assert_eq!(r.unwrap().p2, 1); let r = B::get(&cache, "b:1"); assert!(r.is_ok()); assert_eq!(r.unwrap().id, 1); // Iterate over all A elements let mut v = A::all(&cache, "hello").unwrap(); v.sort_by_key(|a| a.p2); for (i, a) in v.iter().enumerate() { assert_eq!(a.p2, (i+1) as u32); } // Iterate over all B elements let mut v = B::all(&cache, "b").unwrap(); v.sort_by_key(|b| b.id); for (i, b) in v.iter().enumerate() { assert_eq!(b.id, (i+1) as u32); } let _ = remove_dir_all(db); } #[test] fn iterate_write_test() { let db = &format!("{}-it2", DB); let cache = Cache::new(db).unwrap(); //inserting other objects in cache for i in 1..10 { let b = B{ id: i, complex: vec![] }; let r = b.store(&cache); assert!(r.is_ok()); } // Iterate over all B elements let all = B::all(&cache, "b").unwrap(); for mut b in all { b.complex.push("UPDATED".to_string()); b.store(&cache).unwrap(); } // Iterate over all B elements B::iter(&cache, "b", |b| { assert_eq!(b.complex.len(), 1); Continue(true) }).unwrap(); let _ = remove_dir_all(db); } #[test] fn thread_test() { use std::thread; let db = &format!("{}-thread", DB); let cache = Cache::new(db).unwrap(); let b = B{ id: 1, complex: vec![] }; let _ = b.store(&cache); let join_handle: thread::JoinHandle<_> = thread::spawn(move || { let db = &format!("{}-thread", DB); let cache = Cache::new(db).unwrap(); let mut b = B::get(&cache, "b:1").unwrap(); assert_eq!(b.complex.len(), 0); b.complex.push("modified".to_string()); let _ = b.store(&cache); }); // waiting for the thread to finish join_handle.join().unwrap(); let b = B::get(&cache, "b:1").unwrap(); assert_eq!(b.id, 1); assert_eq!(b.complex.len(), 1); assert_eq!(&b.complex[0][..], "modified"); let _ = remove_dir_all(db); } mdl-1.0.4/tests/model_signals_test.rs010064400017500001750000000031631332733407400161110ustar0000000000000000extern crate mdl; #[macro_use] extern crate serde_derive; use mdl::SigType; use mdl::SignalerAsync; use mdl::Cache; use mdl::Model; use std::fs::remove_dir_all; use std::sync::{Arc, Mutex}; use std::{thread, time}; static DB: &'static str = "/tmp/test.lmdb"; #[derive(Serialize, Deserialize, Debug)] struct B { pub id: u32, pub complex: Vec, } impl Model for B { fn key(&self) -> String { format!("b:{}", self.id) } } #[test] fn basic_signal_test() { let db = &format!("{}-basic", DB); let cache = Cache::new(db).unwrap(); let sig = SignalerAsync::new(); sig.signal_loop(); let up_c = Arc::new(Mutex::new(0)); let rm_c = Arc::new(Mutex::new(0)); let counter = Arc::new(Mutex::new(0)); let c1 = up_c.clone(); let c2 = rm_c.clone(); let c3 = counter.clone(); let _id = sig.subscribe("b", Box::new(move |sig| { match sig.type_ { SigType::Update => *c1.lock().unwrap() += 1, SigType::Delete => *c2.lock().unwrap() += 1, }; *c3.lock().unwrap() += 1; })); let b = B{ id: 1, complex: vec![] }; let r = b.store_sig(&cache, &sig); assert!(r.is_ok()); let b = B{ id: 2, complex: vec![] }; let r = b.store_sig(&cache, &sig); assert!(r.is_ok()); let r = b.delete_sig(&cache, &sig); assert!(r.is_ok()); let _ = remove_dir_all(db); // waiting for signal to come let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); assert_eq!(*up_c.lock().unwrap(), 2); assert_eq!(*rm_c.lock().unwrap(), 1); assert_eq!(*counter.lock().unwrap(), 3); } mdl-1.0.4/tests/signaler_test.rs010064400017500001750000000065641332733407400151050ustar0000000000000000extern crate mdl; use mdl::Signaler; use mdl::SignalerAsync; use mdl::SigType; use std::sync::{Arc, Mutex}; use std::{thread, time}; #[test] fn one_signal_test() { let sig = SignalerAsync::new(); sig.signal_loop(); let counter = Arc::new(Mutex::new(0)); // one thread for receive signals let sig1 = sig.clone(); let c1 = counter.clone(); let t1: thread::JoinHandle<_> = thread::spawn(move || { let _ = sig1.subscribe("signal", Box::new(move |_sig| { *c1.lock().unwrap() += 1; })); }); // waiting for threads to finish t1.join().unwrap(); // one thread for emit signals let sig2 = sig.clone(); let t2: thread::JoinHandle<_> = thread::spawn(move || { sig2.emit(SigType::Update, "signal").unwrap(); sig2.emit(SigType::Update, "signal:2").unwrap(); sig2.emit(SigType::Update, "signal:2:3").unwrap(); }); // waiting for threads to finish t2.join().unwrap(); let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); assert_eq!(*counter.lock().unwrap(), 3); } #[test] fn two_signal_test() { let sig = SignalerAsync::new(); sig.signal_loop(); let counter = Arc::new(Mutex::new(0)); let counter2 = Arc::new(Mutex::new(0)); // one thread for receive signals let sig1 = sig.clone(); let c1 = counter.clone(); let c2 = counter2.clone(); let t1: thread::JoinHandle<_> = thread::spawn(move || { let _ = sig1.subscribe("signal", Box::new(move |_sig| { *c1.lock().unwrap() += 1; })); let _ = sig1.subscribe("others", Box::new(move |_sig| { *c2.lock().unwrap() += 1; })); }); // waiting for threads to finish t1.join().unwrap(); // one thread for emit signals let sig2 = sig.clone(); let t2: thread::JoinHandle<_> = thread::spawn(move || { sig2.emit(SigType::Update, "signal").unwrap(); sig2.emit(SigType::Update, "others:2:3").unwrap(); sig2.emit(SigType::Update, "signal:2").unwrap(); sig2.emit(SigType::Update, "signal:2:3").unwrap(); }); // waiting for threads to finish t2.join().unwrap(); let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); assert_eq!(*counter.lock().unwrap(), 3); assert_eq!(*counter2.lock().unwrap(), 1); } #[test] fn unsubscribe_test() { let sig = SignalerAsync::new(); sig.signal_loop(); let counter = Arc::new(Mutex::new(0)); // one thread for receive signals let sig1 = sig.clone(); let c1 = counter.clone(); let t1: thread::JoinHandle<_> = thread::spawn(move || { let sig2 = sig1.clone(); let _ = sig1.subscribe("unsub", Box::new(move |_sig| { *c1.lock().unwrap() += 1; sig2.unsubscribe(1); })); }); // waiting for threads to finish t1.join().unwrap(); // one thread for emit signals let sig2 = sig.clone(); let t2: thread::JoinHandle<_> = thread::spawn(move || { sig2.emit(SigType::Update, "unsub").unwrap(); sig2.emit(SigType::Update, "unsub:2").unwrap(); sig2.emit(SigType::Update, "unsub:2:3").unwrap(); }); // waiting for threads to finish t2.join().unwrap(); let ten_millis = time::Duration::from_millis(10); thread::sleep(ten_millis); assert_eq!(*counter.lock().unwrap(), 1); }