diff --git a/Cargo.lock b/Cargo.lock index f96365e3b3624dfb05a370c868bc9d74e437134e..7f8b1658a899719344ee0ee3b374b2ec72df2938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2433,6 +2433,7 @@ dependencies = [ "base64", "chrono", "derive_more", + "lazy_static", "log", "mobc-redis", "rbatis", diff --git a/Cargo.toml b/Cargo.toml index d1d90ddfd6c5935e8d7f13f3e0f704ebd53318b8..3528b0d7bfd26548b58564f3b6a7ced093a09dfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ url = "2.2.2" base64 = "^0.13.0" simple-logging = { version = "^2.0.2" } strfmt = "^0.1.6" +lazy_static = "1.4.0" strum = { version = "0.23", features = ["derive"] } rbson = { version = "2.0", optional = true } rbatis = { version = "^3.0", default-features = false, features = ["runtime-async-std-rustls", "all-database"], optional = true } diff --git a/src/errors.rs b/src/errors.rs index 30a8ef98fb48b1de97940940fa710a0ec7cb1715..585adc00a4f81dfa05f1a6711900a552defde137 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -41,12 +41,14 @@ pub enum RustusError { UnknownExtension(String), } +/// This conversion allows us to use `RustusError` in the `main` function. impl From<RustusError> for Error { fn from(err: RustusError) -> Self { Error::new(ErrorKind::Other, err) } } +/// Trait to convert errors to http-responses. impl ResponseError for RustusError { fn error_response(&self) -> HttpResponse { HttpResponseBuilder::new(self.status_code()) diff --git a/src/info_storages/mod.rs b/src/info_storages/mod.rs index e444828a8c38163bacc7d3a45a76f4caee3d3de2..66338b6b0513696b27dc1f44e9f7bf2ed8e8e7b2 100644 --- a/src/info_storages/mod.rs +++ b/src/info_storages/mod.rs @@ -1,86 +1,12 @@ -use std::str::FromStr; - -use async_trait::async_trait; -use derive_more::{Display, From}; - -pub use file_info::FileInfo; - -use crate::errors::RustusResult; -use crate::RustusConf; - -mod file_info; - -use strum::{EnumIter, IntoEnumIterator}; +pub mod file_info_storage; #[cfg(feature = "db_info_storage")] pub mod db_info_storage; -pub mod file_info_storage; #[cfg(feature = "redis_info_storage")] pub mod redis_info_storage; -#[derive(PartialEq, From, Display, Clone, Debug, EnumIter)] -pub enum AvailableInfoStores { - #[display(fmt = "file_info_storage")] - Files, - #[cfg(feature = "db_info_storage")] - #[display(fmt = "db_info_storage")] - DB, - #[cfg(feature = "redis_info_storage")] - #[display(fmt = "redis_info_storage")] - Redis, -} - -impl FromStr for AvailableInfoStores { - type Err = String; - - fn from_str(input: &str) -> Result<Self, Self::Err> { - let available_stores = AvailableInfoStores::iter() - .map(|info_store| format!("\t* {}", info_store.to_string())) - .collect::<Vec<String>>() - .join("\n"); - let inp_string = String::from(input); - for store in AvailableInfoStores::iter() { - if inp_string == store.to_string() { - return Ok(store); - } - } - Err(format!( - "Unknown info storage type.\n Available storages:\n{}", - available_stores - )) - } -} - -impl AvailableInfoStores { - /// Convert `AvailableInfoStores` to the impl `InfoStorage`. - /// - /// # Params - /// `config` - Rustus configuration. - /// - pub async fn get( - &self, - config: &RustusConf, - ) -> RustusResult<Box<dyn InfoStorage + Sync + Send>> { - match self { - Self::Files => Ok(Box::new(file_info_storage::FileInfoStorage::new( - config.clone(), - ))), - #[cfg(feature = "db_info_storage")] - Self::DB => Ok(Box::new( - db_info_storage::DBInfoStorage::new(config.clone()).await?, - )), - #[cfg(feature = "redis_info_storage")] - AvailableInfoStores::Redis => Ok(Box::new( - redis_info_storage::RedisStorage::new(config.clone()).await?, - )), - } - } -} +pub mod models; -#[async_trait] -pub trait InfoStorage { - async fn prepare(&mut self) -> RustusResult<()>; - async fn set_info(&self, file_info: &FileInfo, create: bool) -> RustusResult<()>; - async fn get_info(&self, file_id: &str) -> RustusResult<FileInfo>; - async fn remove_info(&self, file_id: &str) -> RustusResult<()>; -} +pub use models::available_info_storages::AvailableInfoStores; +pub use models::file_info::FileInfo; +pub use models::info_store::InfoStorage; diff --git a/src/info_storages/models/available_info_storages.rs b/src/info_storages/models/available_info_storages.rs new file mode 100644 index 0000000000000000000000000000000000000000..256fb348be60a57cda95817684511092c5641431 --- /dev/null +++ b/src/info_storages/models/available_info_storages.rs @@ -0,0 +1,74 @@ +use std::str::FromStr; + +use derive_more::{Display, From}; + +use crate::errors::RustusResult; +use crate::RustusConf; + +use crate::info_storages::{file_info_storage, InfoStorage}; +use strum::{EnumIter, IntoEnumIterator}; + +#[cfg(feature = "db_info_storage")] +use crate::info_storages::db_info_storage; + +#[cfg(feature = "redis_info_storage")] +use crate::info_storages::redis_info_storage; + +#[derive(PartialEq, From, Display, Clone, Debug, EnumIter)] +pub enum AvailableInfoStores { + #[display(fmt = "file_info_storage")] + Files, + #[cfg(feature = "db_info_storage")] + #[display(fmt = "db_info_storage")] + DB, + #[cfg(feature = "redis_info_storage")] + #[display(fmt = "redis_info_storage")] + Redis, +} + +impl FromStr for AvailableInfoStores { + type Err = String; + + fn from_str(input: &str) -> Result<Self, Self::Err> { + let available_stores = AvailableInfoStores::iter() + .map(|info_store| format!("\t* {}", info_store.to_string())) + .collect::<Vec<String>>() + .join("\n"); + let inp_string = String::from(input); + for store in AvailableInfoStores::iter() { + if inp_string == store.to_string() { + return Ok(store); + } + } + Err(format!( + "Unknown info storage type.\n Available storages:\n{}", + available_stores + )) + } +} + +impl AvailableInfoStores { + /// Convert `AvailableInfoStores` to the impl `InfoStorage`. + /// + /// # Params + /// `config` - Rustus configuration. + /// + pub async fn get( + &self, + config: &RustusConf, + ) -> RustusResult<Box<dyn InfoStorage + Sync + Send>> { + match self { + Self::Files => Ok(Box::new(file_info_storage::FileInfoStorage::new( + config.clone(), + ))), + #[cfg(feature = "db_info_storage")] + Self::DB => Ok(Box::new( + db_info_storage::DBInfoStorage::new(config.clone()).await?, + )), + #[cfg(feature = "redis_info_storage")] + AvailableInfoStores::Redis => Ok(Box::new( + redis_info_storage::RedisStorage::new(config.clone()).await?, + )), + } + } +} diff --git a/src/info_storages/file_info.rs b/src/info_storages/models/file_info.rs similarity index 100% rename from src/info_storages/file_info.rs rename to src/info_storages/models/file_info.rs diff --git a/src/info_storages/models/info_store.rs b/src/info_storages/models/info_store.rs new file mode 100644 index 0000000000000000000000000000000000000000..66418e0317343f45eb2873bdf2fa1f57ae8a3055 --- /dev/null +++ b/src/info_storages/models/info_store.rs @@ -0,0 +1,42 @@ +use crate::errors::RustusResult; +use crate::info_storages::FileInfo; +use async_trait::async_trait; + +/// Trait for every info storage. +/// +/// This trait defines required functions +/// for building your own info storage. +#[async_trait] +pub trait InfoStorage { + /// Prepare storage for storing files. + /// + /// In this function you can prepare + /// you info storage. E.G. create a table in a database, + /// or a directory somewhere. + async fn prepare(&mut self) -> RustusResult<()>; + + /// Set information about an upload. + /// + /// This function **must** persist information + /// about given upload so it can be accessed again by file_id. + /// + /// The `create` parameter is for optimizations. + /// It's here, because some storages like databases have to + /// be queried twice in order to get the information + /// about a file and actually store it. To bypass it + /// we can guarantee that this parameter will never be `true` + /// for any update operation. + async fn set_info(&self, file_info: &FileInfo, create: bool) -> RustusResult<()>; + + /// Retrieve information from storage. + /// + /// This function must return information about file + /// from the given storage. + async fn get_info(&self, file_id: &str) -> RustusResult<FileInfo>; + + /// This function removes information about file completely. + /// + /// This function must actually delete any stored information + /// associated with the given `file_id`. + async fn remove_info(&self, file_id: &str) -> RustusResult<()>; +} diff --git a/src/info_storages/models/mod.rs b/src/info_storages/models/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..3ed80cfe51c67f71c90582789aea7d421b943b8c --- /dev/null +++ b/src/info_storages/models/mod.rs @@ -0,0 +1,3 @@ +pub mod available_info_storages; +pub mod file_info; +pub mod info_store; diff --git a/src/main.rs b/src/main.rs index 4870ec39201adb200179319fa0810653d97fc846..f906b878a80a4251a86eb1b6ba3f96a7714b5d06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use crate::storages::Storage; mod config; mod errors; mod info_storages; +mod notifiers; mod protocol; mod routes; mod storages; diff --git a/src/notifiers/mod.rs b/src/notifiers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..feb8e349067b88a2236e0a1ffacf45081512f878 --- /dev/null +++ b/src/notifiers/mod.rs @@ -0,0 +1,2 @@ +mod models; +mod notifier; diff --git a/src/notifiers/models/message.rs b/src/notifiers/models/message.rs new file mode 100644 index 0000000000000000000000000000000000000000..4006433c5d8ed507aeb2214180abea469dd71ca6 --- /dev/null +++ b/src/notifiers/models/message.rs @@ -0,0 +1,7 @@ +use crate::info_storages::FileInfo; +use actix_web::HttpRequest; + +pub struct Message<'a> { + request: &'a HttpRequest, + file_info: FileInfo, +} diff --git a/src/notifiers/models/mod.rs b/src/notifiers/models/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..e216a501809b4e249e2d2df72e47e6e85aa7d017 --- /dev/null +++ b/src/notifiers/models/mod.rs @@ -0,0 +1 @@ +pub mod message; diff --git a/src/notifiers/notifier.rs b/src/notifiers/notifier.rs new file mode 100644 index 0000000000000000000000000000000000000000..f71f3cdd8681f54d9e916aa24983a9c51640e72a --- /dev/null +++ b/src/notifiers/notifier.rs @@ -0,0 +1,6 @@ +use async_trait::async_trait; + +#[async_trait] +trait Notifier { + async fn send_message(); +} diff --git a/src/routes.rs b/src/routes.rs index ba878f8b21fd9d28d4a10f420d9f56f9f80f9117..d7310952a278edaff27b0d6e626dfa5332cdab7e 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -3,6 +3,8 @@ use actix_web::HttpResponse; use crate::errors::{RustusError, RustusResult}; /// Default response to all unknown URLs. +/// All protocol urls can be found +/// at `crate::protocol::*`. #[allow(clippy::unused_async)] pub async fn not_found() -> RustusResult<HttpResponse> { Err(RustusError::FileNotFound) diff --git a/src/storages/mod.rs b/src/storages/mod.rs index 33bb9b0c989e5b68ce8e14e0d3b759c125611db5..083458b5caff04176d39a5ab2de45d0f6a007473 100644 --- a/src/storages/mod.rs +++ b/src/storages/mod.rs @@ -1,120 +1,5 @@ -use std::collections::HashMap; -use std::str::FromStr; - -use actix_files::NamedFile; -use async_trait::async_trait; -use derive_more::{Display, From}; - -use crate::errors::RustusResult; -use crate::info_storages::{FileInfo, InfoStorage}; -use crate::RustusConf; - pub mod file_storage; +mod models; -/// Enum of available Storage implementations. -#[derive(PartialEq, From, Display, Clone, Debug)] -pub enum AvailableStores { - #[display(fmt = "FileStorage")] - FileStorage, -} - -impl FromStr for AvailableStores { - type Err = String; - - /// This function converts string to the `AvailableStore` item. - /// This function is used by structopt to parse CLI parameters. - /// - /// # Params - /// `input` - input string. - fn from_str(input: &str) -> Result<AvailableStores, Self::Err> { - match input { - "file_storage" => Ok(AvailableStores::FileStorage), - _ => Err(String::from("Unknown storage type")), - } - } -} - -impl AvailableStores { - /// Convert `AvailableStores` to the Storage. - /// - /// # Params - /// `config` - Rustus configuration. - /// - pub fn get( - &self, - config: &RustusConf, - info_storage: Box<dyn InfoStorage + Sync + Send>, - ) -> Box<dyn Storage + Send + Sync> { - #[allow(clippy::single_match)] - match self { - Self::FileStorage => { - Box::new(file_storage::FileStorage::new(config.clone(), info_storage)) - } - } - } -} - -#[async_trait] -pub trait Storage { - /// Prepare storage before starting up server. - /// - /// Function to check if configuration is correct - /// and prepare storage E.G. create connection pool, - /// or directory for files. - async fn prepare(&mut self) -> RustusResult<()>; - - /// Get file information. - /// - /// This method returns all information about file. - /// - /// # Params - /// `file_id` - unique file identifier. - async fn get_file_info(&self, file_id: &str) -> RustusResult<FileInfo>; - - /// Get contents of a file. - /// - /// This method must return NamedFile since it - /// is compatible with ActixWeb files interface. - /// - /// # Params - /// `file_id` - unique file identifier. - async fn get_contents(&self, file_id: &str) -> RustusResult<NamedFile>; - - /// Add bytes to the file. - /// - /// This method is used to append bytes to some file. - /// It returns new offset. - /// - /// # Params - /// `file_id` - unique file identifier; - /// `request_offset` - offset from the client. - /// `bytes` - bytes to append to the file. - async fn add_bytes( - &self, - file_id: &str, - request_offset: usize, - bytes: &[u8], - ) -> RustusResult<usize>; - - /// Create file in storage. - /// - /// This method is used to generate unique file id, create file and store information about it. - /// - /// # Params - /// `file_size` - Size of a file. It may be None if size is deferred; - /// `metadata` - Optional file meta-information; - async fn create_file( - &self, - file_size: Option<usize>, - metadata: Option<HashMap<String, String>>, - ) -> RustusResult<String>; - - /// Remove file from storage - /// - /// This method removes file and all associated - /// object if any. - /// - /// # Params - /// `file_id` - unique file identifier; - async fn remove_file(&self, file_id: &str) -> RustusResult<()>; -} +pub use models::available_stores::AvailableStores; +pub use models::storage::Storage; diff --git a/src/storages/models/available_stores.rs b/src/storages/models/available_stores.rs new file mode 100644 index 0000000000000000000000000000000000000000..e13c670e1dd9ea096aa633c7cd1b42a703adfb4a --- /dev/null +++ b/src/storages/models/available_stores.rs @@ -0,0 +1,48 @@ +use crate::info_storages::InfoStorage; +use crate::storages::file_storage; +use crate::{RustusConf, Storage}; +use derive_more::{Display, From}; +use std::str::FromStr; + +/// Enum of available Storage implementations. +#[derive(PartialEq, From, Display, Clone, Debug)] +pub enum AvailableStores { + #[display(fmt = "FileStorage")] + FileStorage, +} + +impl FromStr for AvailableStores { + type Err = String; + + /// This function converts string to the `AvailableStore` item. + /// This function is used by structopt to parse CLI parameters. + /// + /// # Params + /// `input` - input string. + fn from_str(input: &str) -> Result<AvailableStores, Self::Err> { + match input { + "file_storage" => Ok(AvailableStores::FileStorage), + _ => Err(String::from("Unknown storage type")), + } + } +} + +impl AvailableStores { + /// Convert `AvailableStores` to the Storage. + /// + /// # Params + /// `config` - Rustus configuration. + /// + pub fn get( + &self, + config: &RustusConf, + info_storage: Box<dyn InfoStorage + Sync + Send>, + ) -> Box<dyn Storage + Send + Sync> { + #[allow(clippy::single_match)] + match self { + Self::FileStorage => { + Box::new(file_storage::FileStorage::new(config.clone(), info_storage)) + } + } + } +} diff --git a/src/storages/models/mod.rs b/src/storages/models/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..6f58895a7ebac11e31210d33dbf7e97d1c85476f --- /dev/null +++ b/src/storages/models/mod.rs @@ -0,0 +1,2 @@ +pub mod available_stores; +pub mod storage; diff --git a/src/storages/models/storage.rs b/src/storages/models/storage.rs new file mode 100644 index 0000000000000000000000000000000000000000..89bed1b6d14b34d7baea66a58bd5cb6c3509e3da --- /dev/null +++ b/src/storages/models/storage.rs @@ -0,0 +1,70 @@ +use crate::errors::RustusResult; +use crate::info_storages::FileInfo; +use actix_files::NamedFile; +use async_trait::async_trait; +use std::collections::HashMap; + +#[async_trait] +pub trait Storage { + /// Prepare storage before starting up server. + /// + /// Function to check if configuration is correct + /// and prepare storage E.G. create connection pool, + /// or directory for files. + async fn prepare(&mut self) -> RustusResult<()>; + + /// Get file information. + /// + /// This method returns all information about file. + /// + /// # Params + /// `file_id` - unique file identifier. + async fn get_file_info(&self, file_id: &str) -> RustusResult<FileInfo>; + + /// Get contents of a file. + /// + /// This method must return NamedFile since it + /// is compatible with ActixWeb files interface. + /// + /// # Params + /// `file_id` - unique file identifier. + async fn get_contents(&self, file_id: &str) -> RustusResult<NamedFile>; + + /// Add bytes to the file. + /// + /// This method is used to append bytes to some file. + /// It returns new offset. + /// + /// # Params + /// `file_id` - unique file identifier; + /// `request_offset` - offset from the client. + /// `bytes` - bytes to append to the file. + async fn add_bytes( + &self, + file_id: &str, + request_offset: usize, + bytes: &[u8], + ) -> RustusResult<usize>; + + /// Create file in storage. + /// + /// This method is used to generate unique file id, create file and store information about it. + /// + /// # Params + /// `file_size` - Size of a file. It may be None if size is deferred; + /// `metadata` - Optional file meta-information; + async fn create_file( + &self, + file_size: Option<usize>, + metadata: Option<HashMap<String, String>>, + ) -> RustusResult<String>; + + /// Remove file from storage + /// + /// This method removes file and all associated + /// object if any. + /// + /// # Params + /// `file_id` - unique file identifier; + async fn remove_file(&self, file_id: &str) -> RustusResult<()>; +}