Alice_DBMS/
json_engine.rs

1/*                          MIT License
2
3Copyright (c) 2024 Daniil Ermolaev
4
5Permission is hereby granted, free of charge, to any person obtaining a copy
6of this software and associated documentation files (the "Software"), to deal
7in the Software without restriction, including without limitation the rights
8to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9copies of the Software, and to permit persons to whom the Software is
10furnished to do so, subject to the following conditions:
11
12The above copyright notice and this permission notice shall be included in all
13copies or substantial portions of the Software.
14
15THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21SOFTWARE. */
22
23use std::fs;
24use std::io::{self, Read, Write};
25use std::env;
26use std::path::{PathBuf, Path};
27use serde_json::{json, Value, Result as JsonResult};
28
29use log::{info, error, trace};
30use simplelog::*;
31
32use uuid::Uuid;
33use chrono::Local;
34
35const ROOT_DIR: &str = "Alice-Database-Data";
36const ADB_DATA_DIR: &str = "ADB_Data";
37const JSON_ENGINE_DIR: &str = "json_engine";
38
39
40/// A struct representing a document in the database.
41///
42/// A `Document` contains its name, file path, and its content stored as a JSON `Value`.
43#[derive(Debug, Clone)]
44pub struct Document {
45    pub name: String,
46    pub path: PathBuf,
47    pub json_value: Option<Value>,
48}
49
50/// A struct representing a collection of documents.
51///
52/// A `Collection` holds a name and a list of associated `Document`s.
53#[derive(Debug, Clone)]
54pub struct Collection {
55    pub name: String,
56    pub documents: Vec<Document>,
57}
58
59impl Collection {
60    // Method to get a document by name
61    pub fn get_document(&self, name: &str) -> Option<&Document> {
62        self.documents.iter().find(|doc| doc.name == name)
63    }
64
65    /// Retrieve a mutable reference to a document by its name.
66    ///
67    /// # Parameters
68    /// - `name`: The name of the document to retrieve.
69    ///
70    /// # Returns
71    /// An `Option` containing a mutable reference to the `Document` if found,
72    /// or `None` if not found.
73    pub fn get_document_mut(&mut self, name: &str) -> Option<&mut Document> {
74        self.documents.iter_mut().find(|doc| doc.name == name)
75    }
76
77    // Method to add a new document to the collection
78    pub fn add_document(&mut self, name: &str, content: &str) -> io::Result<()> {
79        let collection_path = Path::new(&self.path()).join(&self.name);
80        fs::create_dir_all(&collection_path)?;
81
82        let doc_path = collection_path.join(&name.clone()); // Correctly construct the document path
83
84        let mut file = fs::File::create(&doc_path)?;
85        file.write_all(content.as_bytes())?;
86
87        // Create a new Document instance
88        let new_document = Document {
89            name: name.to_string(),
90            path: doc_path.clone(),
91            json_value: parse_json_data(content).ok(),
92        };
93        self.documents.push(new_document);
94        Ok(())
95    }
96
97    // Method to delete a document from the collection
98    pub fn delete_document(&mut self, name: &str) -> io::Result<()> {
99        if let Some(doc) = self.documents.iter().find(|doc| doc.name == name) {
100            fs::remove_file(&doc.path)?;
101            self.documents.retain(|doc| doc.name != name);
102            Ok(())
103        } else {
104            Err(io::Error::new(io::ErrorKind::NotFound, "Document not found"))
105        }
106    }
107
108    fn path(&self) -> PathBuf {
109        let home_dir = env::home_dir().expect("Failed to get home directory");
110        home_dir.join(ROOT_DIR).join(ADB_DATA_DIR).join(JSON_ENGINE_DIR)
111    }
112}
113
114/// A struct to manage multiple collections of documents.
115#[derive(Debug, Clone)]
116pub struct JSONEngine {
117    collections: Vec<Collection>,
118}
119
120impl JSONEngine {
121    /// Create a new `JSONEngine`.
122    ///
123    /// # Parameters
124    /// - `root`: The path to the root directory for data storage.
125    ///
126    /// # Returns
127    /// A new instance of `JSONEngine`.
128    pub fn new(root: &Path, ) -> Self {
129        let collections = get_exists_collections(root);
130        JSONEngine { collections }
131    }
132
133
134    /// Retrieve a mutable reference to a collection by its name.
135    ///
136    /// # Parameters
137    /// - `name`: The name of the collection to retrieve.
138    ///
139    /// # Returns
140    /// An `Option` containing a mutable reference to the `Collection`, if found.
141    pub fn get_collection_mut(&mut self, name: &str) -> Option<&mut Collection> {
142        self.collections.iter_mut().find(|col| col.name == name)
143    }
144
145    /// Add a new collection.
146    ///
147    /// # Parameters
148    /// - `name`: The name of the collection to create.
149    ///
150    /// # Returns
151    /// An `Option` containing a mutable reference to the newly added `Collection`.
152    pub fn add_collection(&mut self, name: &str) -> Option<&mut Collection> {
153        let collection_path = Path::new(&self.root_path()).join(name);
154        fs::create_dir_all(&collection_path).ok()?; // Create the directory for new collection
155
156        let new_collection = Collection {
157            name: name.to_string(),
158            documents: vec![],
159        };
160
161        self.collections.push(new_collection);
162        self.collections.last_mut() // Return a mutable reference to the newly added collection
163    }
164
165    /// Get a collection by name.
166    ///
167    /// # Parameters
168    /// - `name`: The name of the collection to retrieve.
169    ///
170    /// # Returns
171    /// An `Option` containing a reference to the `Collection`, if found.
172    pub fn get_collection(&self, name: &str) -> Option<&Collection> {
173        self.collections.iter().find(|col| col.name == name)
174    }
175
176    /// Get a document from a specific collection.
177    ///
178    /// # Parameters
179    /// - `collection_name`: The name of the collection the document belongs to.
180    /// - `document_name`: The name of the document to retrieve.
181    ///
182    /// # Returns
183    /// An `Option` containing a reference to the `Document`, if found.
184    pub fn get_document(&self, collection_name: &str, document_name: &str) -> Option<&Document> {
185        self.get_collection(collection_name)?.get_document(document_name)
186    }
187
188    fn root_path(&self) -> PathBuf {
189        let home_dir = env::home_dir().expect("Failed to get home directory");
190        home_dir.join(ROOT_DIR).join(ADB_DATA_DIR).join(JSON_ENGINE_DIR)
191    }
192}
193
194impl Document {
195    /// Update a field in the document.
196    ///
197    /// # Parameters
198    /// - `key`: The key of the field to update.
199    /// - `value`: The new value for the field.
200    ///
201    /// # Returns
202    /// A result indicating success or failure.
203    pub fn update_rows(&mut self, key: &str, value: &Value) -> io::Result<()> {
204        if let Some(json_value) = &mut self.json_value {
205            if let Some(obj) = json_value.as_object_mut() {
206                obj.insert(key.to_string(), value.clone());
207                let updated_content = serde_json::to_string_pretty(json_value)?;
208                let mut file = fs::File::create(&self.path)?;
209                file.write_all(updated_content.as_bytes())?;
210                Ok(())
211            } else {
212                Err(io::Error::new(io::ErrorKind::InvalidData, "JSON is not an object"))
213            }
214        } else {
215            Err(io::Error::new(io::ErrorKind::InvalidData, "Document does not contain valid JSON"))
216        }
217    }
218
219    /// Delete a field in the document.
220    ///
221    /// # Parameters
222    /// - `key`: The key of the field to delete.
223    ///
224    /// # Returns
225    /// A result indicating success or failure.
226    pub fn delete_rows(&mut self, key: &str) -> io::Result<()> {
227        if let Some(json_value) = &mut self.json_value {
228            if let Some(obj) = json_value.as_object_mut() {
229                obj.remove(key);
230                let updated_content = serde_json::to_string_pretty(json_value)?;
231                let mut file = fs::File::create(&self.path)?;
232                file.write_all(updated_content.as_bytes())?;
233                Ok(())
234            } else {
235                Err(io::Error::new(io::ErrorKind::InvalidData, "JSON is not an object"))
236            }
237        } else {
238            Err(io::Error::new(io::ErrorKind::InvalidData, "Document does not contain valid JSON"))
239        }
240    }
241
242    /// Update a field in a nested JSON object.
243    ///
244    /// # Parameters
245    /// - `parent_key`: The parent key of the nested field.
246    /// - `key`: The key of the field to update within the parent key.
247    /// - `value`: The new value for the nested field.
248    ///
249    /// # Returns
250    /// A result indicating success or failure.
251    pub fn update_nested_field(&mut self, parent_key: &str, key: &str, value: &Value) -> io::Result<()> {
252        if let Some(json_value) = &mut self.json_value {
253            if let Some(parent) = json_value.get_mut(parent_key) {
254                if let Some(obj) = parent.as_object_mut() {
255                    obj.insert(key.to_string(), value.clone());
256                    let updated_content = serde_json::to_string_pretty(json_value)?;
257                    let mut file = fs::File::create(&self.path)?;
258                    file.write_all(updated_content.as_bytes())?;
259                    Ok(())
260                } else {
261                    Err(io::Error::new(io::ErrorKind::InvalidData, "Parent key is not an object"))
262                }
263            } else {
264                Err(io::Error::new(io::ErrorKind::NotFound, "Parent key not found"))
265            }
266        } else {
267            Err(io::Error::new(io::ErrorKind::InvalidData, "Document does not contain valid JSON"))
268        }
269    }
270}
271
272// Functions for handling file operations and collections
273fn get_documents_in_collection(path: &Path) -> Vec<Document> {
274    let entries = fs::read_dir(path).unwrap();
275    let mut documents: Vec<Document> = vec![];
276
277    for entry in entries {
278        let entry = entry.unwrap();
279        let entry_path = entry.path();
280        if entry_path.is_file() {
281            let name = entry_path.file_name().unwrap().to_string_lossy().into_owned();
282            let data = read_file_data(&entry_path).unwrap_or_default();
283            let json_value = parse_json_data(&data).ok();
284            let document = Document {
285                name,
286                path: entry_path.clone(),
287                json_value,
288            };
289            documents.push(document);
290        }
291    }
292    documents
293}
294
295fn read_file_data(path: &Path) -> io::Result<String> {
296    let mut file = fs::File::open(path)?;
297    let mut contents = String::new();
298    file.read_to_string(&mut contents)?;
299    Ok(contents)
300}
301
302fn parse_json_data(data: &str) -> JsonResult<Value> {
303    serde_json::from_str(data)
304}
305
306fn get_exists_collections(path: &Path) -> Vec<Collection> {
307    let mut collections: Vec<Collection> = vec![];
308
309    if path.exists() && path.is_dir() {
310        let entries = fs::read_dir(path).unwrap();
311
312        for entry in entries {
313            let entry = entry.unwrap();
314            let entry_path = entry.path();
315
316            if entry_path.is_dir() {
317                let documents = get_documents_in_collection(&entry_path);
318                let collection_name = entry_path.file_name().unwrap().to_string_lossy().into_owned();
319                let collection = Collection {
320                    name: collection_name,
321                    documents,
322                };
323                collections.push(collection);
324            }
325        }
326    } else {
327        error!("The specified path does not exist or is not a directory: {:?}", path);
328    }
329
330    collections
331}
332
333// Helper method to get a mutable reference to a document
334impl JSONEngine {
335    pub fn get_document_mut(&mut self, collection_name: &str, document_name: &str) -> Option<&mut Document> {
336        self.get_collection_mut(collection_name)?.documents.iter_mut().find(|doc| doc.name == document_name)
337    }
338}
339