blob: 0c0e8140315867121a6fc7236e10b61566fae739 [file]
use crate::streaming::storage::{Storage, StreamStorage};
use crate::streaming::streams::stream::Stream;
use crate::streaming::topics::topic::Topic;
use anyhow::Context;
use async_trait::async_trait;
use futures::future::join_all;
use iggy::error::IggyError;
use serde::{Deserialize, Serialize};
use sled::Db;
use std::path::Path;
use std::sync::Arc;
use tokio::fs;
use tokio::fs::create_dir;
use tokio::sync::Mutex;
use tracing::{error, info};
#[derive(Debug)]
pub struct FileStreamStorage {
db: Arc<Db>,
}
impl FileStreamStorage {
pub fn new(db: Arc<Db>) -> Self {
Self { db }
}
}
unsafe impl Send for FileStreamStorage {}
unsafe impl Sync for FileStreamStorage {}
impl StreamStorage for FileStreamStorage {}
#[derive(Debug, Serialize, Deserialize)]
struct StreamData {
name: String,
created_at: u64,
}
#[async_trait]
impl Storage<Stream> for FileStreamStorage {
async fn load(&self, stream: &mut Stream) -> Result<(), IggyError> {
info!("Loading stream with ID: {} from disk...", stream.stream_id);
if !Path::new(&stream.path).exists() {
return Err(IggyError::StreamIdNotFound(stream.stream_id));
}
let key = get_key(stream.stream_id);
let stream_data = match self.db.get(&key).with_context(|| {
format!(
"Failed to load stream with ID: {}, key: {}",
stream.stream_id, key
)
}) {
Ok(stream_data) => {
if let Some(stream_data) = stream_data {
let stream_data = rmp_serde::from_slice::<StreamData>(&stream_data)
.with_context(|| {
format!(
"Failed to deserialize stream with ID: {}, key: {}",
stream.stream_id, key
)
});
match stream_data {
Ok(stream_data) => stream_data,
Err(err) => {
return Err(IggyError::CannotDeserializeResource(err));
}
}
} else {
return Err(IggyError::ResourceNotFound(key));
}
}
Err(err) => {
return Err(IggyError::CannotLoadResource(err));
}
};
stream.name = stream_data.name;
stream.created_at = stream_data.created_at;
let mut unloaded_topics = Vec::new();
let dir_entries = fs::read_dir(&stream.topics_path).await;
if dir_entries.is_err() {
return Err(IggyError::CannotReadTopics(stream.stream_id));
}
let mut dir_entries = dir_entries.unwrap();
while let Some(dir_entry) = dir_entries.next_entry().await.unwrap_or(None) {
let name = dir_entry.file_name().into_string().unwrap();
let topic_id = name.parse::<u32>();
if topic_id.is_err() {
error!("Invalid topic ID file with name: '{}'.", name);
continue;
}
let topic_id = topic_id.unwrap();
let topic = Topic::empty(
stream.stream_id,
topic_id,
stream.config.clone(),
stream.storage.clone(),
);
unloaded_topics.push(topic);
}
let loaded_topics = Arc::new(Mutex::new(Vec::new()));
let mut load_topics = Vec::new();
for mut topic in unloaded_topics {
let loaded_topics = loaded_topics.clone();
let load_stream = tokio::spawn(async move {
match topic.load().await {
Ok(_) => loaded_topics.lock().await.push(topic),
Err(error) => error!(
"Failed to load topic with ID: {} for stream with ID: {}. Error: {}",
topic.topic_id, topic.stream_id, error
),
}
});
load_topics.push(load_stream);
}
join_all(load_topics).await;
for topic in loaded_topics.lock().await.drain(..) {
if stream.topics.contains_key(&topic.topic_id) {
error!(
"Topic with ID: '{}' already exists for stream with ID: {}.",
&topic.topic_id, &stream.stream_id
);
continue;
}
if stream.topics_ids.contains_key(&topic.name) {
error!(
"Topic with name: '{}' already exists for stream with ID: {}.",
&topic.name, &stream.stream_id
);
continue;
}
stream.topics_ids.insert(topic.name.clone(), topic.topic_id);
stream.topics.insert(topic.topic_id, topic);
}
info!(
"Loaded stream: '{}' with ID: {} from disk.",
&stream.name, &stream.stream_id
);
Ok(())
}
async fn save(&self, stream: &Stream) -> Result<(), IggyError> {
if !Path::new(&stream.path).exists() && create_dir(&stream.path).await.is_err() {
return Err(IggyError::CannotCreateStreamDirectory(
stream.stream_id,
stream.path.clone(),
));
}
if !Path::new(&stream.topics_path).exists()
&& create_dir(&stream.topics_path).await.is_err()
{
return Err(IggyError::CannotCreateTopicsDirectory(
stream.stream_id,
stream.topics_path.clone(),
));
}
let key = get_key(stream.stream_id);
match rmp_serde::to_vec(&StreamData {
name: stream.name.clone(),
created_at: stream.created_at,
})
.with_context(|| format!("Failed to serialize stream with key: {}", key))
{
Ok(data) => {
if let Err(err) = self
.db
.insert(&key, data)
.with_context(|| format!("Failed to insert stream with key: {}", key))
{
return Err(IggyError::CannotSaveResource(err));
}
}
Err(err) => {
return Err(IggyError::CannotSerializeResource(err));
}
}
info!("Saved stream with ID: {}.", stream.stream_id);
Ok(())
}
async fn delete(&self, stream: &Stream) -> Result<(), IggyError> {
info!("Deleting stream with ID: {}...", stream.stream_id);
let key = get_key(stream.stream_id);
if let Err(err) = self
.db
.remove(&key)
.with_context(|| format!("Failed to delete stream with key: {}", key))
{
return Err(IggyError::CannotDeleteResource(err));
}
if fs::remove_dir_all(&stream.path).await.is_err() {
return Err(IggyError::CannotDeleteStreamDirectory(stream.stream_id));
}
info!("Deleted stream with ID: {}.", stream.stream_id);
Ok(())
}
}
fn get_key(stream_id: u32) -> String {
format!("streams:{}", stream_id)
}