-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.rs
133 lines (122 loc) · 4.36 KB
/
main.rs
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
126
127
128
129
130
131
132
133
#![feature(unix_sigpipe)]
use std::{
collections::HashMap,
io::Error,
path::{Path, PathBuf},
};
#[unix_sigpipe = "sig_dfl"]
fn main() {
let mut argv = std::env::args().skip(1);
let key = argv
.next()
.expect("No key passed! Did you forget to pass args: key value");
let value = argv
.next()
.expect("No value passed! Did you forget to pass args: key value");
println!("key: {} = {}", key, value);
let filename = "/tmp/kvstore.db";
let path = PathBuf::new().with_file_name(filename);
let mut database =
Database::new(path).expect(format!("Database::new({:#?}) crashed", filename).as_str());
println!("Initial Database is: {:?}", database);
// database.insert(key.to_uppercase(), value.clone());
database.insert(key.to_lowercase(), value.clone());
// database.save(filename);
println!("Intermediate Database is: {:?}", database);
database.flush().unwrap();
println!("Post-flush() Database is: {:?}", database);
// If flush() is called above, The following will compile because flush(&mut self) borrows the database inside the function scope
// If we comment this line out, the dirty bit is not reset so we don't need to flush in drop() destructor
database.insert(key.to_uppercase(), value);
println!("Final Database is: {:?}", database);
}
#[derive(Debug)]
struct Database {
path: PathBuf,
map: HashMap<String, String>,
dirty: bool,
}
impl Database {
fn new(db_filepath: PathBuf) -> Result<Database, Error> {
let mut map: HashMap<String, String> = HashMap::new();
// read the DB file
if !db_filepath.exists() {
let _maybe_parent_dir = db_filepath.parent();
let parent_dir = _maybe_parent_dir.unwrap_or_else(|| Path::new("."));
if !parent_dir.exists() {
std::fs::create_dir_all(parent_dir)?;
} //else if parent_dir.exists() && !parent_dir.is_dir() {
// return std::io::Error
// Err(format!("Path exists: {:?}", parent_dir.to_str()));
// }
} else {
let contents = std::fs::read_to_string(&db_filepath)?;
// parse the string
for line in contents.lines() {
let (key, value) = line.split_once('\t').expect("Corrupt database");
map.insert(key.to_owned(), value.to_owned());
}
}
// populate our map
Ok(Database {
path: db_filepath,
map,
dirty: false,
})
}
// fn insert() -> Result<(), Error> {
fn insert(&mut self, key: String, value: String) -> () {
self.map.insert(key, value);
self.dirty |= true;
}
fn save(&self, filename: &str) -> Result<(), Error> {
todo!("Implement Database.save(): Save and write Database to file.");
// Original main() implementation:
// if !path.exists() {
// let contents = format!("{}\t{}\n", key, value);
// let result = std::fs::write(path, contents);
// match result {
// Ok(()) => {
// println!("Successfully wrote to db file: {filename}\n");
// // TODO: return Result(Ok());
// // std::process::exit(0);
// }
// Err(e) => {
// return std::io::Error
// // panic!("Error writing to db file: {filename}\n{e}");
// }
// }
// }
}
fn flush(&mut self) -> std::io::Result<()> {
if self.dirty {
println!("database.flush() called");
let mut contents = String::new();
for (key, value) in &self.map {
contents.push_str(key);
contents.push('\t');
contents.push_str(value);
contents.push('\n');
}
match std::fs::write(&self.path, contents) {
Ok(()) => {
self.dirty = false;
Ok(())
}
Err(e) => {
println!("Error during Database::flush(): {:?}", e);
Err(e)
}
}
} else {
Ok(())
}
}
}
impl Drop for Database {
fn drop(&mut self) {
if self.dirty {
let _ = self.flush();
}
}
}