1use crate::core::editor::CommandEditor;
2use crate::db::driver::Driver;
3use crate::error::{EngineError, MyError, MyResult};
4use rustyline::error::ReadlineError;
5use std::collections::BTreeMap;
6use std::fs;
7use std::fs::File;
8use std::io::{ErrorKind, Read, Write};
9use std::path::PathBuf;
10
11pub trait System<F: Read + Write> {
12 fn open_config(&mut self) -> MyResult<Option<F>>;
13
14 fn create_config(&self) -> MyResult<F>;
15
16 fn load_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()>;
17
18 fn save_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()>;
19
20 fn remove_history(&self, drivers: &BTreeMap<String, Driver>) -> MyResult<()>;
21}
22
23pub struct FileSystem;
24
25impl FileSystem {
26 pub fn new() -> Self {
27 Self { }
28 }
29}
30
31impl System<File> for FileSystem {
32 fn open_config(&mut self) -> MyResult<Option<File>> {
33 let path = config_path()?;
34 match File::open(&path) {
35 Ok(file) => Ok(Some(file)),
36 Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
37 Err(error) => Err(MyError::Io(error)),
38 }
39 }
40
41 fn create_config(&self) -> MyResult<File> {
42 let path = config_path()?;
43 if let Some(parent) = path.parent() {
44 fs::create_dir_all(parent)?;
45 }
46 let writer = File::create(&path)?;
47 Ok(writer)
48 }
49
50 fn load_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()> {
51 let path = history_path(alias)?;
52 match editor.load_history(&path) {
53 Ok(_) => Ok(()),
54 Err(ReadlineError::Io(error)) if error.kind() == ErrorKind::NotFound => Ok(()),
55 Err(error) => Err(MyError::Readline(error)),
56 }
57 }
58
59 fn save_history(&self, editor: &mut CommandEditor, alias: &str) -> MyResult<()> {
60 let path = history_path(alias)?;
61 if let Some(parent) = path.parent() {
62 fs::create_dir_all(parent)?;
63 }
64 editor.save_history(&path)?;
65 Ok(())
66 }
67
68 fn remove_history(&self, drivers: &BTreeMap<String, Driver>) -> MyResult<()> {
69 let dir = history_dir()?;
70 if let Ok(entries) = dir.read_dir() {
71 entries
72 .into_iter()
73 .filter_map(|entry| entry.ok())
74 .filter(|entry| entry.path().is_file())
75 .filter(|entry| !drivers.contains_key(entry.file_name().to_string_lossy().as_ref()))
76 .for_each(|entry| fs::remove_file(entry.path()).unwrap_or_default());
77 }
78 Ok(())
79 }
80}
81
82fn parent_dir() -> MyResult<PathBuf> {
83 match dirs::home_dir() {
84 Some(dir) => Ok(dir.join(".config").join("zql")),
85 None => Err(MyError::Engine(EngineError::HomeDirectory)),
86 }
87}
88
89fn config_path() -> MyResult<PathBuf> {
90 let parent = parent_dir()?;
91 let path = parent.join("drivers.yaml");
92 Ok(path)
93}
94
95fn history_dir() -> MyResult<PathBuf> {
96 let parent = parent_dir()?;
97 let path = parent.join("history");
98 Ok(path)
99}
100
101fn history_path(alias: &str) -> MyResult<PathBuf> {
102 let parent = parent_dir()?;
103 let path = parent.join("history").join(alias);
104 Ok(path)
105}
106
107#[cfg(any(test, debug_assertions))]
108pub mod tests {
109 use crate::core::editor::CommandEditor;
110 use crate::core::system::System;
111 use crate::db::driver::Driver;
112 use crate::error::MyResult;
113 use std::collections::BTreeMap;
114 use std::io::Cursor;
115
116 pub struct MockSystem {
117 buffer: Vec<u8>,
118 }
119
120 impl MockSystem {
121 pub fn new(source: &str) -> Self {
122 let buffer = Vec::from(source.as_bytes());
123 Self { buffer }
124 }
125 }
126
127 impl System<Cursor<Vec<u8>>> for MockSystem {
128 fn open_config(&mut self) -> MyResult<Option<Cursor<Vec<u8>>>> {
129 let buffer = self.buffer.drain(..).collect();
130 let reader = Cursor::new(buffer);
131 Ok(Some(reader))
132 }
133
134 fn create_config(&self) -> MyResult<Cursor<Vec<u8>>> {
135 let writer = Cursor::new(Vec::new());
136 Ok(writer)
137 }
138
139 fn load_history(&self, _editor: &mut CommandEditor, _alias: &str) -> MyResult<()> {
140 Ok(())
141 }
142
143 fn save_history(&self, _editor: &mut CommandEditor, _alias: &str) -> MyResult<()> {
144 Ok(())
145 }
146
147 fn remove_history(&self, _drivers: &BTreeMap<String, Driver>) -> MyResult<()> {
148 Ok(())
149 }
150 }
151}