1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
pub static HOBBIT_STORAGE: &str = "/var/tmp/hdb/";

macro_rules! unwrap_or_return {
    ( $e:expr ) => {
        match $e {
            Ok(x) => x,
            Err(e) => return Err(e.to_string()),
        }
    };
}

macro_rules! some_or_return {
    ( $e:expr ) => {
        match $e {
            Some(x) => x.to_string(),
            _ => return Err("None".to_string()),
        }
    };
}

/// Trim everything after the last slash of a string, e.g.,
///
///     /hobbit/test/hello -> /hobbit/test/
fn trim_slash(s: &str) -> String {
    let trim_len = match s.rfind('/') {
        None => 0,
        Some(i) => {
            // if the "/" is the last item in the string don't increment it
            // so that we don't create an out of bounds slice.
            if i == s.len() - 1 {
                i
            } else {
                // Move past the /
                i + 1
            }
        }
    };
    let trimmed = &s[..trim_len];
    trimmed.to_string()
}

pub fn get(table: &str, key: &str) -> Result<String, String> {
    let path = format!("{}{}", HOBBIT_STORAGE, table);
    if let Ok(data) = std::fs::read_to_string(&path) {
        let json: serde_json::Value = unwrap_or_return!(serde_json::from_str(&data));
        return Ok(some_or_return!(json[key].as_str()));
    }
    let error = format!("Error reading table '{}'", table);
    Err(error)
}

pub fn set(table: &str, key: &str, value: &str) -> Result<(), String> {
    // If the table doesn't yet exist, just create it while we're at it.
    let _ = make(table);
    let path = format!("{}{}", HOBBIT_STORAGE, table);
    if let Ok(data) = std::fs::read_to_string(&path) {
        let mut json: serde_json::Value = unwrap_or_return!(serde_json::from_str(&data));
        json[key] = serde_json::Value::String(value.to_string());
        return match std::fs::write(&path, json.to_string()) {
            Ok(_) => Ok(()),
            Err(e) => Err(e.to_string()),
        };
    }
    let error = format!("Error reading table '{}'", table);
    Err(error)
}

pub fn del(table: &str, key: &str) -> Result<(), String> {
    let path = format!("{}{}", HOBBIT_STORAGE, table);
    if let Ok(data) = std::fs::read_to_string(&path) {
        let json: serde_json::Value = unwrap_or_return!(serde_json::from_str(&data));
        match json {
            serde_json::Value::Object(mut map) => {
                map.remove(key);
                // If the table is now empty, just remove the file too.
                if map.len() == 0 {
                    let _ = std::fs::remove_file(&path);
                    // If the directory is now empty, then clean it up as well.
                    let _ = std::fs::remove_dir(trim_slash(&path));
                    return Ok(());
                } else {
                    let v: serde_json::Value = map.into();
                    return match std::fs::write(&path, v.to_string()) {
                        Ok(_) => Ok(()),
                        Err(e) => Err(e.to_string()),
                    };
                }
            }
            _ => return Err("Key not found".to_string()),
        }
    }
    let error = format!("Error reading table '{}'", table);
    Err(error)
}

fn make(table: &str) -> Result<(), String> {
    let _ = std::fs::create_dir_all(HOBBIT_STORAGE);
    let path = format!("{}{}", HOBBIT_STORAGE, table);
    // If the table contains a slash, create the subdirectory first.
    let _ = std::fs::create_dir_all(trim_slash(&path));
    // If the table already exists, return ok. If not, create empty json.
    if !std::fs::metadata(path.to_string()).is_ok() {
        let data = serde_json::json!({});
        match std::fs::write(path.to_string(), data.to_string()) {
            Ok(_) => return Ok(()),
            Err(e) => return Err(e.to_string()),
        }
    }
    Ok(())
}

pub fn map(table: &str) -> Result<serde_json::Map<String, serde_json::Value>, String> {
    let path = format!("{}{}", HOBBIT_STORAGE, table);
    if let Ok(data) = std::fs::read_to_string(&path) {
        let json: serde_json::Value = unwrap_or_return!(serde_json::from_str(&data));
        match json {
            serde_json::Value::Object(map) => {
                return Ok(map);
            }
            _ => return Err("Could not parse table as Map".to_string()),
        }
    }
    let error = format!("Error reading table '{}'", table);
    Err(error)
}