diff --git a/Cargo.lock b/Cargo.lock index ec3ebc1..b0eb9bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,175 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "avarice" version = "0.1.0" -dependencies = [ - "custom_error 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)", - "simplelog 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", -] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "custom_error" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "itoa" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "libc" -version = "0.2.80" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "log" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde" -version = "1.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "serde_json" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "simplelog" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)", - "wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[metadata] -"checksum autocfg 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" -"checksum chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -"checksum custom_error 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "51ac5e99a7fea3ee8a03fa4721a47e2efd3fbb38358fc61192a54d4c6f866c12" -"checksum itoa 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" -"checksum libc 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" -"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" -"checksum num-integer 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -"checksum num-traits 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -"checksum ryu 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" -"checksum serde 1.0.117 (registry+https://github.com/rust-lang/crates.io-index)" = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" -"checksum serde_json 1.0.59 (registry+https://github.com/rust-lang/crates.io-index)" = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" -"checksum simplelog 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2736f58087298a448859961d3f4a0850b832e72619d75adc69da7993c2cd3c" -"checksum termcolor 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -"checksum time 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -"checksum wasi 0.10.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" -"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index f9c8363..e8facc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,3 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -simplelog = "0.8" -log = "0.4" -serde_json = "1.0" -custom_error = "1.8.0" diff --git a/fixtures/database/administration/registered.json b/fixtures/database/administration/registered.json deleted file mode 100644 index aad23a4..0000000 --- a/fixtures/database/administration/registered.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "76561198025127722": { - "allowed_ips": ["127.0.0.1", "192.168.0.100"], - "groups": ["admin"], - "ip_lock": true, - "password_hash": "fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d" - }, - "76561198044316328": { - "groups": ["admin"], - "ip_lock": false, - "password_hash": "fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d" - } -} diff --git a/fixtures/database/game/general.json b/fixtures/database/game/general.json deleted file mode 100644 index 24cc980..0000000 --- a/fixtures/database/game/general.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "76561198025127722": { - "walked": 1073, - "dosh_thrown": 483482, - "achievements": ["kf:LabCleaner", "kf:ChickenFarmer", "scrn:playedscrn"] - }, - "76561198044316328": { - "walked": 1693, - "dosh_thrown": 527624, - "achievements": ["kf:PubCrawl", "kf:FascistDietitian", "kf:GimliThatAxe!", "scrn:playedscrn"] - } -} diff --git a/fixtures/database/game/perks.json b/fixtures/database/game/perks.json deleted file mode 100644 index a35c51d..0000000 --- a/fixtures/database/game/perks.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "76561198025127722": { - "headshots": 582, - "assault_rifle_damage": 9067, - "stalker_kills": 143 - }, - "76561198044316328": { - "explosive_damage": 19674, - "shotgun_damage": 3835, - "welded_amount": 1 - } -} diff --git a/src/database/file.rs b/src/database/file.rs deleted file mode 100644 index d1d9390..0000000 --- a/src/database/file.rs +++ /dev/null @@ -1,220 +0,0 @@ -use serde_json; -use serde_json::json; -use std::error::Error; - -extern crate custom_error; -use custom_error::custom_error; - -const JSON_POINTER_SEPARATOR: &str = "/"; - -custom_error! { pub IncorrectPointer{pointer: String} = "Incorrect pointer is specified: {pointer}" } - -/// This is a enum that used internally to refer to values inside of -/// JSON (their serde implementation) objects and arrays. -/// This enum helps to simplify module's code. -/// -/// For values inside of JSON object it stores object's -/// `Map` and name of referred value. -/// -/// For values inside JSON arrays it stores array's -/// `Vec` and referred index. -/// -/// `Invalid` can be used to return a failed state. -enum ValueReference<'a> { - Object(&'a mut serde_json::Map, String), - Array(&'a mut Vec, usize), - Invalid, -} - -/// Implements database's file by wrapping JSON value (`serde_json::Value`) -/// and providing several convenient accessor methods. -#[derive(Debug)] -pub struct File { - /// File's full contents, normally a JSON object. - contents: serde_json::Value, -} - -impl ToString for File { - fn to_string(&self) -> String { - self.contents.to_string() - } -} - -impl File { - /// Creates an empty file that will contain an empty JSON object. - pub fn empty() -> File { - File { - contents: json!({}), - } - } - - /// Loads JSON value from the specified file. - pub fn load(file_contents: String) -> Result> { - Ok(File { - contents: serde_json::from_str(&file_contents)?, - }) - } - - /// Returns file's "root", - JSON value contained inside it. - pub fn root(&self) -> &serde_json::Value { - &self.contents - } - - /// Attempts to return JSON value, corresponding to the given JSON pointer. - /// `None` if the value is missing. - pub fn get(&self, pointer: &str) -> Option<&serde_json::Value> { - self.contents.pointer(pointer) - } - - /// Checks if values at a given JSON pointer exists. - /// Returns `true` if it does. - pub fn contains(&self, pointer: &str) -> bool { - self.get(pointer) != None - } - - /// Inserts new JSON value inside this file - /// (possibly in some sub-object/array). - /// - /// Given pointer must point at new value: - /// 1. If it already exists, - it will be overwritten. - /// 2. If it does not exist, but it's parent object/array does - - /// it will be added. - /// 3. Otherwise an error will be raise. - /// - /// If array needs to be expanded, - missing values will be filled - /// with `json!(null)`, i.e. inserting `7` at index `5` in array `[1, 2, 3]` - /// will produce `[1, 2, 3, null, null, 7]`. - pub fn insert( - &mut self, - pointer: &str, - new_value: serde_json::Value, - ) -> Result<(), IncorrectPointer> { - self.touch(pointer)?; - match self.contents.pointer_mut(pointer) { - Some(v) => *v = new_value, - _ => { - // If after `touch()` call we still don't have an existing value - - // something is wrong with the `pointer` - return Err(IncorrectPointer { - pointer: pointer.to_owned(), - }); - } - }; - Ok(()) - } - - /// Removes (and returns) value specified by theJSON pointer. - /// If it did not exist - returns `None`. - pub fn remove(&mut self, pointer: &str) -> Option { - match self.pointer_to_reference(pointer) { - ValueReference::Object(map, variable_name) => map.remove(&variable_name), - ValueReference::Array(vec, variable_index) => { - if variable_index < vec.len() { - return Some(vec.remove(variable_index)); - } - None - } - _ => None, - } - } - - /// Helper method to create a value if missing (as `json!(null)`). - /// Can only be done if parent container already exists. - /// - /// For specifics refer to `insert()` method. - fn touch(&mut self, pointer: &str) -> Result<(), IncorrectPointer> { - // If value is present - we're done - if pointer.is_empty() || self.contents.pointer_mut(pointer).is_some() { - return Ok(()); - } - // Otherwise - try to create it - match self.pointer_to_reference(pointer) { - ValueReference::Object(map, variable_name) => { - map.insert(variable_name, json!(null)); - } - ValueReference::Array(vec, variable_index) => { - // We've checked at the beginning of this method that value - // at `variable_index` does not exist, which guarantees - // that array is to short and we won't shrink it - vec.resize(variable_index + 1, json!(null)); - } - _ => { - return Err(IncorrectPointer { - pointer: pointer.to_owned(), - }) - } - }; - Ok(()) - } - - /// Helper method, - converts JSON pointer into auxiliary `ValueReference` enum. - fn pointer_to_reference<'a>(&'a mut self, pointer: &str) -> ValueReference<'a> { - if pointer.is_empty() { - return ValueReference::Invalid; - } - // Extract variable name (that `pointer` points to) - // and reference to it's container - // - // i.e. given file with '{"obj":{"arr":[1,3,5,2,4]}}', - // for pointer `/obj/arr/5`, - // it will return, basically, `(&[1,3,5,2,4], "5")` - let container_variable_pair = - pop_json_pointer(pointer).and_then(move |(path, variable_name)| { - match self.contents.pointer_mut(&path) { - Some(v) => Some((v, variable_name)), - _ => None, - } - }); - let (json_container, variable_name) = match container_variable_pair { - Some(v) => v, - _ => return ValueReference::Invalid, - }; - // For arrays we also need to confirm validity of the variable name - // and convert it into `usize` - match json_container { - serde_json::Value::Object(map) => ValueReference::Object(map, variable_name), - serde_json::Value::Array(vec) => { - let index: usize = match variable_name.parse() { - Ok(v) => v, - _ => return ValueReference::Invalid, - }; - ValueReference::Array(vec, index) - } - _ => ValueReference::Invalid, - } - } -} - -// Helper function to disassemble JSON path. -fn pop_json_pointer(pointer: &str) -> Option<(String, String)> { - let mut pointer = pointer.to_string(); - let last_separator_index = match pointer.rfind(JSON_POINTER_SEPARATOR) { - Some(v) => v, - _ => { - return None; - } - }; - if last_separator_index >= pointer.len() { - pointer.pop(); - return Some((pointer, String::new())); - } - let var_name = pointer.split_off(last_separator_index + 1); - pointer.pop(); - Some((pointer, var_name)) -} - -#[test] -fn test_pop_json_pointer() { - assert_eq!( - pop_json_pointer("/a/b/c/d"), - Some(("/a/b/c".to_owned(), "d".to_owned())) - ); - assert_eq!(pop_json_pointer("/"), Some(("".to_owned(), "".to_owned()))); - assert_eq!( - pop_json_pointer("/a/b/"), - Some(("/a/b".to_owned(), "".to_owned())) - ); - assert_eq!(pop_json_pointer(""), None); - // This pointer is incorrect - assert_eq!(pop_json_pointer("var"), None); -} diff --git a/src/database/io.rs b/src/database/io.rs deleted file mode 100644 index 9cbb679..0000000 --- a/src/database/io.rs +++ /dev/null @@ -1,240 +0,0 @@ -use super::*; -use log::{error, info, warn}; -use std::collections::HashMap; -use std::error::Error; -use std::fs; -use std::path; -use std::path::Path; - -extern crate custom_error; -use custom_error::custom_error; - -const JSON_EXTENSION: &str = "json"; - -custom_error! { pub IOError - NotDirectory{path: String} = "Path to the database should point at a directory: {path}", -} - -/// Reads database data from a directory at the specified path -/// as a vector of `Group`s. -pub fn read(db_path: &path::Path) -> Result, Box> { - if !db_path.is_dir() { - error!( - "Loading database from a non-directory {} was attempted.", - db_path.display() - ); - return Err(Box::new(IOError::NotDirectory { - path: db_path.display().to_string(), - })); - } - info!("Loading database from {}.", db_path.display()); - let mut groups = Vec::new(); - for entry in fs::read_dir(db_path)? { - let path = match entry { - Ok(r) => r, - _ => continue, - } - .path(); - if !check_valid_group_dir(path.as_path()) { - continue; - } - match read_group(&path)? { - Some(g) => groups.push(g), - _ => (), - } - } - info!("Correctly finished leading database."); - Ok(groups) -} - -/// Writes data of the given database into the directory specified by the path. -/// Does not clear it from any previously existing files, -/// you can use `clear_dir()` for that. -pub fn write(db_path: &path::Path, db: &Database) -> Result<(), Box> { - if db_path.exists() && !db_path.is_dir() { - error!( - "Cannot write database into a non-directory {}", - db_path.display() - ); - return Err(Box::new(IOError::NotDirectory { - path: db_path.display().to_string(), - })); - } - fs::create_dir(db_path)?; - for group in db.group_names().iter() { - let group_path = db_path.join(group); - if !group_path.exists() || !group_path.is_dir() { - fs::create_dir(group_path.clone())?; - } - for file in db.file_names_in(group).iter() { - let file_path = group_path.join(format!("{}.{}", file, JSON_EXTENSION)); - match db.file(group, file) { - Some(file) => fs::write(file_path, file.to_string())?, - _ => (), - } - } - } - Ok(()) -} - -/// Given a path to directory that was used for database storage - -/// clears it's data. -/// -/// This means removing any '.json' files from all immediate subdirectories and -/// then removing any subdirectories that were or became empty. -pub fn clear_dir(db_path: &path::Path) -> Result<(), Box> { - info!( - "Clearing directory {} from database files.", - db_path.display() - ); - if !db_path.exists() { - info!("Directory not found, nothing to do."); - return Ok(()); - } - for entry in fs::read_dir(db_path)? { - let dir_path = match entry { - Ok(r) => r, - _ => continue, - } - .path(); - if !check_valid_group_dir(dir_path.as_path()) { - continue; - } - for entry in fs::read_dir(dir_path.clone())? { - let file_path = match entry { - Ok(r) => r, - _ => continue, - } - .path(); - if !check_valid_data_file(file_path.as_path()) { - continue; - } - fs::remove_file(file_path)?; - } - let _ = fs::remove_dir(dir_path); - } - let _ = fs::remove_dir(db_path); - info!("Correctly finished clearing database files."); - Ok(()) -} - -/// helper function to read a group from a given subdirectory: -/// loads data from all containing '.json' files. -fn read_group(group_path: &path::Path) -> Result, Box> { - let mut files = HashMap::new(); - for entry in fs::read_dir(group_path)? { - let path = match entry { - Ok(r) => r, - _ => continue, - } - .path(); - if !check_valid_data_file(path.as_path()) { - continue; - } - let file_name = get_file_name(path.as_path()); - let file_contents = fs::read_to_string(&path)?; - files.insert(file_name, File::load(file_contents)?); - } - if files.len() > 0 { - return Ok(Some(Group { - name: get_file_name(group_path), - files, - })); - } - Ok(None) -} - -/// Checks if given path points at a directory that can represent -/// a database group (has a valid name). -fn check_valid_group_dir(dir_path: &path::Path) -> bool { - if !dir_path.is_dir() { - warn!( - r#"Skipping {}, because only directories are expected in database's root."#, - dir_path.display() - ); - return false; - } - if !is_name_valid(&get_file_name(dir_path)) { - warn!( - r#"Skipping directory {}, because it does not have a valid name."#, - dir_path.display() - ); - return false; - } - true -} - -/// Checks if given path points at a file that can represent -/// a database group (has a valid name). -fn check_valid_data_file(file_path: &path::Path) -> bool { - if file_path.is_dir() { - warn!( - r#"Skipping directory {}, because group directories are only\ - supposed to contain files."#, - file_path.display() - ); - return false; - } - let name = get_file_name(file_path); - if !is_name_valid(&name) { - warn!( - r#"Skipping file {}, because it does not have a valid name."#, - file_path.display() - ); - return false; - } - let extension = get_file_extension(file_path); - if !JSON_EXTENSION.eq_ignore_ascii_case(&extension) { - warn!( - r#"Skipping file {}, because it does not have "json" extension."#, - file_path.display() - ); - return false; - } - true -} - -fn get_file_name(path: &path::Path) -> String { - path.file_stem() - .and_then(|x| x.to_str()) - .unwrap_or_default() - .to_string() -} - -fn get_file_extension(path: &path::Path) -> String { - path.extension() - .and_then(|x| x.to_str()) - .unwrap_or_default() - .to_string() -} - -#[test] -fn test_file_name_extension_extraction() { - assert_eq!(get_file_name(Path::new("/dir/file")), "file".to_owned()); - assert_eq!( - get_file_name(Path::new("/dir/sub_dir/some.ext")), - "some".to_owned() - ); - assert_eq!( - get_file_name(Path::new("/dir/sub_dir/.ext")), - ".ext".to_owned() - ); - assert_eq!( - get_file_name(Path::new("/dir/sub_dir/thing.")), - "thing".to_owned() - ); - - assert_eq!(get_file_extension(Path::new("/dir/file")), "".to_owned()); - assert_eq!( - get_file_extension(Path::new("/dir/sub_dir/some.ext")), - "ext".to_owned() - ); - assert_eq!( - get_file_extension(Path::new("/dir/sub_dir/.ext")), - "".to_owned() - ); - assert_eq!( - get_file_extension(Path::new("/dir/sub_dir/thing.")), - "".to_owned() - ); -} diff --git a/src/database/mod.rs b/src/database/mod.rs deleted file mode 100644 index 72b52b7..0000000 --- a/src/database/mod.rs +++ /dev/null @@ -1,263 +0,0 @@ -#[cfg(test)] -mod tests; - -use serde_json; -use std::collections::HashMap; -use std::error::Error; -use std::path; - -extern crate custom_error; -use custom_error::custom_error; - -pub mod file; -pub use file::File; - -pub mod io; - -custom_error! { pub DBError - InvalidEntityName{entity_name: String} = r#"Cannot use {entity_name} for file or group"#, - NoGroup{group_name: String} = r#"Group "{group_name}" does not exist"#, - NoFile{group_name: String, file_name: String} = r#"There is no "{file_name}" file in group\ - "{group_name}""#, - GroupAlreadyExists{group_name: String} = r#"Group "{group_name}" already exists"#, - FileAlreadyExists{group_name: String, file_name: String} = r#"File "{file_name}" already exists\ - in the group "{group_name}""#, -} - -/// Avarice database is a collection of named JSON values (by default objects), -/// separated into different names groups. Names of such groups and JSON values must only contain -/// numbers and latin letters (ASCII subset). -/// -/// This database is only supposed to hold a relatively small amount of data -/// that: -/// -/// 1. can be freely and full loaded into memory; -/// 2. then saved all at once. -/// -/// Database is loaded and saved on the disk as a directory, -/// that contains subdirectories (with valid names) for each group; -/// those subdirectories in turn must contain "*.json" files (with valid names) -/// that correspond to the stored JSON values. -/// -/// Database directory should not contain any other files, but their presence -/// should not prevent database from loading (such files should be ignored). -pub struct Database { - /// Path to the database's directory - storage_path: path::PathBuf, - /// Collection of groups (of JSON values) inside of database - groups: Vec, -} - -/// Represents a database's group: a ste of named files -pub struct Group { - /// Name of the group - name: String, - /// Maps file names with their contents (as file::File structures) - files: HashMap, -} - -impl Database { - /// Creates new database by loading it from the specified directory. - /// Directory must contain valid database and be readable. - pub fn load(storage_path: &path::Path) -> Result> { - Ok(Database { - storage_path: storage_path.to_path_buf(), - groups: io::read(storage_path)?, - }) - } - - /// Removes all data from the database. - pub fn clear(&mut self) { - self.groups = Vec::new(); - } - - /// Returns path from which this database was loaded (can be changed with `change_path()`). - pub fn path(&mut self) -> path::PathBuf { - self.storage_path.clone() - } - - /// Changes current path of this database. All operations with files will use this path, - /// unless stated otherwise. - /// Directory must contain valid database and be readable. - /// - /// This method will also remove all data from the current database's path - /// and fail if it can't. - pub fn change_path(&mut self, new_path: &path::Path) -> Result<(), Box> { - self.write_copy(new_path)?; - io::clear_dir(&self.storage_path)?; - self.storage_path = new_path.to_path_buf(); - Ok(()) - } - - /// Erases database's data on disk. - pub fn erase(self) -> Result<(), Box> { - io::clear_dir(&self.storage_path)?; - Ok(()) - } - - /// Writes copy of the current database into specified directory. - /// Erases any preexisting database files. - /// - /// Empty group won't be saved. - pub fn write_copy(&self, new_path: &path::Path) -> Result<(), Box> { - io::clear_dir(new_path)?; - io::write(new_path, &self)?; - Ok(()) - } - - /// Writes current state of the database on the disk. - /// - /// Empty group won't be saved. - pub fn save(&self) -> Result<(), Box> { - self.write_copy(&self.storage_path)?; - Ok(()) - } - - /// Returns names of all the groups in the database. - pub fn group_names(&self) -> Vec { - self.groups.iter().map(|x| x.name.clone()).collect() - } - - /// Returns names of all the files in a particular group. - pub fn file_names_in(&self, group_name: &str) -> Vec { - match self.groups.iter().find(|x| x.name.eq(group_name)) { - Some(group) => group.files.keys().map(|x| x.clone()).collect(), - None => Vec::new(), - } - } - - /// Checks if specified group exists in the database - pub fn contains_group(&self, group_name: &str) -> bool { - self.group_index(group_name).is_ok() - } - - /// Creates a new empty group. - /// Will produce error if group already exists. - pub fn create_group(&mut self, group_name: &str) -> Result<(), DBError> { - assert_name_is_valid(group_name)?; - self.assert_no_group(group_name)?; - self.groups.push(Group { - name: group_name.to_owned(), - files: HashMap::new(), - }); - Ok(()) - } - - /// Removes specified group. - /// Will produce error if group does not exist. - pub fn remove_group(&mut self, group_name: &str) -> Result<(), DBError> { - let index = self.group_index(group_name)?; - self.groups.remove(index); - Ok(()) - } - - /// Checks if specified file (in a specified group) is contained in the database. - pub fn contains_file(&self, group_name: &str, file_name: &str) -> bool { - self.group_files(group_name) - .and_then(|x| Ok(x.contains_key(&file_name.to_owned()))) - .unwrap_or(false) - } - - /// Creates new file in the specified group that will contain an empty JSON object. - /// Will produce error if file already exists. - pub fn create_file(&mut self, group_name: &str, file_name: &str) -> Result<&mut File, DBError> { - assert_name_is_valid(&file_name)?; - let files = self.group_files_mut(group_name)?; - if files.contains_key(&file_name.to_owned()) { - return Err(DBError::FileAlreadyExists { - group_name: group_name.to_owned(), - file_name: file_name.to_owned(), - }); - } - let new_file = File::empty(); - files.insert(file_name.to_owned(), new_file); - Ok(files - .get_mut(file_name) - .expect("Missing value that was just inserted.")) - } - - /// Removes specified file (in a specified group). - /// Will produce error if file does not exist. - pub fn remove_file(&mut self, group_name: &str, file_name: &str) -> Result<(), DBError> { - if self - .group_files_mut(group_name)? - .remove(file_name) - .is_none() - { - return Err(DBError::NoFile { - group_name: group_name.to_owned(), - file_name: file_name.to_owned(), - }); - } - Ok(()) - } - - /// Returns immutable reference to the specified file (in a specified group) as `file::File`. - /// `None` if file does not exist. - pub fn file_mut(&mut self, group_name: &str, file_name: &str) -> Option<&mut File> { - match self.group_files_mut(group_name) { - Ok(files) => files.get_mut(&file_name.to_owned()), - _ => None, - } - } - - /// Returns mutable reference to the specified file (in a specified group) as `file::File`. - /// `None` if file does not exist. - pub fn file(&self, group_name: &str, file_name: &str) -> Option<&File> { - match self.group_files(group_name) { - Ok(files) => files.get(&file_name.to_owned()), - _ => None, - } - } - - /// Helper method that raises error if specified group exists. - fn assert_no_group(&self, group_name: &str) -> Result<(), DBError> { - if self.group_index(group_name).is_ok() { - return Err(DBError::GroupAlreadyExists { - group_name: group_name.to_owned(), - }); - } - Ok(()) - } - - /// Returns current index of the specified group in`groups` vector. - fn group_index(&self, group_name: &str) -> Result { - match self.groups.iter().position(|x| x.name.eq(group_name)) { - Some(index) => Ok(index), - _ => { - return Err(DBError::NoGroup { - group_name: group_name.to_owned(), - }) - } - } - } - - // Helper methods that return (im)mutable reference to `HashMap` - // (of 'file_name -> file' map) for a particular group. - fn group_files_mut(&mut self, group_name: &str) -> Result<&mut HashMap, DBError> { - let group_index = self.group_index(group_name)?; - Ok(&mut (&mut self.groups[group_index]).files) - } - - fn group_files(&self, group_name: &str) -> Result<&HashMap, DBError> { - let group_index = self.group_index(group_name)?; - Ok(&self.groups[group_index].files) - } -} - -/// Name validity check (for groups and files) -fn is_name_valid(entity_name: &str) -> bool { - entity_name - .chars() - .all(|x| x.is_ascii_alphabetic() || x.is_ascii_digit()) -} - -/// Helper function that raises error if passed name is invalid -fn assert_name_is_valid(entity_name: &str) -> Result<(), DBError> { - if is_name_valid(entity_name) { - return Ok(()); - } - return Err(DBError::InvalidEntityName { - entity_name: entity_name.to_owned(), - }); -} diff --git a/src/database/tests.rs b/src/database/tests.rs deleted file mode 100644 index a45d8fa..0000000 --- a/src/database/tests.rs +++ /dev/null @@ -1,380 +0,0 @@ -use super::*; -use serde_json::json; -use std::fs; -use std::path; - -const TEST_DB_PATH: &str = "./fixtures/database"; -const TEST_DB_MOVED_PATH: &str = "./fixtures/moved"; - -const NO_DB_MESSAGE: &str = "Can not find/load test database"; - -struct TestCleanup { - path: String, - clear_moved: bool, -} - -impl Drop for TestCleanup { - fn drop(&mut self) { - let _ = fs::remove_dir_all(&self.path); - if self.clear_moved { - let _ = fs::remove_dir_all(TEST_DB_MOVED_PATH); - } - } -} - -fn prepare_db_copy(copy_id: &str, clear_moved: bool) -> (String, TestCleanup) { - let path = format!("{}_{}", TEST_DB_PATH, copy_id); - let original_db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - original_db - .write_copy(path::Path::new(&path)) - .expect("Should be able to create a new copy of the fixture database."); - ( - path.clone(), - TestCleanup { - path: path.to_owned(), - clear_moved: clear_moved, - }, - ) -} - -#[test] -fn db_path() { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - assert!(db.path() == path::Path::new(TEST_DB_PATH)); -} - -#[test] -fn db_clear() { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - db.clear(); - assert!(!db.contains_group("game")); - assert!(!db.contains_file("administration", "registered")); - let names = db.group_names(); - assert_eq!(names.len(), 0); -} - -#[test] -fn db_group_names() { - let db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - - let names = db.group_names(); - assert!(names.contains(&"administration".to_owned())); - assert!(names.contains(&"game".to_owned())); - assert_eq!(names.len(), 2); -} - -#[test] -fn db_file_names() { - let db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - - let names_admin = db.file_names_in("administration"); - let names_game = db.file_names_in("game"); - assert!(names_admin.contains(&"registered".to_owned())); - assert!(names_game.contains(&"general".to_owned())); - assert!(names_game.contains(&"perks".to_owned())); - assert_eq!(names_admin.len(), 1); - assert_eq!(names_game.len(), 2); -} - -#[test] -fn db_group_check() { - let db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - - assert!(db.contains_group("game")); - assert!(db.contains_group("administration")); - assert!(!db.contains_group("perks")); - assert!(!db.contains_group("7random7")); - assert!(!db.contains_group("")); -} - -#[test] -fn db_group_remove() -> Result<(), Box> { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - // Success - db.remove_group("administration")?; - db.remove_group("game")?; - assert!(!db.contains_group("administration")); - assert!(!db.contains_group("game")); - // Failure - db.remove_group("test") - .expect_err("Testing whether removing non-existent groups with incorrect ASCII characters causes errors."); - db.remove_group("administration") - .expect_err("Testing whether removing non-existent groups with incorrect ASCII characters causes errors."); - db.remove_group("game group").expect_err( - "Testing whether removing non-existent groups with whitespace characters causes errors.", - ); - Ok(()) -} - -#[test] -fn db_group_create() -> Result<(), Box> { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - // Success - db.create_group("7random7")?; - assert!(db.contains_group("7random7")); - assert!(db.contains_group("game")); - // Failure - db.create_group("my_group").expect_err( - "Testing whether creating groups with incorrect ASCII characters causes errors.", - ); - db.create_group("my group") - .expect_err("Testing whether creating groups with whitespace characters causes errors."); - db.create_group("Š–group") - .expect_err("Testing whether creating groups with non-ASCII characters causes errors."); - // Create after removal - db.remove_group("game")?; - assert!(!db.contains_group("game")); - db.create_group("game")?; - assert!(db.contains_group("game")); - assert!(db.file_names_in("game").is_empty()); - Ok(()) -} - -#[test] -fn db_file_check() { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - // Success - assert!(db.contains_file("administration", "registered")); - assert!(db.file("game", "general").is_some()); - assert!(db.file_mut("game", "perks").is_some()); - // Failure - assert!(!db.contains_file("game", "perk")); - assert!(db.file("games", "perks").is_none()); - assert!(db.file_mut("random", "rnd_file").is_none()); -} - -#[test] -fn db_file_create() -> Result<(), Box> { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - // Success - let file = db.create_file("administration", "secrets")?; - assert_eq!(file.to_string(), "{}"); - assert!(db.contains_file("administration", "secrets")); - assert!(db.contains_file("game", "perks")); - // Failure - db.create_file("administration", "secrets") - .expect_err("Testing whether creating existing file causes errors."); - db.create_file("none", "secrets") - .expect_err("Testing whether creating existing file in non-existent group causes errors."); - db.create_file("game", "sec_rets") - .expect_err("Testing whether creating existing file with invalid name causes errors."); - Ok(()) -} - -#[test] -fn db_file_remove() -> Result<(), Box> { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - // Success - db.remove_file("administration", "registered")?; - assert!(!db.contains_file("administration", "registered")); - assert!(db.contains_group("administration")); - db.remove_file("game", "perks")?; - assert_eq!(db.file_names_in("game").len(), 1); - // Failure - db.remove_file("administration", "registered") - .expect_err("Testing whether removing non-existent files causes errors."); - db.remove_file("administration", "never") - .expect_err("Testing whether removing non-existent files causes errors."); - db.remove_file("never", "file") - .expect_err("Testing whether removing non-existent files causes errors."); - Ok(()) -} - -#[test] -fn file_json_contents() { - let db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - let registered = db.file("administration", "registered").unwrap().root(); - let user_map = registered - .as_object() - .expect("Read value is not an object."); - assert_eq!(user_map.len(), 2); - assert!(user_map.contains_key("76561198025127722")); - let user_record = user_map - .get("76561198044316328") - .unwrap() - .as_object() - .unwrap(); - assert_eq!(user_record.len(), 3); - assert_eq!(user_record.get("ip_lock").unwrap().as_bool(), Some(false)); - assert_eq!( - user_record.get("password_hash").unwrap().as_str(), - Some("fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d") - ); - - let groups_arrays = user_record.get("groups").unwrap().as_array().unwrap(); - assert_eq!(groups_arrays.len(), 1); - assert_eq!(groups_arrays[0], "admin"); -} - -#[test] -fn file_json_get() { - let db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - // Test empty path - let file = db.file("administration", "registered").unwrap(); - assert_eq!(file.root(), file.get("").unwrap()); - // Test complex path - let expected = file.get("/76561198025127722/allowed_ips/1").unwrap(); - assert_eq!(expected.as_str().unwrap(), "192.168.0.100"); - // Test bad paths - assert!(file.get("/777") == None); - assert!(file.get("/76561198025127722/allowed_ips/2") == None); -} - -#[test] -fn file_contains_check() { - let db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - let registered_file = db.file("administration", "registered").unwrap(); - let perks_file = db.file("game", "perks").unwrap(); - // These exist - assert!(registered_file.contains("/76561198025127722/password_hash")); - assert!(registered_file.contains("/76561198044316328/groups/0")); - assert!(perks_file.contains("/76561198025127722/headshots")); - assert!(perks_file.contains("/76561198044316328")); - // These do not exist - assert!(!registered_file.contains("/76561198025127722/password/")); - assert!(!registered_file.contains("/76561198044316328/groups/2")); - assert!(!perks_file.contains("/76561198025127722/assault_rifle_damage/9067")); - assert!(!perks_file.contains("/76561198044316328/headshots")); -} - -#[test] -fn db_insert_success() -> Result<(), Box> { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - let registered_file = db.file_mut("administration", "registered").unwrap(); - // Modify existing - registered_file.insert("/76561198025127722/ip_lock", json!(false))?; - assert_eq!( - registered_file - .get("/76561198025127722/ip_lock") - .unwrap() - .to_string(), - "false" - ); - registered_file.insert("/76561198044316328/password_hash", json!({"var":13524}))?; - assert_eq!( - registered_file - .get("/76561198044316328/password_hash") - .unwrap() - .to_string(), - r#"{"var":13524}"# - ); - // Reset whole file - registered_file.insert("", json!({}))?; - assert_eq!(registered_file.root().to_string(), "{}"); - // Add new values - registered_file.insert("/new_var", json!([42, {"word":"life"}, null]))?; - assert_eq!( - registered_file.root().to_string(), - r#"{"new_var":[42,{"word":"life"},null]}"# - ); - let general_file = db.file_mut("game", "general").unwrap(); - general_file.insert("/76561198025127722/achievements/5", json!("kf:bugged"))?; - assert_eq!( - general_file - .get("/76561198025127722/achievements") - .unwrap() - .to_string(), - r#"["kf:LabCleaner","kf:ChickenFarmer","scrn:playedscrn",null,null,"kf:bugged"]"# - ); - Ok(()) -} - -#[test] -fn db_set_failure() { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - let file = db.file_mut("administration", "registered").unwrap(); - file.insert("/76561198025127722/dir/var", json!(null)) - .expect_err("Testing panic at trying to set a value in non-existing object/array."); - file.insert("/76561198044316328/groups/d", json!(null)) - .expect_err("Testing panic at trying to set a value at non-numeric index in an array."); - file.insert("/76561198044316328/groups/-1", json!(null)) - .expect_err("Testing panic at trying to set a value at negative index in an array."); -} - -#[test] -fn db_remove_value() { - let mut db = Database::load(path::Path::new(TEST_DB_PATH)).expect(NO_DB_MESSAGE); - let file = db.file_mut("administration", "registered").unwrap(); - // Removing non-existent value - assert_eq!(file.remove("/76561198025127722/something"), None); - // Remove simple value - assert_eq!( - file.remove("/76561198025127722/password_hash").unwrap(), - json!("fce798e0804dfb217f929bdba26745024f37f6b6ba7406f3775176e20dd5089d") - ); - assert!(!file.contains("/76561198025127722/password_hash")); - // Remove complex value (array) - assert_eq!( - file.remove("/76561198044316328/groups").unwrap(), - json!(["admin"]) - ); - assert!(!file.contains("/76561198044316328/groups/0")); - assert!(!file.contains("/76561198044316328/groups")); - // Remove array elements - assert_eq!( - file.remove("/76561198025127722/allowed_ips/0").unwrap(), - json!("127.0.0.1") - ); - assert!(file.contains("/76561198025127722/allowed_ips/0")); - assert!(!file.contains("/76561198025127722/allowed_ips/1")); - assert_eq!( - file.remove("/76561198025127722/allowed_ips/0").unwrap(), - json!("192.168.0.100") - ); - assert!(file.contains("/76561198025127722/allowed_ips")); - assert!(!file.contains("/76561198025127722/allowed_ips/0")); -} - -#[test] -fn db_save() { - let (path, _cleanup) = prepare_db_copy("db_save", false); - // Change something up and save - let mut db = Database::load(path::Path::new(&path)).expect(NO_DB_MESSAGE); - db.remove_group("administration") - .expect(r#"Should be able to remove "administration" group"#); - db.save() - .expect("Should be able to save copy of the database."); - // Reload and check changes - let db = Database::load(path::Path::new(&path)).expect(NO_DB_MESSAGE); - assert_eq!(db.group_names().len(), 1); - assert_eq!(db.group_names().get(0), Some(&"game".to_owned())); - assert_eq!(db.file_names_in("game").len(), 2); - assert!(db.contains_file("game", "general")); - assert!(db.contains_file("game", "perks")); -} - -#[test] -fn db_change_path() { - let (path, _cleanup) = prepare_db_copy("db_change_path", true); - // Change something up and move - let mut db = Database::load(path::Path::new(&path)).expect(NO_DB_MESSAGE); - db.remove_group("administration") - .expect(r#"Should be able to remove "administration" group"#); - db.file_mut("game", "perks") - .unwrap() - .insert("", json!({"var":7})) - .expect("Should be able to insert into root."); - db.change_path(path::Path::new(TEST_DB_MOVED_PATH)) - .expect("Should be able to change database's path."); - assert!(!path::Path::new(&path).exists()); - assert!(path::Path::new(TEST_DB_MOVED_PATH).exists()); - // Reload and check the changes - let db = Database::load(path::Path::new(TEST_DB_MOVED_PATH)).expect(NO_DB_MESSAGE); - assert_eq!(db.group_names().len(), 1); - assert_eq!(db.group_names().get(0), Some(&"game".to_owned())); - assert_eq!(db.file_names_in("game").len(), 2); - assert!(db.contains_file("game", "general")); - assert!(db.contains_file("game", "perks")); - assert_eq!( - db.file("game", "perks").unwrap().root().to_string(), - r#"{"var":7}"#.to_owned() - ); -} - -#[test] -fn db_erase() { - let (path, _cleanup) = prepare_db_copy("db_erase", false); - let db = Database::load(path::Path::new(&path)).expect(NO_DB_MESSAGE); - db.erase().expect("Should be able to erase data."); - assert!(!path::Path::new(&path).exists()); -} diff --git a/src/main.rs b/src/main.rs index 0c27ddf..8f664c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,13 @@ use std::env; use std::path::Path; -mod database; - -use simplelog::{Config, LevelFilter, SimpleLogger}; +mod unreal_config; fn main() { - let _ = SimpleLogger::init(LevelFilter::Info, Config::default()); let args: Vec = env::args().collect(); let filename = &args[1]; - let db = database::Database::load(Path::new(filename)); - /*match db { - Ok(db) => print!("{}", db), - Err(error) => println!("OH NO: {}", error), - }*/ + let config = unreal_config::load_file(Path::new(filename)); + match config { + Ok(config) => print!("{}", config), + _ => (), + } }