2020-10-24 13:08:23 +00:00
|
|
|
use crate::db::errors::DataError;
|
|
|
|
use crate::db::schema::convert_i32;
|
|
|
|
use byteorder::LittleEndian;
|
2020-10-24 13:46:06 +00:00
|
|
|
use sled::transaction::{abort, TransactionalTree};
|
2020-11-03 20:14:15 +00:00
|
|
|
use sled::Transactional;
|
2020-10-24 13:08:23 +00:00
|
|
|
use sled::Tree;
|
|
|
|
use std::collections::HashMap;
|
2020-10-24 13:46:06 +00:00
|
|
|
use std::convert::From;
|
2020-10-24 13:08:23 +00:00
|
|
|
use std::str;
|
|
|
|
use zerocopy::byteorder::I32;
|
|
|
|
use zerocopy::AsBytes;
|
|
|
|
|
2020-10-24 13:46:06 +00:00
|
|
|
pub(super) mod migrations;
|
|
|
|
|
2020-10-24 13:08:23 +00:00
|
|
|
#[derive(Clone)]
|
2020-11-03 20:14:15 +00:00
|
|
|
pub struct Variables {
|
|
|
|
//room id + username + variable = i32
|
|
|
|
pub(in crate::db) room_user_variables: Tree,
|
2020-10-24 13:46:06 +00:00
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
//room id + username = i32
|
|
|
|
pub(in crate::db) room_user_variable_count: Tree,
|
2020-10-24 13:46:06 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
/// Request soemthing by a username and room ID.
|
|
|
|
pub struct UserAndRoom<'a>(pub &'a str, pub &'a str);
|
2020-10-24 13:08:23 +00:00
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
fn to_vec(value: &UserAndRoom<'_>) -> Vec<u8> {
|
|
|
|
let mut bytes = vec![];
|
|
|
|
bytes.extend_from_slice(value.0.as_bytes());
|
|
|
|
bytes.push(0xfe);
|
|
|
|
bytes.extend_from_slice(value.1.as_bytes());
|
|
|
|
bytes
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
impl From<UserAndRoom<'_>> for Vec<u8> {
|
|
|
|
fn from(value: UserAndRoom) -> Vec<u8> {
|
|
|
|
to_vec(&value)
|
|
|
|
}
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
impl From<&UserAndRoom<'_>> for Vec<u8> {
|
|
|
|
fn from(value: &UserAndRoom) -> Vec<u8> {
|
|
|
|
to_vec(value)
|
2020-10-24 13:46:06 +00:00
|
|
|
}
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Use a transaction to atomically alter the count of variables in
|
|
|
|
/// the database by the given amount. Count cannot go below 0.
|
|
|
|
fn alter_room_variable_count(
|
2020-11-03 20:14:15 +00:00
|
|
|
room_variable_count: &TransactionalTree,
|
|
|
|
user_and_room: &UserAndRoom<'_>,
|
2020-10-24 13:08:23 +00:00
|
|
|
amount: i32,
|
|
|
|
) -> Result<i32, DataError> {
|
2020-11-03 20:14:15 +00:00
|
|
|
let key: Vec<u8> = user_and_room.into();
|
2020-10-24 13:46:06 +00:00
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
let mut new_count = match room_variable_count.get(&key)? {
|
2020-10-24 13:08:23 +00:00
|
|
|
Some(bytes) => convert_i32(&bytes)? + amount,
|
|
|
|
None => amount,
|
|
|
|
};
|
|
|
|
|
|
|
|
if new_count < 0 {
|
|
|
|
new_count = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let db_value: I32<LittleEndian> = I32::new(new_count);
|
2020-11-03 20:14:15 +00:00
|
|
|
room_variable_count.insert(key, db_value.as_bytes())?;
|
2020-10-24 13:08:23 +00:00
|
|
|
Ok(new_count)
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Variables {
|
2020-11-03 20:14:15 +00:00
|
|
|
pub(in crate::db) fn new(db: &sled::Db) -> Result<Variables, sled::Error> {
|
|
|
|
Ok(Variables {
|
|
|
|
room_user_variables: db.open_tree("variables")?,
|
|
|
|
room_user_variable_count: db.open_tree("room_user_variable_count")?,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
pub fn get_user_variables(
|
2020-10-24 13:08:23 +00:00
|
|
|
&self,
|
2020-11-03 20:14:15 +00:00
|
|
|
key: &UserAndRoom<'_>,
|
2020-10-24 13:08:23 +00:00
|
|
|
) -> Result<HashMap<String, i32>, DataError> {
|
2020-11-03 20:14:15 +00:00
|
|
|
let mut prefix: Vec<u8> = key.into();
|
|
|
|
prefix.push(0xff);
|
2020-10-24 13:08:23 +00:00
|
|
|
let prefix_len: usize = prefix.len();
|
|
|
|
|
|
|
|
let variables: Result<Vec<_>, DataError> = self
|
2020-11-03 20:14:15 +00:00
|
|
|
.room_user_variables
|
2020-10-24 13:08:23 +00:00
|
|
|
.scan_prefix(prefix)
|
|
|
|
.map(|entry| match entry {
|
|
|
|
Ok((key, raw_value)) => {
|
|
|
|
//Strips room and username from key, leaving behind name.
|
|
|
|
let variable_name = str::from_utf8(&key[prefix_len..])?;
|
|
|
|
Ok((variable_name.to_owned(), convert_i32(&raw_value)?))
|
|
|
|
}
|
|
|
|
Err(e) => Err(e.into()),
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
//Convert I32 to hash map. collect() inferred via return type.
|
2020-10-24 13:08:23 +00:00
|
|
|
variables.map(|entries| entries.into_iter().collect())
|
|
|
|
}
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
pub fn get_variable_count(&self, user_and_room: &UserAndRoom<'_>) -> Result<i32, DataError> {
|
|
|
|
let key: Vec<u8> = user_and_room.into();
|
2020-10-24 13:46:06 +00:00
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
match self.room_user_variable_count.get(&key)? {
|
|
|
|
Some(raw_value) => convert_i32(&raw_value),
|
|
|
|
None => Ok(0),
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
pub fn get_user_variable(
|
2020-10-24 13:08:23 +00:00
|
|
|
&self,
|
2020-11-03 20:14:15 +00:00
|
|
|
user_and_room: &UserAndRoom<'_>,
|
2020-10-24 13:08:23 +00:00
|
|
|
variable_name: &str,
|
|
|
|
) -> Result<i32, DataError> {
|
2020-11-03 20:14:15 +00:00
|
|
|
let mut key: Vec<u8> = user_and_room.into();
|
|
|
|
key.push(0xff);
|
|
|
|
key.extend_from_slice(variable_name.as_bytes());
|
2020-10-24 13:08:23 +00:00
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
match self.room_user_variables.get(&key)? {
|
|
|
|
Some(raw_value) => convert_i32(&raw_value),
|
|
|
|
_ => Err(DataError::KeyDoesNotExist(variable_name.to_owned())),
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
pub fn set_user_variable(
|
2020-10-24 13:08:23 +00:00
|
|
|
&self,
|
2020-11-03 20:14:15 +00:00
|
|
|
user_and_room: &UserAndRoom<'_>,
|
2020-10-24 13:08:23 +00:00
|
|
|
variable_name: &str,
|
|
|
|
value: i32,
|
|
|
|
) -> Result<(), DataError> {
|
2020-11-03 20:14:15 +00:00
|
|
|
if self.get_variable_count(user_and_room)? >= 100 {
|
|
|
|
return Err(DataError::TooManyEntries);
|
|
|
|
}
|
|
|
|
|
|
|
|
(&self.room_user_variables, &self.room_user_variable_count).transaction(
|
|
|
|
|(tx_vars, tx_counts)| {
|
|
|
|
let mut key: Vec<u8> = user_and_room.into();
|
|
|
|
key.push(0xff);
|
|
|
|
key.extend_from_slice(variable_name.as_bytes());
|
|
|
|
|
2020-10-24 13:08:23 +00:00
|
|
|
let db_value: I32<LittleEndian> = I32::new(value);
|
2020-11-03 20:14:15 +00:00
|
|
|
let old_value = tx_vars.insert(key, db_value.as_bytes())?;
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
//Only increment variable count on new keys.
|
|
|
|
if let None = old_value {
|
2020-11-03 20:14:15 +00:00
|
|
|
if let Err(e) = alter_room_variable_count(&tx_counts, &user_and_room, 1) {
|
|
|
|
return abort(e);
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-03 20:14:15 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
pub fn delete_user_variable(
|
2020-10-24 13:08:23 +00:00
|
|
|
&self,
|
2020-11-03 20:14:15 +00:00
|
|
|
user_and_room: &UserAndRoom<'_>,
|
2020-10-24 13:08:23 +00:00
|
|
|
variable_name: &str,
|
|
|
|
) -> Result<(), DataError> {
|
2020-11-03 20:14:15 +00:00
|
|
|
(&self.room_user_variables, &self.room_user_variable_count).transaction(
|
|
|
|
|(tx_vars, tx_counts)| {
|
|
|
|
let mut key: Vec<u8> = user_and_room.into();
|
|
|
|
key.push(0xff);
|
|
|
|
key.extend_from_slice(variable_name.as_bytes());
|
2020-10-24 13:46:06 +00:00
|
|
|
|
|
|
|
//TODO why does tx.remove require moving the key?
|
2020-11-03 20:14:15 +00:00
|
|
|
if let Some(_) = tx_vars.remove(key.clone())? {
|
|
|
|
if let Err(e) = alter_room_variable_count(&tx_counts, user_and_room, -1) {
|
|
|
|
return abort(e);
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-11-03 20:14:15 +00:00
|
|
|
return abort(DataError::KeyDoesNotExist(variable_name.to_owned()));
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
2020-11-03 20:14:15 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
},
|
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok(())
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-11-03 20:14:15 +00:00
|
|
|
use sled::Config;
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
fn create_test_instance() -> Variables {
|
2020-11-03 20:14:15 +00:00
|
|
|
let config = Config::new().temporary(true);
|
|
|
|
let db = config.open().unwrap();
|
|
|
|
Variables::new(&db).unwrap()
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
//Room Variable count tests
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn alter_room_variable_count_test() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
let alter_count = |amount: i32| {
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.room_user_variable_count
|
|
|
|
.transaction(|tx| match alter_room_variable_count(&tx, &key, amount) {
|
|
|
|
Err(e) => abort(e),
|
|
|
|
_ => Ok(()),
|
2020-10-24 13:08:23 +00:00
|
|
|
})
|
|
|
|
.expect("got transaction failure");
|
|
|
|
};
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
let get_count = |variables: &Variables| -> i32 {
|
2020-10-24 13:08:23 +00:00
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.get_variable_count(&key)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not get variable count")
|
2020-11-03 20:14:15 +00:00
|
|
|
};
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
//addition
|
|
|
|
alter_count(5);
|
2020-10-24 13:28:19 +00:00
|
|
|
assert_eq!(5, get_count(&variables));
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
//subtraction
|
|
|
|
alter_count(-3);
|
2020-10-24 13:28:19 +00:00
|
|
|
assert_eq!(2, get_count(&variables));
|
2020-10-24 13:08:23 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn alter_room_variable_count_cannot_go_below_0_test() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.room_user_variable_count
|
|
|
|
.transaction(|tx| match alter_room_variable_count(&tx, &key, -1000) {
|
|
|
|
Err(e) => abort(e),
|
|
|
|
_ => Ok(()),
|
|
|
|
})
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("got transaction failure");
|
|
|
|
|
|
|
|
let count = variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.get_variable_count(&key)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not get variable count");
|
|
|
|
|
|
|
|
assert_eq!(0, count);
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn empty_db_reports_0_room_variable_count_test() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
let count = variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.get_variable_count(&key)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not get variable count");
|
|
|
|
|
|
|
|
assert_eq!(0, count);
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn set_user_variable_increments_count() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.set_user_variable(&key, "myvariable", 5)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not insert variable");
|
|
|
|
|
|
|
|
let count = variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.get_variable_count(&key)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not get variable count");
|
|
|
|
|
|
|
|
assert_eq!(1, count);
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn update_user_variable_does_not_increment_count() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.set_user_variable(&key, "myvariable", 5)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not insert variable");
|
|
|
|
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.set_user_variable(&key, "myvariable", 10)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not update variable");
|
|
|
|
|
|
|
|
let count = variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.get_variable_count(&key)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not get variable count");
|
|
|
|
|
|
|
|
assert_eq!(1, count);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set/get/delete variable tests
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn set_and_get_variable_test() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
|
|
|
|
2020-10-24 13:08:23 +00:00
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.set_user_variable(&key, "myvariable", 5)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not insert variable");
|
|
|
|
|
|
|
|
let value = variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.get_user_variable(&key, "myvariable")
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not get value");
|
|
|
|
|
|
|
|
assert_eq!(5, value);
|
|
|
|
}
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
#[test]
|
|
|
|
fn cannot_set_more_than_100_variables_per_room() {
|
|
|
|
let variables = create_test_instance();
|
|
|
|
let key = UserAndRoom("username", "room");
|
|
|
|
|
|
|
|
for c in 0..100 {
|
|
|
|
variables
|
|
|
|
.set_user_variable(&key, &format!("myvariable{}", c), 5)
|
|
|
|
.expect("could not insert variable");
|
|
|
|
}
|
|
|
|
|
|
|
|
let result = variables.set_user_variable(&key, "myvariable101", 5);
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(matches!(result, Err(DataError::TooManyEntries)));
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn delete_variable_test() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.set_user_variable(&key, "myvariable", 5)
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not insert variable");
|
|
|
|
|
|
|
|
variables
|
2020-11-03 20:14:15 +00:00
|
|
|
.delete_user_variable(&key, "myvariable")
|
2020-10-24 13:08:23 +00:00
|
|
|
.expect("could not delete value");
|
|
|
|
|
2020-11-03 20:14:15 +00:00
|
|
|
let result = variables.get_user_variable(&key, "myvariable");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(matches!(result, Err(DataError::KeyDoesNotExist(_))));
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn get_missing_variable_returns_key_does_not_exist() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
|
|
|
let result = variables.get_user_variable(&key, "myvariable");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(matches!(result, Err(DataError::KeyDoesNotExist(_))));
|
|
|
|
}
|
|
|
|
|
2020-10-24 13:28:19 +00:00
|
|
|
#[test]
|
|
|
|
fn remove_missing_variable_returns_key_does_not_exist() {
|
2020-10-24 13:08:23 +00:00
|
|
|
let variables = create_test_instance();
|
2020-11-03 20:14:15 +00:00
|
|
|
let key = UserAndRoom("username", "room");
|
|
|
|
let result = variables.delete_user_variable(&key, "myvariable");
|
2020-10-24 13:08:23 +00:00
|
|
|
|
|
|
|
assert!(result.is_err());
|
|
|
|
assert!(matches!(result, Err(DataError::KeyDoesNotExist(_))));
|
|
|
|
}
|
|
|
|
}
|