/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use super::{FdbStore, MAX_VALUE_SIZE}; use crate::{ IterateParams, SUBSPACE_BLOBS, backend::foundationdb::into_error, write::{AnyKey, key::KeySerializer}, }; use std::ops::Range; use trc::AddContext; use types::blob_hash::BLOB_HASH_LEN; impl FdbStore { pub(crate) async fn get_blob( &self, key: &[u8], range: Range, ) -> trc::Result>> { let block_start = range.start / MAX_VALUE_SIZE; let bytes_start = range.start % MAX_VALUE_SIZE; let block_end = (range.end / MAX_VALUE_SIZE) + 1; let begin = KeySerializer::new(key.len() + 2) .write(key) .write(block_start as u16) .finalize(); let end = KeySerializer::new(key.len() + 2) .write(key) .write(block_end as u16) .finalize(); let key_len = begin.len(); let mut blob_data: Option> = None; let blob_range = range.end - range.start; self.iterate( IterateParams::new( AnyKey { subspace: SUBSPACE_BLOBS, key: begin, }, AnyKey { subspace: SUBSPACE_BLOBS, key: end, }, ), |key, value| { if key.len() == key_len { if let Some(blob_data) = &mut blob_data { blob_data.extend_from_slice( value .get( ..std::cmp::min( blob_range.saturating_sub(blob_data.len()), value.len(), ), ) .unwrap_or(&[]), ); if blob_data.len() == blob_range { return Ok(false); } } else { let blob_size = if blob_range <= (5 * (1 << 20)) { blob_range } else if value.len() == MAX_VALUE_SIZE { MAX_VALUE_SIZE * 2 } else { value.len() }; let mut blob_data_ = Vec::with_capacity(blob_size); blob_data_.extend_from_slice( value .get( bytes_start ..std::cmp::min(bytes_start + blob_range, value.len()), ) .unwrap_or(&[]), ); let is_done = blob_data_.len() == blob_range; blob_data = blob_data_.into(); if is_done { return Ok(false); } } } Ok(true) }, ) .await .caused_by(trc::location!())?; Ok(blob_data) } pub(crate) async fn put_blob(&self, key: &[u8], data: &[u8]) -> trc::Result<()> { const N_CHUNKS: usize = (1 << 5) - 1; let last_chunk = std::cmp::max( (data.len() / MAX_VALUE_SIZE) + if !data.len().is_multiple_of(MAX_VALUE_SIZE) { 1 } else { 0 }, 1, ) - 1; let mut trx = self.db.create_trx().map_err(into_error)?; for (chunk_pos, chunk_bytes) in data.chunks(MAX_VALUE_SIZE).enumerate() { trx.set( &KeySerializer::new(key.len() + 3) .write(SUBSPACE_BLOBS) .write(key) .write(chunk_pos as u16) .finalize(), chunk_bytes, ); if chunk_pos == last_chunk || (chunk_pos > 0 && chunk_pos % N_CHUNKS == 0) { self.commit(trx, false).await?; if chunk_pos < last_chunk { trx = self.db.create_trx().map_err(into_error)?; } else { break; } } } Ok(()) } pub(crate) async fn delete_blob(&self, key: &[u8]) -> trc::Result { if key.len() < BLOB_HASH_LEN { return Ok(false); } let trx = self.db.create_trx().map_err(into_error)?; trx.clear_range( &KeySerializer::new(key.len() + 3) .write(SUBSPACE_BLOBS) .write(key) .write(0u16) .finalize(), &KeySerializer::new(key.len() + 3) .write(SUBSPACE_BLOBS) .write(key) .write(u16::MAX) .finalize(), ); self.commit(trx, false).await } }