use std::path::{Path, PathBuf};
use std::sync::Arc;
use async_trait::async_trait;
use redb::{Database, ReadableDatabase, ReadableTable, TableDefinition};
use crate::{MessageStore, StoreError};
const SENDER_SEQ: TableDefinition<&str, u64> = TableDefinition::new("sender_seq");
const TARGET_SEQ: TableDefinition<&str, u64> = TableDefinition::new("target_seq");
const MESSAGES: TableDefinition<(&str, u64), &[u8]> = TableDefinition::new("messages");
const CREATION_TIME: TableDefinition<&str, i64> = TableDefinition::new("creation_time");
fn now_unix() -> i64 {
time::OffsetDateTime::now_utc().unix_timestamp()
}
fn backend<E: std::fmt::Display>(e: E) -> StoreError {
StoreError::Backend(e.to_string())
}
fn join_err(e: tokio::task::JoinError) -> StoreError {
StoreError::Backend(format!("redb task panicked: {e}"))
}
#[derive(Debug, Clone)]
pub struct RedbStoreConfig {
pub path: PathBuf,
pub session_id: String,
}
impl RedbStoreConfig {
pub fn new(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
session_id: "default".to_owned(),
}
}
}
pub struct RedbStore {
db: Arc<Database>,
session_id: String,
}
impl RedbStore {
pub async fn connect(path: &Path) -> Result<Self, StoreError> {
Self::connect_with_config(RedbStoreConfig::new(path)).await
}
pub async fn connect_with_config(config: RedbStoreConfig) -> Result<Self, StoreError> {
let path = config.path;
let session_id = config.session_id;
let session_id_for_init = session_id.clone();
let db = tokio::task::spawn_blocking(move || -> Result<Database, StoreError> {
let db = Database::create(&path).map_err(backend)?;
let txn = db.begin_write().map_err(backend)?;
txn.open_table(SENDER_SEQ).map_err(backend)?;
txn.open_table(TARGET_SEQ).map_err(backend)?;
txn.open_table(MESSAGES).map_err(backend)?;
{
let mut creation = txn.open_table(CREATION_TIME).map_err(backend)?;
if creation
.get(session_id_for_init.as_str())
.map_err(backend)?
.is_none()
{
creation
.insert(session_id_for_init.as_str(), now_unix())
.map_err(backend)?;
}
}
txn.commit().map_err(backend)?;
Ok(db)
})
.await
.map_err(join_err)??;
Ok(Self {
db: Arc::new(db),
session_id,
})
}
pub fn with_session_id(&self, session_id: impl Into<String>) -> Self {
Self {
db: self.db.clone(),
session_id: session_id.into(),
}
}
async fn get_seq(&self, table: TableDefinition<'static, &str, u64>) -> Result<u64, StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
tokio::task::spawn_blocking(move || -> Result<u64, StoreError> {
let txn = db.begin_read().map_err(backend)?;
let t = txn.open_table(table).map_err(backend)?;
Ok(t.get(session_id.as_str())
.map_err(backend)?
.map(|guard| guard.value())
.unwrap_or(1))
})
.await
.map_err(join_err)?
}
async fn set_seq(
&self,
table: TableDefinition<'static, &str, u64>,
seq: u64,
) -> Result<(), StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
tokio::task::spawn_blocking(move || -> Result<(), StoreError> {
let txn = db.begin_write().map_err(backend)?;
{
let mut t = txn.open_table(table).map_err(backend)?;
t.insert(session_id.as_str(), seq).map_err(backend)?;
}
txn.commit().map_err(backend)?;
Ok(())
})
.await
.map_err(join_err)?
}
}
#[async_trait]
impl MessageStore for RedbStore {
async fn next_sender_seq(&self) -> Result<u64, StoreError> {
self.get_seq(SENDER_SEQ).await
}
async fn next_target_seq(&self) -> Result<u64, StoreError> {
self.get_seq(TARGET_SEQ).await
}
async fn set_next_sender_seq(&self, seq: u64) -> Result<(), StoreError> {
self.set_seq(SENDER_SEQ, seq).await
}
async fn set_next_target_seq(&self, seq: u64) -> Result<(), StoreError> {
self.set_seq(TARGET_SEQ, seq).await
}
async fn save(&self, seq: u64, message: &[u8]) -> Result<(), StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
let message = message.to_vec();
tokio::task::spawn_blocking(move || -> Result<(), StoreError> {
let txn = db.begin_write().map_err(backend)?;
{
let mut t = txn.open_table(MESSAGES).map_err(backend)?;
t.insert((session_id.as_str(), seq), message.as_slice())
.map_err(backend)?;
}
txn.commit().map_err(backend)?;
Ok(())
})
.await
.map_err(join_err)?
}
async fn get(&self, begin: u64, end: u64) -> Result<Vec<(u64, Vec<u8>)>, StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
tokio::task::spawn_blocking(move || -> Result<Vec<(u64, Vec<u8>)>, StoreError> {
let txn = db.begin_read().map_err(backend)?;
let t = txn.open_table(MESSAGES).map_err(backend)?;
let mut out = Vec::new();
let range = t
.range((session_id.as_str(), begin)..=(session_id.as_str(), end))
.map_err(backend)?;
for entry in range {
let (key, value) = entry.map_err(backend)?;
let (_, seq) = key.value();
out.push((seq, value.value().to_vec()));
}
Ok(out)
})
.await
.map_err(join_err)?
}
async fn reset(&self) -> Result<(), StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
tokio::task::spawn_blocking(move || -> Result<(), StoreError> {
let txn = db.begin_write().map_err(backend)?;
{
let mut messages = txn.open_table(MESSAGES).map_err(backend)?;
messages
.retain_in(
(session_id.as_str(), 0)..=(session_id.as_str(), u64::MAX),
|_, _| false,
)
.map_err(backend)?;
let mut sender = txn.open_table(SENDER_SEQ).map_err(backend)?;
sender.insert(session_id.as_str(), 1u64).map_err(backend)?;
let mut target = txn.open_table(TARGET_SEQ).map_err(backend)?;
target.insert(session_id.as_str(), 1u64).map_err(backend)?;
let mut creation = txn.open_table(CREATION_TIME).map_err(backend)?;
creation
.insert(session_id.as_str(), now_unix())
.map_err(backend)?;
}
txn.commit().map_err(backend)?;
Ok(())
})
.await
.map_err(join_err)?
}
async fn creation_time(&self) -> Result<Option<time::OffsetDateTime>, StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
tokio::task::spawn_blocking(
move || -> Result<Option<time::OffsetDateTime>, StoreError> {
let txn = db.begin_read().map_err(backend)?;
let t = txn.open_table(CREATION_TIME).map_err(backend)?;
let secs = t
.get(session_id.as_str())
.map_err(backend)?
.map(|guard| guard.value());
Ok(secs.and_then(|s| time::OffsetDateTime::from_unix_timestamp(s).ok()))
},
)
.await
.map_err(join_err)?
}
async fn save_and_advance_sender(&self, seq: u64, message: &[u8]) -> Result<(), StoreError> {
let db = self.db.clone();
let session_id = self.session_id.clone();
let message = message.to_vec();
tokio::task::spawn_blocking(move || -> Result<(), StoreError> {
let txn = db.begin_write().map_err(backend)?;
{
let mut messages = txn.open_table(MESSAGES).map_err(backend)?;
messages
.insert((session_id.as_str(), seq), message.as_slice())
.map_err(backend)?;
let mut sender = txn.open_table(SENDER_SEQ).map_err(backend)?;
sender
.insert(session_id.as_str(), seq + 1)
.map_err(backend)?;
}
txn.commit().map_err(backend)?;
Ok(())
})
.await
.map_err(join_err)?
}
}