about summary refs log tree commit diff
path: root/kittybox-rs/src/media/storage
diff options
context:
space:
mode:
Diffstat (limited to 'kittybox-rs/src/media/storage')
-rw-r--r--kittybox-rs/src/media/storage/file.rs61
-rw-r--r--kittybox-rs/src/media/storage/mod.rs53
2 files changed, 114 insertions, 0 deletions
diff --git a/kittybox-rs/src/media/storage/file.rs b/kittybox-rs/src/media/storage/file.rs
new file mode 100644
index 0000000..8c0ddf0
--- /dev/null
+++ b/kittybox-rs/src/media/storage/file.rs
@@ -0,0 +1,61 @@
+use super::{ErrorKind, MediaStore, MediaStoreError, Result};
+use async_trait::async_trait;
+use std::path::PathBuf;
+use tokio::fs::OpenOptions;
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+
+#[derive(Clone)]
+pub struct FileStore {
+    base: PathBuf,
+}
+
+impl From<tokio::io::Error> for MediaStoreError {
+    fn from(source: tokio::io::Error) -> Self {
+        Self {
+            source: Some(Box::new(source)),
+            msg: "file I/O error".to_owned(),
+            kind: ErrorKind::Backend,
+        }
+    }
+}
+
+#[async_trait]
+impl MediaStore for FileStore {
+    async fn write_streaming(
+        &self, domain: url::Host, filename: &str,
+        content: axum::extract::multipart::Field<'_>
+    ) -> Result<()> {
+        todo!()
+    }
+
+    async fn read_streaming(&self, domain: url::Host, filename: &str) -> Result<tokio_util::io::ReaderStream<Box<dyn tokio::io::AsyncRead>>> {
+        todo!()
+    }
+    
+    async fn write(&self, domain: url::Host, filename: &str, content: &[u8]) -> Result<()> {
+        let path = self.base.join(format!("{}/{}", domain, filename));
+
+        let mut file = OpenOptions::new()
+            .create_new(true)
+            .write(true)
+            .open(path)
+            .await?;
+
+        Ok(file.write_all(content).await?)
+    }
+
+    async fn read(&self, domain: url::Host, filename: &str) -> Result<Vec<u8>> {
+        let path = self.base.join(format!("{}/{}", domain, filename));
+
+        let mut file = OpenOptions::new().read(true).open(path).await?;
+
+        let mut buf: Vec<u8> = Vec::default();
+        file.read_to_end(&mut buf).await?;
+
+        Ok(buf)
+    }
+
+    async fn delete(&self, domain: url::Host, filename: &str) -> Result<()> {
+        todo!()
+    }
+}
diff --git a/kittybox-rs/src/media/storage/mod.rs b/kittybox-rs/src/media/storage/mod.rs
new file mode 100644
index 0000000..e9b01f9
--- /dev/null
+++ b/kittybox-rs/src/media/storage/mod.rs
@@ -0,0 +1,53 @@
+use async_trait::async_trait;
+
+pub mod file;
+
+#[derive(Debug, Clone, Copy)]
+pub enum ErrorKind {
+    Backend,
+    Permission,
+    Conflict,
+    Other,
+}
+
+#[derive(Debug)]
+pub struct MediaStoreError {
+    kind: ErrorKind,
+    source: Option<Box<dyn std::error::Error + Send + Sync>>,
+    msg: String,
+}
+
+impl std::error::Error for MediaStoreError {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        self.source
+            .as_ref()
+            .map(|i| i.as_ref() as &dyn std::error::Error)
+    }
+}
+
+impl std::fmt::Display for MediaStoreError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(
+            f,
+            "{}: {}",
+            match self.kind {
+                ErrorKind::Backend => "media storage backend error",
+                ErrorKind::Permission => "permission denied",
+                ErrorKind::Conflict => "conflict with existing data",
+                ErrorKind::Other => "unknown media storage error",
+            },
+            self.msg
+        )
+    }
+}
+
+pub type Result<T> = std::result::Result<T, MediaStoreError>;
+
+#[async_trait]
+pub trait MediaStore: 'static + Send + Sync + Clone {
+    async fn write_streaming(&self, domain: url::Host, filename: &str, content: axum::extract::multipart::Field<'_>) -> Result<()>;
+    async fn write(&self, domain: url::Host, filename: &str, content: &[u8]) -> Result<()>;
+    async fn read_streaming(&self, domain: url::Host, filename: &str) -> Result<tokio_util::io::ReaderStream<Box<dyn tokio::io::AsyncRead>>>;
+    async fn read(&self, domain: url::Host, filename: &str) -> Result<Vec<u8>>;
+    async fn delete(&self, domain: url::Host, filename: &str) -> Result<()>;
+}