use bc_components::ARID;
use bc_envelope::Envelope;
use bc_ur::prelude::*;
use super::{
Error as HybridError,
reference::{
create_reference_envelope, extract_reference_arid,
is_reference_envelope,
},
};
use crate::{
KvStore, Result, ipfs::IpfsKv, logging::verbose_println,
mainline::MainlineDhtKv,
};
pub struct HybridKv {
dht: MainlineDhtKv,
ipfs: IpfsKv,
dht_size_limit: usize,
}
impl HybridKv {
pub async fn new(ipfs_rpc_url: &str) -> Result<Self> {
let dht = MainlineDhtKv::new().await?;
let ipfs = IpfsKv::new(ipfs_rpc_url);
Ok(Self {
dht,
ipfs,
dht_size_limit: 1000, })
}
pub fn with_dht_size_limit(mut self, limit: usize) -> Self {
self.dht_size_limit = limit;
self
}
pub fn with_pin_content(mut self, pin: bool) -> Self {
self.ipfs = self.ipfs.with_pin_content(pin);
self
}
fn fits_in_dht(&self, envelope: &Envelope) -> bool {
let serialized = envelope.tagged_cbor().to_cbor_data();
serialized.len() <= self.dht_size_limit
}
async fn put_impl(
&self,
arid: &ARID,
envelope: &Envelope,
ttl_seconds: Option<u64>,
verbose: bool,
) -> Result<String> {
if self.fits_in_dht(envelope) {
if verbose {
verbose_println(&format!(
"Storing envelope in DHT (size ≤ {} bytes)",
self.dht_size_limit
));
}
self.dht.put(arid, envelope, ttl_seconds, verbose).await?;
Ok(format!("Stored in DHT at ARID: {}", arid.ur_string()))
} else {
if verbose {
verbose_println(
"Envelope too large for DHT, using IPFS indirection",
);
}
let reference_arid = ARID::new();
if verbose {
verbose_println(&format!(
"Storing actual envelope in IPFS with reference ARID: {}",
reference_arid.ur_string()
));
}
self.ipfs
.put(&reference_arid, envelope, ttl_seconds, verbose)
.await?;
let envelope_size = envelope.tagged_cbor().to_cbor_data().len();
let reference =
create_reference_envelope(&reference_arid, envelope_size);
if verbose {
verbose_println(
"Storing reference envelope in DHT at original ARID",
);
}
self.dht.put(arid, &reference, ttl_seconds, verbose).await?;
Ok(format!(
"Stored in IPFS (ref: {}) via DHT at ARID: {}",
reference_arid.ur_string(),
arid.ur_string()
))
}
}
async fn get_impl(
&self,
arid: &ARID,
timeout_seconds: Option<u64>,
verbose: bool,
) -> Result<Option<Envelope>> {
let dht_envelope = self.dht.get(arid, timeout_seconds, verbose).await?;
match dht_envelope {
None => Ok(None),
Some(envelope) => {
if is_reference_envelope(&envelope) {
if verbose {
verbose_println(
"Found reference envelope, fetching actual envelope from IPFS",
);
}
let reference_arid = extract_reference_arid(&envelope)?;
if verbose {
verbose_println(&format!(
"Reference ARID: {}",
reference_arid.ur_string()
));
}
let ipfs_envelope = self
.ipfs
.get(&reference_arid, timeout_seconds, verbose)
.await?;
match ipfs_envelope {
Some(actual) => {
if verbose {
verbose_println(
"Successfully retrieved actual envelope from IPFS",
);
}
Ok(Some(actual))
}
None => Err(HybridError::ContentNotFound.into()),
}
} else {
if verbose {
verbose_println(
"Envelope is not a reference, treating as direct payload",
);
}
Ok(Some(envelope))
}
}
}
}
}
#[async_trait::async_trait(?Send)]
impl KvStore for HybridKv {
async fn put(
&self,
arid: &ARID,
envelope: &Envelope,
ttl_seconds: Option<u64>,
verbose: bool,
) -> Result<String> {
self.put_impl(arid, envelope, ttl_seconds, verbose).await
}
async fn get(
&self,
arid: &ARID,
timeout_seconds: Option<u64>,
verbose: bool,
) -> Result<Option<Envelope>> {
self.get_impl(arid, timeout_seconds, verbose).await
}
async fn exists(&self, arid: &ARID) -> Result<bool> {
self.dht.exists(arid).await
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_placeholder() {
}
}