ktd/state/
store.rs

1//! Exposes methods to set and get for a given table and key,
2//!  also supports listing keys in a table.
3
4use bytes::Bytes;
5use clap::Parser;
6use serde::{Deserialize, Serialize};
7use std::{os::unix::ffi::OsStringExt, path::PathBuf, sync::OnceLock};
8use tokio::fs::File;
9use tokio::io::AsyncWriteExt;
10use uuid::Uuid;
11
12/// Where the `sqlite` database should be stored.
13/// Initialized by [init].
14static STORE_PATH: OnceLock<PathBuf> = OnceLock::new();
15
16fn store_path() -> &'static PathBuf {
17    STORE_PATH.get().expect("Store path not initialized")
18}
19
20/// Storage configuration.
21#[derive(Debug, Parser)]
22pub struct StoreCli {
23    #[arg(long, default_value = "store.db")]
24    store_path: PathBuf,
25}
26
27/// Initializes the storage global state as specified in the configuration.
28///
29/// Creates the sqlite table if it does not exist.
30pub fn init(cli: StoreCli) {
31    log::info!("Initialize store at {}", cli.store_path.display());
32    std::fs::create_dir_all(cli.store_path.join("tmp")).expect("Failed to create store directory");
33    STORE_PATH
34        .set(cli.store_path)
35        .expect("Store path already initialized");
36}
37
38/// Errors that can be returned by this module's methods.
39#[derive(thiserror::Error, Serialize, Deserialize, Debug, Clone)]
40pub enum Error {
41    #[error("I/O error: {0}")]
42    Io(String),
43    #[error("Hex decoding error: {0}")]
44    Hex(String),
45}
46
47impl From<std::io::Error> for Error {
48    fn from(e: std::io::Error) -> Self {
49        Error::Io(e.to_string())
50    }
51}
52
53impl From<hex::FromHexError> for Error {
54    fn from(e: hex::FromHexError) -> Self {
55        Error::Hex(e.to_string())
56    }
57}
58
59pub async fn item_get(table: Uuid, key: &[u8]) -> Result<Option<Bytes>, Error> {
60    log::debug!("{table} get");
61    let path = store_path().join(table.to_string()).join(hex::encode(key));
62
63    match tokio::fs::read(&path).await {
64        Ok(content) => Ok(Some(Bytes::from(content))),
65        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
66        Err(e) => Err(e.into()),
67    }
68}
69
70#[cfg(target_vendor = "apple")]
71fn fsync(f: &File) -> std::io::Result<()> {
72    use std::os::fd::{AsFd, AsRawFd};
73
74    if unsafe { libc::fsync(f.as_fd().as_raw_fd()) } == -1 {
75        return Err(std::io::Error::last_os_error());
76    }
77
78    Ok(())
79}
80
81pub async fn item_set(table: Uuid, key: &[u8], value: &[u8]) -> Result<(), Error> {
82    log::debug!("{table} set");
83    let tmp_path = store_path().join("tmp").join(Uuid::now_v7().to_string());
84    let mut file = File::create_new(&tmp_path).await?;
85    file.write_all(value).await?;
86
87    #[cfg(not(target_vendor = "apple"))]
88    file.sync_all().await?;
89    #[cfg(target_vendor = "apple")]
90    fsync(&file)?;
91
92    let path = store_path().join(table.to_string());
93    std::fs::create_dir_all(&path)?;
94    let path = path.join(hex::encode(key));
95    tokio::fs::rename(&tmp_path, &path).await?;
96    Ok(())
97}
98
99pub type KeyValue = (Vec<u8>, Vec<u8>);
100
101pub async fn item_list(table: Uuid) -> Result<Vec<KeyValue>, Error> {
102    log::debug!("{table} list");
103    let path = store_path().join(table.to_string());
104    let mut items = Vec::new();
105    let mut iter = match tokio::fs::read_dir(path).await {
106        Ok(iter) => iter,
107        Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
108            return Ok(items);
109        }
110        Err(e) => return Err(e.into()),
111    };
112    while let Some(entry) = iter.next_entry().await? {
113        let key = hex::decode(entry.file_name().into_vec())?;
114        let value = item_get(table, &key).await?.expect("Item should exist");
115        items.push((key, value.to_vec()));
116    }
117    Ok(items)
118}
119
120pub async fn table_delete(table: Uuid) -> Result<(), Error> {
121    log::debug!("{table} destroy");
122    let path = store_path().join(table.to_string());
123    match tokio::fs::remove_dir_all(path).await {
124        Ok(()) => Ok(()),
125        Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
126        Err(e) => Err(e.into()),
127    }
128}