SQLite-backed Key / Value Store
1
fork

Configure Feed

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

Merge pull request #11 from Tyrannican/simpler-safir-core

Simpler safir core

authored by

Graham Keenan and committed by
GitHub
7a94c5b9 f05f34c8

+262 -94
+21
Cargo.lock
··· 66 66 ] 67 67 68 68 [[package]] 69 + name = "anyhow" 70 + version = "1.0.75" 71 + source = "registry+https://github.com/rust-lang/crates.io-index" 72 + checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" 73 + 74 + [[package]] 75 + name = "async-trait" 76 + version = "0.1.73" 77 + source = "registry+https://github.com/rust-lang/crates.io-index" 78 + checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" 79 + dependencies = [ 80 + "proc-macro2", 81 + "quote", 82 + "syn 2.0.29", 83 + ] 84 + 85 + [[package]] 69 86 name = "autocfg" 70 87 version = "1.1.0" 71 88 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 678 695 name = "safir" 679 696 version = "0.7.0" 680 697 dependencies = [ 698 + "anyhow", 681 699 "clap", 682 700 "safir-core", 683 701 "tokio", ··· 687 705 name = "safir-core" 688 706 version = "0.1.0" 689 707 dependencies = [ 708 + "anyhow", 709 + "async-trait", 690 710 "colored", 691 711 "dirs", 692 712 "psutil", ··· 702 722 name = "safir-mem" 703 723 version = "0.2.0" 704 724 dependencies = [ 725 + "anyhow", 705 726 "clap", 706 727 "safir-core", 707 728 "tokio",
+3 -1
safir-core/Cargo.toml
··· 1 1 [package] 2 2 name = "safir-core" 3 - version = "0.1.0" 3 + version = "0.2.0" 4 4 edition = "2021" 5 5 description = "Internal lib used to build Safir and Safir-mem" 6 6 authors = ["Graham Keenan graham.keenan@outlook.com"] ··· 12 12 categories = ["command-line-interface"] 13 13 14 14 [dependencies] 15 + anyhow = "1.0.75" 16 + async-trait = "0.1.73" 15 17 colored = "2.0.4" 16 18 dirs = "5.0.1" 17 19 psutil = "3.2.2"
+10 -3
safir-core/src/config.rs
··· 1 1 use serde::{Deserialize, Serialize}; 2 - use std::{io::Result, path::Path}; 2 + use std::{ 3 + io::Result, 4 + path::{Path, PathBuf}, 5 + }; 3 6 use tokio::{fs, io::AsyncWriteExt}; 4 7 5 8 #[derive(Default, Serialize, Deserialize, Debug)] 6 9 pub struct SafirConfig { 10 + #[serde(skip)] 11 + pub root_path: PathBuf, 12 + #[serde(skip)] 13 + pub config_path: PathBuf, 7 14 pub memcache_pid: Option<u32>, 8 15 } 9 16 ··· 17 24 Ok(serde_json::from_str(&cfg).expect("unable to deserialize safir config")) 18 25 } 19 26 20 - pub async fn write(&self, cfg_path: impl AsRef<Path>) -> Result<()> { 27 + pub async fn write(&self) -> Result<()> { 21 28 let data = serde_json::to_string_pretty(&self).expect("unable to serialize safir config"); 22 - let mut file = fs::File::create(cfg_path).await?; 29 + let mut file = fs::File::create(&self.config_path).await?; 23 30 file.write_all(data.as_bytes()).await?; 24 31 25 32 Ok(())
+45 -34
safir-core/src/disk.rs
··· 8 8 //! 9 9 //! Safir gives you the option to add / get / remove items from the store 10 10 //! and to clear / purge when you're finished with them. 11 - use std::{io::Result, path::Path}; 11 + use anyhow::Result; 12 12 13 + use async_trait::async_trait; 13 14 use colored::*; 14 15 16 + use crate::config::SafirConfig; 15 17 use crate::utils::{confirm_entry, print_header, print_output}; 18 + use crate::SafirEngine; 16 19 use rubin::store::persistence::PersistentStore; 17 20 18 21 /// Safir Store (fancy wrapper around reading and writing to a JSON file) 19 - pub struct Safir { 22 + pub struct SafirStore { 20 23 pub store: PersistentStore, 21 24 } 22 25 23 - impl Safir { 24 - /// Initialises the Safirstore if not already initialised 25 - pub async fn init(store_loc: &Path) -> Result<Self> { 26 - let store_path = store_loc.join("safirstore.json"); 27 - let mut ps = if store_path.exists() { 28 - PersistentStore::from_existing(store_path).await? 29 - } else { 30 - PersistentStore::new(store_path).await? 31 - }; 32 - 33 - ps.set_write_on_update(true); 34 - Ok(Self { store: ps }) 35 - } 36 - 26 + #[async_trait] 27 + impl SafirEngine for SafirStore { 37 28 /// Add an entry to the store and write it out to disk 38 - pub async fn add_entry(&mut self, key: String, value: String) -> Result<()> { 39 - self.store.insert_string(&key, &value).await 29 + async fn add_entry(&mut self, key: String, value: String) -> Result<()> { 30 + self.store.insert_string(&key, &value).await?; 31 + Ok(()) 40 32 } 41 33 42 34 /// Get an entry form the store by loading it from disk and displaying it 43 - pub fn get_entry(&self, key: String) -> Result<()> { 35 + async fn get_entry(&self, key: String) -> Result<()> { 44 36 print_header(); 45 37 let output = if let Ok(val) = self.store.get_string(&key) { 46 38 format!("{}: \"{}\"", key.bold().yellow(), val) ··· 53 45 Ok(()) 54 46 } 55 47 56 - /// Display all key/values in the store 57 - pub fn display_all(&self) { 58 - print_header(); 59 - let mut output: String; 60 - let strings = self.store.get_string_store_ref(); 61 - for (key, value) in strings.iter() { 62 - output = format!("{}: \"{}\"", key.bold().yellow(), value); 63 - print_output(&output); 64 - } 65 - } 66 - 67 48 /// Remove a key/value pair from the store and update onto disk 68 - pub async fn remove_entry(&mut self, keys: Vec<String>) -> Result<()> { 49 + async fn remove_entry(&mut self, keys: Vec<String>) -> Result<()> { 69 50 for key in &keys { 70 51 self.store.remove_string(key).await?; 71 52 } ··· 77 58 /// 78 59 /// E.g. With a prefix of `alias` this will display the command as 79 60 /// `alias {key}="{value}"` with {key} / {value} replaced with their values from the store 80 - pub fn set_commands(&self, prefix: &str, keys: &Vec<String>) { 61 + async fn set_commands(&mut self, prefix: &str, keys: &Vec<String>) { 81 62 print_header(); 82 63 let prefix = match prefix { 83 64 "alias" => "alias".bold().green(), ··· 93 74 } 94 75 95 76 /// Clear the the contents of the store and update onto disk 96 - pub async fn clear_entries(&mut self) -> Result<()> { 77 + async fn clear_entries(&mut self) -> Result<()> { 97 78 if confirm_entry("Are you sure you want to clear the store?") { 98 79 self.store.clear_strings().await?; 99 80 } ··· 101 82 Ok(()) 102 83 } 103 84 85 + fn to_type(&self) -> &dyn std::any::Any { 86 + self 87 + } 88 + } 89 + 90 + impl SafirStore { 91 + /// Initialises the Safirstore if not already initialised 92 + pub async fn new(config: &SafirConfig) -> Result<Self> { 93 + let store_path = config.root_path.join("safirstore.json"); 94 + let mut ps = if store_path.exists() { 95 + PersistentStore::from_existing(store_path).await? 96 + } else { 97 + PersistentStore::new(store_path).await? 98 + }; 99 + 100 + ps.set_write_on_update(true); 101 + Ok(Self { store: ps }) 102 + } 103 + 104 + /// Display all key/values in the store 105 + pub fn display_all(&self) { 106 + print_header(); 107 + let mut output: String; 108 + let strings = self.store.get_string_store_ref(); 109 + for (key, value) in strings.iter() { 110 + output = format!("{}: \"{}\"", key.bold().yellow(), value); 111 + print_output(&output); 112 + } 113 + } 114 + 104 115 /// Remove the store directory and all contents 105 - pub fn purge(&mut self) { 116 + pub fn purge(&self) { 106 117 if confirm_entry("Are you sure you want to purge Safirstore?") { 107 118 std::fs::remove_dir_all(&self.store.path) 108 119 .expect("unable to remove safirstore directory");
+89
safir-core/src/lib.rs
··· 2 2 pub mod disk; 3 3 pub mod mem; 4 4 pub mod utils; 5 + 6 + use anyhow::Result; 7 + use async_trait::async_trait; 8 + use std::any::Any; 9 + 10 + use config::SafirConfig; 11 + use disk::SafirStore; 12 + use mem::SafirMemcache; 13 + 14 + #[derive(Debug, PartialEq)] 15 + pub enum SafirEngineType { 16 + Store, 17 + Memcache, 18 + } 19 + 20 + #[async_trait] 21 + pub trait SafirEngine { 22 + async fn add_entry(&mut self, key: String, value: String) -> Result<()>; 23 + async fn get_entry(&self, key: String) -> Result<()>; 24 + async fn remove_entry(&mut self, keys: Vec<String>) -> Result<()>; 25 + async fn clear_entries(&mut self) -> Result<()>; 26 + async fn set_commands(&mut self, prefix: &str, keys: &Vec<String>); 27 + fn to_type(&self) -> &dyn Any; 28 + } 29 + 30 + pub struct Safir { 31 + pub engine: Box<dyn SafirEngine>, 32 + pub config: SafirConfig, 33 + } 34 + 35 + impl Safir { 36 + pub async fn new(engine_type: SafirEngineType) -> Result<Self> { 37 + let config = utils::init().await?; 38 + match engine_type { 39 + SafirEngineType::Store => { 40 + let e = crate::disk::SafirStore::new(&config).await?; 41 + Ok(Self { 42 + engine: Box::new(e), 43 + config, 44 + }) 45 + } 46 + SafirEngineType::Memcache => { 47 + let e = crate::mem::SafirMemcache::new(&config).await?; 48 + Ok(Self { 49 + engine: Box::new(e), 50 + config, 51 + }) 52 + } 53 + } 54 + } 55 + 56 + pub async fn add_entry(&mut self, key: String, value: String) -> Result<()> { 57 + self.engine.add_entry(key, value).await?; 58 + Ok(()) 59 + } 60 + 61 + pub async fn get_entry(&self, key: String) -> Result<()> { 62 + self.engine.get_entry(key).await?; 63 + Ok(()) 64 + } 65 + 66 + pub async fn remove_entry(&mut self, keys: Vec<String>) -> Result<()> { 67 + self.engine.remove_entry(keys).await?; 68 + Ok(()) 69 + } 70 + 71 + pub async fn clear_entries(&mut self) -> Result<()> { 72 + self.engine.clear_entries().await?; 73 + Ok(()) 74 + } 75 + 76 + pub async fn set_commands(&mut self, prefix: &str, keys: &Vec<String>) { 77 + self.engine.set_commands(prefix, keys).await; 78 + } 79 + 80 + pub fn as_safir_store(&self) -> &SafirStore { 81 + self.engine 82 + .to_type() 83 + .downcast_ref::<SafirStore>() 84 + .expect("unable to get Safir store type") 85 + } 86 + 87 + pub fn as_safir_memcache(&self) -> &SafirMemcache { 88 + self.engine 89 + .to_type() 90 + .downcast_ref::<SafirMemcache>() 91 + .expect("unable to get Safir memcache type") 92 + } 93 + }
+27 -17
safir-core/src/mem.rs
··· 1 - use std::io::Result; 1 + use anyhow::Result; 2 2 3 + use async_trait::async_trait; 3 4 use colored::*; 4 5 5 - use crate::utils::{confirm_entry, print_header, print_output}; 6 + use crate::config::SafirConfig; 7 + use crate::utils::{self, confirm_entry, print_header, print_output}; 8 + use crate::SafirEngine; 6 9 use rubin::net::client::RubinClient; 7 10 8 11 fn safir_offline() { ··· 16 19 client: RubinClient, 17 20 } 18 21 19 - impl SafirMemcache { 20 - pub fn new(is_online: bool) -> Self { 21 - Self { 22 - is_online, 23 - client: RubinClient::new("127.0.0.1", 9876), 24 - } 25 - } 26 - 27 - pub async fn add_entry(&self, key: &str, value: &str) -> Result<()> { 22 + #[async_trait] 23 + impl SafirEngine for SafirMemcache { 24 + async fn add_entry(&mut self, key: String, value: String) -> Result<()> { 28 25 if !self.is_online { 29 26 safir_offline(); 30 27 return Ok(()); 31 28 } 32 29 33 - self.client.insert_string(key, value).await?; 30 + self.client.insert_string(&key, &value).await?; 34 31 Ok(()) 35 32 } 36 33 37 - pub async fn get_string(&self, key: &str) -> Result<()> { 34 + async fn get_entry(&self, key: String) -> Result<()> { 38 35 if !self.is_online { 39 36 safir_offline(); 40 37 return Ok(()); 41 38 } 42 39 43 40 print_header(); 44 - let output = if let Ok(val) = self.client.get_string(key).await { 41 + let output = if let Ok(val) = self.client.get_string(&key).await { 45 42 format!("{}: \"{}\"", key.bold().yellow(), val) 46 43 } else { 47 44 format!("{}: ", key.bold().yellow()) ··· 52 49 Ok(()) 53 50 } 54 51 55 - pub async fn remove_entry(&self, keys: Vec<String>) -> Result<()> { 52 + async fn remove_entry(&mut self, keys: Vec<String>) -> Result<()> { 56 53 if !self.is_online { 57 54 safir_offline(); 58 55 return Ok(()); ··· 65 62 Ok(()) 66 63 } 67 64 68 - pub async fn set_commands(&self, prefix: &str, keys: &Vec<String>) { 65 + async fn set_commands(&mut self, prefix: &str, keys: &Vec<String>) { 69 66 if !self.is_online { 70 67 safir_offline(); 71 68 return; ··· 85 82 } 86 83 } 87 84 88 - pub async fn clear_entries(&self) -> Result<()> { 85 + async fn clear_entries(&mut self) -> Result<()> { 89 86 if !self.is_online { 90 87 safir_offline(); 91 88 return Ok(()); ··· 96 93 } 97 94 98 95 Ok(()) 96 + } 97 + 98 + fn to_type(&self) -> &dyn std::any::Any { 99 + self 100 + } 101 + } 102 + 103 + impl SafirMemcache { 104 + pub async fn new(cfg: &SafirConfig) -> Result<Self> { 105 + Ok(Self { 106 + is_online: utils::is_safir_running(cfg.memcache_pid), 107 + client: RubinClient::new("127.0.0.1", 9876), 108 + }) 99 109 } 100 110 101 111 pub async fn dump_store(&self, path: &str) -> Result<()> {
+20 -11
safir-core/src/utils.rs
··· 1 - use std::io::{self, Write}; 1 + use anyhow::Result; 2 + use std::io::Write; 2 3 use std::path::{Path, PathBuf}; 3 4 4 5 use crate::config::SafirConfig; ··· 7 8 use sysinfo::{Pid, System, SystemExt}; 8 9 use tokio::fs; 9 10 11 + pub async fn init() -> Result<SafirConfig> { 12 + let store_dir = create_safir_directory().await?; 13 + let cfg = load_safir_config(&store_dir).await?; 14 + Ok(cfg) 15 + } 16 + 10 17 pub fn check_rubin_installed() -> bool { 11 18 if which::which("rubin").is_ok() { 12 19 return true; ··· 27 34 28 35 pub fn is_safir_running(pid: Option<u32>) -> bool { 29 36 match pid { 30 - Some(pid) => { 31 - return check_process_running(pid); 32 - } 37 + Some(pid) => check_process_running(pid), 33 38 None => false, 34 39 } 35 40 } ··· 38 43 path.as_ref().exists() 39 44 } 40 45 41 - pub async fn create_safir_directory() -> io::Result<PathBuf> { 46 + pub async fn create_safir_directory() -> Result<PathBuf> { 42 47 let home_dir = dirs::home_dir().unwrap(); 43 48 let store_path = home_dir.join(".safirstore"); 44 49 fs::create_dir_all(&store_path).await?; ··· 47 52 } 48 53 49 54 #[cfg(target_family = "unix")] 50 - pub async fn kill_process(pid: u32) -> io::Result<()> { 55 + pub async fn kill_process(pid: u32) -> Result<()> { 51 56 if let Ok(process) = psutil::process::Process::new(pid) { 52 57 if let Err(err) = process.kill() { 53 58 eprintln!("failed to kill process: {}", err); ··· 95 100 .expect("unable to get input from user"); 96 101 97 102 let answer = answer.trim().to_lowercase(); 98 - if answer == "y" { 103 + if answer == "y" || answer == "yes" { 99 104 return true; 100 105 } 101 106 102 107 false 103 108 } 104 109 105 - pub async fn load_safir_config(safir_cfg: impl AsRef<Path>) -> io::Result<SafirConfig> { 106 - let mut cfg = if path_exists(&safir_cfg).await { 107 - SafirConfig::load(&safir_cfg).await? 110 + pub async fn load_safir_config(store_dir: impl AsRef<Path>) -> Result<SafirConfig> { 111 + let cfg_path = &store_dir.as_ref().join("safir.cfg"); 112 + let mut cfg = if path_exists(&cfg_path).await { 113 + SafirConfig::load(&cfg_path).await? 108 114 } else { 109 115 SafirConfig::new() 110 116 }; 111 117 118 + cfg.root_path = store_dir.as_ref().to_owned(); 119 + cfg.config_path = cfg_path.to_owned(); 120 + 112 121 // Used in cases where the process has ended ungracefully and the config hasnt been updated 113 122 if let Some(pid) = cfg.memcache_pid { 114 123 if !check_process_running(pid) { 115 124 cfg = SafirConfig::new(); 116 - cfg.write(&safir_cfg).await?; 125 + cfg.write().await?; 117 126 } 118 127 } 119 128
+4
safir-mem/CHANGELOG.md
··· 2 2 3 3 Documenting changes between versions 4 4 5 + ## v0.2.1 6 + 7 + Changes to the internal code structure, no changes to operation 8 + 5 9 ## v0.2.0 6 10 7 11 Moved the core of the control to a new internal library called `safir-core`
+2 -1
safir-mem/Cargo.toml
··· 1 1 [package] 2 2 name = "safir-mem" 3 - version = "0.2.0" 3 + version = "0.2.1" 4 4 edition = "2021" 5 5 authors = ["Graham Keenan graham.keenan@outlook.com"] 6 6 license = "MIT OR Apache-2.0" ··· 17 17 clap = { version = "4.2.5" , features = ["derive"] } 18 18 tokio = { version = "1.28.2", features = ["full"] } 19 19 safir-core = { version = "0.1.0", path = "../safir-core" } 20 + anyhow = "1.0.75"
+20 -17
safir-mem/src/main.rs
··· 1 1 mod cli; 2 2 3 3 use cli::*; 4 - use safir_core::{mem::SafirMemcache, utils}; 4 + use safir_core::{utils, Safir, SafirEngineType}; 5 5 6 + use anyhow::Result; 6 7 use std::process::{Command, Stdio}; 7 8 8 9 #[tokio::main] 9 - async fn main() -> std::io::Result<()> { 10 + async fn main() -> Result<()> { 10 11 let cli = Cli::parse(); 11 - let store_dir = utils::create_safir_directory().await?; 12 - let safir_cfg = &store_dir.join("safir.cfg"); 13 - let mut cfg = utils::load_safir_config(&safir_cfg).await?; 14 - 15 - let safir_state = utils::is_safir_running(cfg.memcache_pid); 16 - let safir_mem = SafirMemcache::new(safir_state); 12 + let mut safir_mem = Safir::new(SafirEngineType::Memcache).await?; 17 13 18 14 match &cli.command { 19 - Commands::Add(args) => safir_mem.add_entry(&args.key, &args.value).await?, 15 + Commands::Add(args) => { 16 + safir_mem 17 + .add_entry(args.key.to_owned(), args.value.to_owned()) 18 + .await? 19 + } 20 20 Commands::Get(args) => { 21 21 if let Some(key) = &args.key { 22 - safir_mem.get_string(key).await?; 22 + safir_mem.get_entry(key.to_string()).await?; 23 23 } else { 24 24 utils::print_header(); 25 25 utils::print_output("A key is required for memcache GET command!"); ··· 45 45 return Ok(()); 46 46 } 47 47 48 - if let Some(pid) = cfg.memcache_pid { 48 + let config = &mut safir_mem.config; 49 + if let Some(pid) = config.memcache_pid { 49 50 println!( 50 51 "Safir memcache service is already running on 127.0.0.1:9876 - PID {}", 51 52 pid ··· 63 64 .expect("unable to spawn child process"); 64 65 65 66 let pid = child.id(); 66 - cfg.memcache_pid = Some(pid); 67 - cfg.write(&safir_cfg).await?; 67 + config.memcache_pid = Some(pid); 68 + config.write().await?; 68 69 println!( 69 70 "Safir memcache service started at 127.0.0.1:9876 - PID {}", 70 71 pid ··· 76 77 return Ok(()); 77 78 } 78 79 79 - let pid = match cfg.memcache_pid { 80 + let config = &mut safir_mem.config; 81 + let pid = match config.memcache_pid { 80 82 Some(pid) => pid, 81 83 None => { 82 84 println!("Safir memcache service does not seem to be running."); ··· 90 92 err 91 93 ); 92 94 } else { 93 - cfg.memcache_pid = None; 94 - cfg.write(&safir_cfg).await?; 95 + config.memcache_pid = None; 96 + config.write().await?; 95 97 println!("Stopping Safir memcache service!"); 96 98 } 97 99 } 98 100 Commands::Dump(args) => { 99 - if let Err(e) = safir_mem.dump_store(&args.path).await { 101 + let inner = safir_mem.as_safir_memcache(); 102 + if let Err(e) = inner.dump_store(&args.path).await { 100 103 eprintln!("unable to dump Safir memcache service: {}", e); 101 104 } 102 105 }
+8
safir/CHANGELOG.md
··· 2 2 3 3 Documenting changes between versions beginning from v0.3.0 4 4 5 + ## v0.7.1 6 + 7 + Changes to the internal code structure, no changes to operation 8 + 9 + ## v0.7.0 10 + 11 + Something happened here but not major... 12 + 5 13 ## v0.6.0 6 14 7 15 Removed the Safir Memcache and moved it to its own project.
+2 -1
safir/Cargo.toml
··· 1 1 [package] 2 2 name = "safir" 3 - version = "0.7.0" 3 + version = "0.7.1" 4 4 edition = "2021" 5 5 authors = ["Graham Keenan graham.keenan@outlook.com"] 6 6 license = "MIT OR Apache-2.0" ··· 17 17 clap = { version = "4.2.5" , features = ["derive"] } 18 18 tokio = { version = "1.28.2", features = ["full"] } 19 19 safir-core = { version = "0.1.0", path = "../safir-core" } 20 + anyhow = "1.0.75"
+11 -9
safir/src/main.rs
··· 1 1 mod cli; 2 2 3 + use anyhow::Result; 3 4 use cli::*; 4 5 5 - use safir_core::{disk::Safir, utils}; 6 + use safir_core::{Safir, SafirEngineType}; 6 7 7 8 #[tokio::main] 8 - async fn main() -> std::io::Result<()> { 9 + async fn main() -> Result<()> { 9 10 let cli = Cli::parse(); 10 - let store_dir = utils::create_safir_directory().await?; 11 - let mut safir = Safir::init(&store_dir).await?; 11 + let mut safir = Safir::new(SafirEngineType::Store).await?; 12 12 13 13 match &cli.command { 14 14 Commands::Add(args) => { ··· 18 18 } 19 19 Commands::Get(args) => { 20 20 if let Some(key) = &args.key { 21 - safir.get_entry(key.clone())?; 21 + safir.get_entry(key.clone()).await?; 22 22 } else { 23 - safir.display_all(); 23 + let inner = safir.as_safir_store(); 24 + inner.display_all(); 24 25 } 25 26 } 26 27 Commands::Rm(args) => { 27 28 safir.remove_entry(args.key.clone()).await?; 28 29 } 29 30 Commands::Alias(args) => { 30 - safir.set_commands("alias", &args.keys); 31 + safir.set_commands("alias", &args.keys).await; 31 32 } 32 33 Commands::Export(args) => { 33 - safir.set_commands("export", &args.keys); 34 + safir.set_commands("export", &args.keys).await; 34 35 } 35 36 Commands::Clear => { 36 37 safir.clear_entries().await?; 37 38 } 38 39 Commands::Purge => { 39 - safir.purge(); 40 + let inner = safir.as_safir_store(); 41 + inner.purge(); 40 42 } 41 43 } 42 44