SQLite-backed Key / Value Store
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: got first run with kv store

+93 -63
+5 -2
src/cli.rs
··· 49 49 keys: Vec<String>, 50 50 }, 51 51 52 - /// Sets the mode for Safir (KV-file store or SQLite store - active on next run) 53 - Mode { mode: SafirMode }, 52 + /// Sets the mode for Safir (active on the next run of Safir) 53 + Mode { 54 + /// Mode to set (KV-file store or SQLite DB store) 55 + mode: SafirMode, 56 + }, 54 57 55 58 /// List all values in the store 56 59 List,
+14 -10
src/main.rs
··· 3 3 mod utils; 4 4 5 5 use cli::*; 6 + use store::SafirStore; 6 7 7 - use anyhow::Result; 8 + use anyhow::{Context, Result}; 8 9 9 - fn main() -> Result<()> { 10 + #[tokio::main] 11 + async fn main() -> Result<()> { 10 12 let cli = Cli::parse(); 13 + let mut safir = store::init_safir().context("loading safir store")?; 11 14 12 15 match cli.command { 13 16 Commands::Add { key, value } => { 14 - // safir.add(key.to_owned(), value.to_owned()); 17 + safir.add(key.to_owned(), value.to_owned()).await?; 15 18 } 16 19 Commands::Get { keys } => { 17 - // safir.get(args.keys.to_owned()); 20 + safir.get(keys.to_owned()).await?; 18 21 } 19 22 Commands::Rm { keys } => { 20 - // safir.remove(args.keys.to_owned()); 23 + safir.remove(keys.to_owned()).await?; 21 24 } 22 25 Commands::Alias { keys } => { 23 - // safir.custom_display("alias", args.keys.to_owned()); 26 + // TODO: Fix custom displays 27 + // safir.custom_display("alias", keys.to_owned()).await?; 24 28 } 25 29 Commands::Export { keys } => { 26 - // safir.custom_display("export", args.keys.to_owned()); 30 + // safir.custom_display("export", keys.to_owned()).await?; 27 31 } 28 32 Commands::List => { 29 - // safir.list(); 33 + safir.list().await?; 30 34 } 31 35 Commands::Clear => { 32 - // safir.clear(); 36 + safir.clear().await?; 33 37 } 34 38 Commands::Purge => { 35 - // safir.purge(); 39 + safir.purge().await?; 36 40 } 37 41 Commands::Mode { mode } => { 38 42 println!("Mode: {mode:?}");
+37 -39
src/store/file_store.rs
··· 1 - use crate::utils; 2 - use std::{collections::HashMap, fs, path::PathBuf}; 1 + use crate::{store::SafirStore, utils}; 2 + use anyhow::Result; 3 + use std::{collections::HashMap, path::PathBuf}; 3 4 4 5 pub struct KVStore { 5 6 pub path: PathBuf, 6 - pub file: PathBuf, 7 7 pub store: HashMap<String, String>, 8 8 } 9 9 10 10 impl KVStore { 11 - pub fn init_safir() -> Self { 12 - match dirs::home_dir() { 13 - Some(home) => { 14 - let working_dir = home.join(".safirstore"); 15 - fs::create_dir_all(&working_dir).expect("unable to create main directory"); 11 + pub fn load(ws: PathBuf) -> Self { 12 + let store_path = ws.join("safirstore.json"); 13 + let store = if store_path.exists() { 14 + utils::load_store(&store_path) 15 + } else { 16 + let store = HashMap::new(); 17 + utils::write_store(&store, &store_path); 18 + store 19 + }; 16 20 17 - let store_path = working_dir.join("safirstore.json"); 18 - let store = if store_path.exists() { 19 - utils::load_store(&store_path) 20 - } else { 21 - let store = HashMap::new(); 22 - utils::write_store(&store, &store_path); 23 - store 24 - }; 21 + Self { path: ws, store } 22 + } 25 23 26 - return Self { 27 - path: working_dir, 28 - file: store_path, 29 - store, 30 - }; 31 - } 32 - None => { 33 - eprintln!("unable to obtain home directory path!"); 34 - std::process::exit(-1); 24 + pub fn custom_display(&self, display_cmd: &str, keys: Vec<String>) { 25 + for key in keys.iter() { 26 + if let Some(value) = self.store.get(key) { 27 + println!("{display_cmd} {key}=\"{value}\""); 35 28 } 36 29 } 37 30 } 31 + } 38 32 39 - pub fn add(&mut self, key: String, value: String) { 33 + impl SafirStore for KVStore { 34 + async fn add(&mut self, key: String, value: String) -> Result<()> { 40 35 if let Some(v) = self.store.get(&key) { 41 36 let confirm_msg = format!("Key {key} already exists ({v}), Replace?"); 42 37 if utils::confirm_entry(&confirm_msg) { ··· 45 40 } else { 46 41 self.store.insert(key, value); 47 42 } 43 + 44 + Ok(()) 48 45 } 49 46 50 - pub fn get(&self, keys: Vec<String>) { 47 + async fn get(&self, keys: Vec<String>) -> Result<()> { 51 48 for key in keys.iter() { 52 49 if let Some(value) = self.store.get(key) { 53 50 utils::display_kv(key, value); 54 51 } 55 52 } 53 + 54 + Ok(()) 56 55 } 57 56 58 - pub fn list(&self) { 57 + async fn list(&self) -> Result<()> { 59 58 for (key, value) in self.store.iter() { 60 59 utils::display_kv(key, value); 61 60 } 61 + 62 + Ok(()) 62 63 } 63 64 64 - pub fn remove(&mut self, keys: Vec<String>) { 65 + async fn remove(&mut self, keys: Vec<String>) -> Result<()> { 65 66 for key in keys.iter() { 66 67 if let Some(v) = self.store.get(key) { 67 68 let confirm_msg = format!("Remove {key} ({v}) from the store?"); ··· 70 71 } 71 72 } 72 73 } 73 - } 74 74 75 - pub fn custom_display(&self, display_cmd: &str, keys: Vec<String>) { 76 - for key in keys.iter() { 77 - if let Some(value) = self.store.get(key) { 78 - println!("{display_cmd} {key}=\"{value}\""); 79 - } 80 - } 75 + Ok(()) 81 76 } 82 - 83 - pub fn clear(&mut self) { 77 + async fn clear(&mut self) -> Result<()> { 84 78 let confirm_msg = "Are you sure you want to clear the cache of all contents?"; 85 79 if utils::confirm_entry(&confirm_msg) { 86 80 self.store.clear(); 87 81 } 82 + 83 + Ok(()) 88 84 } 89 85 90 - pub fn purge(&mut self) { 86 + async fn purge(&mut self) -> Result<()> { 91 87 let confirm_msg = 92 88 "Are you sure you want to remove the .safirstore directory and ALL contents?"; 93 89 if utils::confirm_entry(&confirm_msg) { 94 90 utils::purge_directory(self.path.clone()); 95 91 std::process::exit(0); 96 92 } 93 + 94 + Ok(()) 97 95 } 98 96 }
+23 -11
src/store/mod.rs
··· 2 2 3 3 use crate::utils; 4 4 5 - use std::path::Path; 5 + use std::path::{Path, PathBuf}; 6 6 7 7 use anyhow::{Context, Result}; 8 8 use clap::ValueEnum; 9 + use file_store::KVStore; 9 10 use serde::{Deserialize, Serialize}; 10 11 11 12 pub trait SafirStore { 12 - async fn add(key: String, value: String) -> Result<()>; 13 - async fn get(keys: Vec<String>) -> Result<Vec<String>>; 14 - async fn list(keys: Vec<String>) -> Result<Vec<String>>; 15 - async fn remove(keys: Vec<String>) -> Result<()>; 16 - async fn clear() -> Result<()>; 17 - async fn purge() -> Result<()>; 13 + async fn add(&mut self, key: String, value: String) -> Result<()>; 14 + async fn get(&self, keys: Vec<String>) -> Result<()>; 15 + async fn list(&self) -> Result<()>; 16 + async fn remove(&mut self, keys: Vec<String>) -> Result<()>; 17 + async fn clear(&mut self) -> Result<()>; 18 + async fn purge(&mut self) -> Result<()>; 18 19 } 19 20 20 21 #[derive(ValueEnum, Default, Debug, Copy, PartialEq, Eq, Clone, Serialize, Deserialize)] ··· 31 32 } 32 33 33 34 impl SafirConfig { 34 - pub fn load(fp: impl AsRef<Path>) -> Result<Self> { 35 - let fp = fp.as_ref().join("safirstore.cfg"); 35 + pub fn load(workdir: impl AsRef<Path>) -> Result<Self> { 36 + let fp = workdir.as_ref().join("safirstore.cfg"); 37 + if !fp.exists() { 38 + return Ok(Self::default()); 39 + } 36 40 let contents = std::fs::read_to_string(&fp).context("loading safir config")?; 37 41 serde_json::from_str(&contents).context("deserializing safir config") 38 42 } 39 43 } 40 44 41 - pub fn init_safir() { 45 + pub fn init_safir() -> Result<impl SafirStore> { 42 46 // 1. Create .safirstore dir 43 - let _ws = utils::create_safir_workspace(); 47 + let ws = utils::create_safir_workspace(); 44 48 45 49 // 2. Load / Create config 50 + let cfg = SafirConfig::load(&ws).expect("can't load safir config"); 51 + 46 52 // 3. Load / Create stores 53 + let store = match cfg.mode { 54 + SafirMode::File => KVStore::load(ws), 55 + _ => unimplemented!("not yet on db work"), 56 + }; 57 + 58 + Ok(store) 47 59 }
+14 -1
src/utils.rs
··· 5 5 path::{Path, PathBuf}, 6 6 }; 7 7 8 + /// Debug flag for testing without affecting my existing store 9 + const DEBUG: bool = true; 10 + 8 11 /// Confirmation dialog for important calls 9 12 pub fn confirm_entry(msg: &str) -> bool { 10 13 let mut answer = String::new(); ··· 54 57 55 58 /// Create the .safirstore directory in the user HOME 56 59 pub fn create_safir_workspace() -> PathBuf { 60 + let store_dir = if DEBUG { 61 + ".safirstore_debug" 62 + } else { 63 + ".safirstore" 64 + }; 65 + 66 + if DEBUG { 67 + println!("DEBUG: Creating safir store at debug location"); 68 + } 69 + 57 70 match dirs::home_dir() { 58 71 Some(home) => { 59 - let working_dir = home.join(".safirstore"); 72 + let working_dir = home.join(store_dir); 60 73 fs::create_dir_all(&working_dir).expect("unable to create main directory"); 61 74 62 75 working_dir