From cf747aaefcc512195699b391629adabe86c68385 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Fri, 18 Aug 2023 11:25:27 +0200 Subject: [PATCH 01/17] added: WIP state, not working --- movies-db-service/movies-db-cli/Cargo.toml | 3 +- movies-db-service/movies-db-cli/src/main.rs | 22 +++++++--- movies-db-service/movies-db/Cargo.toml | 3 ++ movies-db-service/movies-db/src/lib.rs | 2 + movies-db-service/movies-db/src/service.rs | 47 +++++++++++++++++++++ 5 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 movies-db-service/movies-db/src/service.rs diff --git a/movies-db-service/movies-db-cli/Cargo.toml b/movies-db-service/movies-db-cli/Cargo.toml index 25c7e85..7ed6ee6 100644 --- a/movies-db-service/movies-db-cli/Cargo.toml +++ b/movies-db-service/movies-db-cli/Cargo.toml @@ -10,4 +10,5 @@ movies-db = { path = "../movies-db" } log = "0.4" anyhow = "1.0" simple-logging = "2.0" -clap = { version = "4.2", features = ["derive"] } \ No newline at end of file +clap = { version = "4.2", features = ["derive"] } +tokio = { version = "1.32", features = ["full"] } diff --git a/movies-db-service/movies-db-cli/src/main.rs b/movies-db-service/movies-db-cli/src/main.rs index 66db1c3..ef61343 100644 --- a/movies-db-service/movies-db-cli/src/main.rs +++ b/movies-db-service/movies-db-cli/src/main.rs @@ -2,11 +2,12 @@ mod options; use anyhow::Result; use log::{error, info}; +use movies_db::{Options as ServiceOptions, Service, SimpleMoviesIndex}; use options::Options; -use clap::{Parser}; +use clap::Parser; -use log::{LevelFilter}; +use log::LevelFilter; /// Parses the program arguments and returns None, if no arguments were provided and Some otherwise. fn parse_args() -> Result { @@ -19,17 +20,24 @@ fn initialize_logging(filter: LevelFilter) { simple_logging::log_to(std::io::stdout(), filter); } - /// Runs the program. -fn run_program() -> Result<()> { +async fn run_program() -> Result<()> { let options = parse_args()?; initialize_logging(LevelFilter::from(options.log_level)); + let service_options = ServiceOptions { + root_dir: options.root_dir, + }; + + let service: Service = Service::new(&service_options)?; + service.run().await?; + Ok(()) } -fn main() { - match run_program() { +#[tokio::main] +async fn main() { + match run_program().await { Ok(()) => { info!("SUCCESS"); } @@ -40,4 +48,4 @@ fn main() { std::process::exit(-1); } } -} \ No newline at end of file +} diff --git a/movies-db-service/movies-db/Cargo.toml b/movies-db-service/movies-db/Cargo.toml index e7b1041..a2da09f 100644 --- a/movies-db-service/movies-db/Cargo.toml +++ b/movies-db-service/movies-db/Cargo.toml @@ -11,6 +11,9 @@ uuid = { version = "1.4", features = ["v4", "fast-rng", "macro-diagnostics"] } chrono = { version = "0.4", features = ["serde"] } wildmatch = "2.1" serde = { version = "1.0", features = ["derive"] } +hyper = { version = "0.14", features = ["full"] } +tokio = { version = "1", features = ["full"] } +futures = { version = "0.3" } [dev-dependencies] tempdir = "0.3" diff --git a/movies-db-service/movies-db/src/lib.rs b/movies-db-service/movies-db/src/lib.rs index a312f97..32c617f 100644 --- a/movies-db-service/movies-db/src/lib.rs +++ b/movies-db-service/movies-db/src/lib.rs @@ -1,9 +1,11 @@ mod db; mod error; mod options; +mod service; mod storage; pub use db::*; pub use error::*; pub use options::*; +pub use service::*; pub use storage::*; diff --git a/movies-db-service/movies-db/src/service.rs b/movies-db-service/movies-db/src/service.rs new file mode 100644 index 0000000..6ed61d7 --- /dev/null +++ b/movies-db-service/movies-db/src/service.rs @@ -0,0 +1,47 @@ +use std::convert::Infallible; + +use hyper::service::{make_service_fn, service_fn}; +use log::info; + +use crate::{Error, MoviesIndex, Options}; + +pub struct Service +where + I: MoviesIndex, +{ + index: I, +} + +impl Service +where + I: MoviesIndex, +{ + /// Creates a new instance of the service. + /// + /// # Arguments + /// * `options` - The options for the service. + pub fn new(options: &Options) -> Result { + let index = I::new(options)?; + + Ok(Self { index }) + } + + /// Runs the service. + pub async fn run(&self) -> Result<(), Error> { + info!("Running the service..."); + + Ok(()) + } + + fn run_http_server(&self) { + info!("Running the HTTP server..."); + + // create service handler + let make_svc = make_service_fn(move |_conn| { + async move { + // This is the request handler. + Ok::<_, Infallible>(service_fn(move |req| Self::serve_req(req, handler))) + } + }); + } +} From dff8420c1cbc1680ffd7b32f4f547d1efe665f4c Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 09:48:13 +0200 Subject: [PATCH 02/17] added: WIP state --- movies-db-service/movies-db/Cargo.toml | 2 + movies-db-service/movies-db/src/options.rs | 7 +- movies-db-service/movies-db/src/service.rs | 47 ------ .../movies-db/src/service/mod.rs | 4 + .../movies-db/src/service/service.rs | 77 ++++++++++ .../movies-db/src/service/service_handler.rs | 145 ++++++++++++++++++ .../movies-db/src/storage/mod.rs | 4 +- 7 files changed, 237 insertions(+), 49 deletions(-) delete mode 100644 movies-db-service/movies-db/src/service.rs create mode 100644 movies-db-service/movies-db/src/service/mod.rs create mode 100644 movies-db-service/movies-db/src/service/service.rs create mode 100644 movies-db-service/movies-db/src/service/service_handler.rs diff --git a/movies-db-service/movies-db/Cargo.toml b/movies-db-service/movies-db/Cargo.toml index a2da09f..9019b28 100644 --- a/movies-db-service/movies-db/Cargo.toml +++ b/movies-db-service/movies-db/Cargo.toml @@ -14,6 +14,8 @@ serde = { version = "1.0", features = ["derive"] } hyper = { version = "0.14", features = ["full"] } tokio = { version = "1", features = ["full"] } futures = { version = "0.3" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [dev-dependencies] tempdir = "0.3" diff --git a/movies-db-service/movies-db/src/options.rs b/movies-db-service/movies-db/src/options.rs index ac7b52a..b15fa0a 100644 --- a/movies-db-service/movies-db/src/options.rs +++ b/movies-db-service/movies-db/src/options.rs @@ -1,14 +1,19 @@ -use std::path::PathBuf; +use std::{net::SocketAddr, path::PathBuf}; /// The options for the service +#[derive(Debug, Clone)] pub struct Options { pub root_dir: PathBuf, + + /// The address to bind the HTTP server to. + pub http_address: SocketAddr, } impl Default for Options { fn default() -> Self { Self { root_dir: PathBuf::from("./"), + http_address: SocketAddr::from(([127, 0, 0, 1], 3030)), } } } diff --git a/movies-db-service/movies-db/src/service.rs b/movies-db-service/movies-db/src/service.rs deleted file mode 100644 index 6ed61d7..0000000 --- a/movies-db-service/movies-db/src/service.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::convert::Infallible; - -use hyper::service::{make_service_fn, service_fn}; -use log::info; - -use crate::{Error, MoviesIndex, Options}; - -pub struct Service -where - I: MoviesIndex, -{ - index: I, -} - -impl Service -where - I: MoviesIndex, -{ - /// Creates a new instance of the service. - /// - /// # Arguments - /// * `options` - The options for the service. - pub fn new(options: &Options) -> Result { - let index = I::new(options)?; - - Ok(Self { index }) - } - - /// Runs the service. - pub async fn run(&self) -> Result<(), Error> { - info!("Running the service..."); - - Ok(()) - } - - fn run_http_server(&self) { - info!("Running the HTTP server..."); - - // create service handler - let make_svc = make_service_fn(move |_conn| { - async move { - // This is the request handler. - Ok::<_, Infallible>(service_fn(move |req| Self::serve_req(req, handler))) - } - }); - } -} diff --git a/movies-db-service/movies-db/src/service/mod.rs b/movies-db-service/movies-db/src/service/mod.rs new file mode 100644 index 0000000..1b79f05 --- /dev/null +++ b/movies-db-service/movies-db/src/service/mod.rs @@ -0,0 +1,4 @@ +mod service; +mod service_handler; + +pub use service::*; diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs new file mode 100644 index 0000000..dabf626 --- /dev/null +++ b/movies-db-service/movies-db/src/service/service.rs @@ -0,0 +1,77 @@ +use std::{convert::Infallible, sync::Arc}; + +use hyper::{ + service::{make_service_fn, service_fn}, + Server, +}; +use log::info; + +use crate::{Error, MovieStorage, MoviesIndex, Options}; + +pub type BoxError = Box; + +use hyper::{Body, Request, Response}; + +use super::service_handler::ServiceHandler; + +pub struct Service +where + I: MoviesIndex, + S: MovieStorage, +{ + handler: Arc>, + options: Options, +} + +impl Service +where + I: MoviesIndex, + S: MovieStorage, +{ + /// Creates a new instance of the service. + /// + /// # Arguments + /// * `options` - The options for the service. + pub fn new(options: &Options) -> Result { + let handler: ServiceHandler = ServiceHandler::new(options.clone()); + let handler = Arc::new(handler); + + let options = options.clone(); + + Ok(Self { handler, options }) + } + + /// Runs the service. + pub async fn run(&self) -> Result<(), Error> { + info!("Running the service..."); + + Ok(()) + } + + fn run_http_server(&self) { + info!("Running the HTTP server..."); + + let handler = self.handler.clone(); + + // create service handler + let make_svc = make_service_fn(move |_conn| { + async move { + // This is the request handler. + Ok::<_, Infallible>(service_fn(move |req| Self::serve_req(req, handler))) + } + }); + + // create server instance + let server = Server::bind(&self.options.http_address).serve(make_svc); + } + + async fn serve_req( + req: Request, + shared: Arc>, + ) -> Result, BoxError> { + match shared.handle(req).await { + Ok(response) => Ok(response), + Err(err) => panic!("Unexpected server error due to {}", err), + } + } +} diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs new file mode 100644 index 0000000..2394bbb --- /dev/null +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -0,0 +1,145 @@ +use log::debug; + +use hyper::{ + body::to_bytes, + header::{ + ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, + ACCESS_CONTROL_MAX_AGE, CONTENT_TYPE, + }, + Body, HeaderMap, Method, Request, Response, StatusCode, +}; + +use std::convert::Infallible; + +use crate::{MovieStorage, MoviesIndex, Options}; + +pub struct ServiceHandler +where + I: MoviesIndex, + S: MovieStorage, +{ + index: I, + storage: S, +} + +impl ServiceHandler +where + I: MoviesIndex, + S: MovieStorage, +{ + /// Creates a new instance of the service handler. + /// + /// # Arguments + /// * `options` - The options for the service handler. + pub fn new(options: Options) -> Self { + let index = I::new(&options).unwrap(); + let storage = S::new(&options).unwrap(); + + Self { index, storage } + } + + /// Handles the HTTP request. + /// + /// # Arguments + /// * `req` - The HTTP request to handle. + pub async fn handle(&self, req: Request) -> Result, Infallible> { + debug!("Got Request {:?}", req); + + // check for options request + if req.method() == Method::OPTIONS { + let mut response = Self::status_ok(); + Self::patch_cors_header(response.headers_mut()); + + return Ok(response); + } + + let uri = req.uri().clone(); + let path = uri.path(); + + if path.starts_with("/api/v1/") { + return self.handle_v1(req).await; + } else { + Ok(Self::invalid_request( + "Invalid or missing version".to_owned(), + )) + } + } + + /// Handles the HTTP request for the v1 API. + /// + /// # Arguments + /// * `req` - The HTTP request to handle. + async fn handle_v1(&self, req: Request) -> Result, Infallible> { + let uri = req.uri().clone(); + let path = uri.path(); + + if path == "/api/v1/movies" { + match req.method() { + Method::GET => { + let response = self.handle_v1_movies_get(req).await; + return Ok(response); + } + _ => { + let mut response = Self::invalid_request("Invalid Request".to_owned()); + Self::patch_cors_header(response.headers_mut()); + + return Ok(response); + } + } + } + + let mut response = Self::status_ok(); + Self::patch_cors_header(response.headers_mut()); + + Ok(response) + } + + /// Handles the HTTP GET request for the v1 API by returning a list of movies. + /// + /// # Arguments + /// * `req` - The HTTP request to handle. + async fn handle_v1_movies_get(&self, req: Request) -> Result, Infallible> { + let mut response = Self::status_ok(); + Self::patch_cors_header(response.headers_mut()); + + let movies = self.index.list_movies(); + let movies = serde_json::to_string(&movies).unwrap(); + + *response.body_mut() = Body::from(movies); + + Ok(response) + } + + /// Sets the CORS headers in the header map. + /// + /// # Arguments + /// * `header` - The header map to set the CORS headers in. + fn patch_cors_header(header: &mut HeaderMap) { + header.insert(ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap()); + header.insert(ACCESS_CONTROL_ALLOW_METHODS, "*".parse().unwrap()); + header.insert(ACCESS_CONTROL_ALLOW_HEADERS, "*".parse().unwrap()); + header.insert(ACCESS_CONTROL_MAX_AGE, "1728000".parse().unwrap()); + } + + /// Creates a new response with status code 200. + fn status_ok() -> Response { + let response = Response::new(Body::empty()); + response + } + + /// Creates a new response with status code 400. + /// + /// # Arguments + /// * `err_str` - The error string to return in the response. + fn invalid_request(err_str: String) -> Response { + let mut response = Response::new(Body::from(err_str)); + + response + .headers_mut() + .insert(CONTENT_TYPE, "text/plain".parse().unwrap()); + + *response.status_mut() = StatusCode::BAD_REQUEST; + + response + } +} diff --git a/movies-db-service/movies-db/src/storage/mod.rs b/movies-db-service/movies-db/src/storage/mod.rs index d6f1770..fb4e785 100644 --- a/movies-db-service/movies-db/src/storage/mod.rs +++ b/movies-db-service/movies-db/src/storage/mod.rs @@ -1,2 +1,4 @@ -mod file_storage; +pub mod file_storage; mod movies_storage; + +pub use movies_storage::*; From 682726e2716a4691a1e0ff0f7a0939f69545b4b7 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 11:49:00 +0200 Subject: [PATCH 03/17] changed: using actix-web instead of tokio --- movies-db-service/movies-db-cli/Cargo.toml | 2 +- movies-db-service/movies-db-cli/src/main.rs | 10 +- .../movies-db-cli/src/options.rs | 18 ++- movies-db-service/movies-db/Cargo.toml | 4 +- .../movies-db/src/service/service.rs | 72 ++++++----- .../movies-db/src/service/service_handler.rs | 118 ------------------ .../movies-db/src/storage/file_storage.rs | 6 +- 7 files changed, 67 insertions(+), 163 deletions(-) diff --git a/movies-db-service/movies-db-cli/Cargo.toml b/movies-db-service/movies-db-cli/Cargo.toml index 7ed6ee6..e58f202 100644 --- a/movies-db-service/movies-db-cli/Cargo.toml +++ b/movies-db-service/movies-db-cli/Cargo.toml @@ -11,4 +11,4 @@ log = "0.4" anyhow = "1.0" simple-logging = "2.0" clap = { version = "4.2", features = ["derive"] } -tokio = { version = "1.32", features = ["full"] } +actix-web = "4" diff --git a/movies-db-service/movies-db-cli/src/main.rs b/movies-db-service/movies-db-cli/src/main.rs index ef61343..119becb 100644 --- a/movies-db-service/movies-db-cli/src/main.rs +++ b/movies-db-service/movies-db-cli/src/main.rs @@ -2,7 +2,7 @@ mod options; use anyhow::Result; use log::{error, info}; -use movies_db::{Options as ServiceOptions, Service, SimpleMoviesIndex}; +use movies_db::{file_storage::FileStorage, Options as ServiceOptions, Service, SimpleMoviesIndex}; use options::Options; use clap::Parser; @@ -25,17 +25,15 @@ async fn run_program() -> Result<()> { let options = parse_args()?; initialize_logging(LevelFilter::from(options.log_level)); - let service_options = ServiceOptions { - root_dir: options.root_dir, - }; + let service_options: ServiceOptions = options.into(); - let service: Service = Service::new(&service_options)?; + let service: Service = Service::new(&service_options)?; service.run().await?; Ok(()) } -#[tokio::main] +#[actix_web::main] async fn main() { match run_program().await { Ok(()) => { diff --git a/movies-db-service/movies-db-cli/src/options.rs b/movies-db-service/movies-db-cli/src/options.rs index 857b6a5..d218b00 100644 --- a/movies-db-service/movies-db-cli/src/options.rs +++ b/movies-db-service/movies-db-cli/src/options.rs @@ -3,6 +3,8 @@ use std::path::PathBuf; use clap::{Parser, ValueEnum}; use log::LevelFilter; +use movies_db::Options as ServiceOptions; + #[derive(ValueEnum, Clone, Copy, Debug)] pub enum LogLevel { Trace, @@ -24,7 +26,6 @@ impl From for LevelFilter { } } - /// CLI interface to test different occlusion culler algorithms. #[derive(Parser, Debug, Clone)] #[command(author, version, about, long_about = None)] @@ -33,7 +34,20 @@ pub struct Options { #[arg(short, value_enum, long, default_value_t = LogLevel::Info)] pub log_level: LogLevel, + /// The address to bind the http server to + #[arg(short, value_enum, long, default_value = "0.0.0.0:3030")] + pub address: String, + /// The path to the root directory #[arg(short, long)] pub root_dir: PathBuf, -} \ No newline at end of file +} + +impl Into for Options { + fn into(self) -> ServiceOptions { + ServiceOptions { + root_dir: self.root_dir, + http_address: self.address.parse().unwrap(), + } + } +} diff --git a/movies-db-service/movies-db/Cargo.toml b/movies-db-service/movies-db/Cargo.toml index 9019b28..0b14964 100644 --- a/movies-db-service/movies-db/Cargo.toml +++ b/movies-db-service/movies-db/Cargo.toml @@ -10,9 +10,7 @@ itertools = "0.11" uuid = { version = "1.4", features = ["v4", "fast-rng", "macro-diagnostics"] } chrono = { version = "0.4", features = ["serde"] } wildmatch = "2.1" -serde = { version = "1.0", features = ["derive"] } -hyper = { version = "0.14", features = ["full"] } -tokio = { version = "1", features = ["full"] } +actix-web = "4" futures = { version = "0.3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index dabf626..6b68d55 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,17 +1,13 @@ -use std::{convert::Infallible, sync::Arc}; +use std::sync::Arc; -use hyper::{ - service::{make_service_fn, service_fn}, - Server, -}; -use log::info; +use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; + +use log::{error, info}; use crate::{Error, MovieStorage, MoviesIndex, Options}; pub type BoxError = Box; -use hyper::{Body, Request, Response}; - use super::service_handler::ServiceHandler; pub struct Service @@ -45,33 +41,51 @@ where pub async fn run(&self) -> Result<(), Error> { info!("Running the service..."); + match self.run_http_server().await { + Err(err) => { + error!("Running the service...FAILED"); + error!("Error: {}", err); + return Err(err); + } + Ok(()) => { + info!("Running the service...STOPPED"); + } + } + Ok(()) } - fn run_http_server(&self) { + async fn run_http_server(&self) -> Result<(), Error> { info!("Running the HTTP server..."); - let handler = self.handler.clone(); - - // create service handler - let make_svc = make_service_fn(move |_conn| { - async move { - // This is the request handler. - Ok::<_, Infallible>(service_fn(move |req| Self::serve_req(req, handler))) + info!("Listening on {}", self.options.http_address); + + match HttpServer::new(|| { + App::new().service(web::resource("/").to(|| async { "hello world" })) + }) + .bind(self.options.http_address.clone())? + .run() + .await + { + Err(err) => { + error!("Running the HTTP server...FAILED"); + error!("Error: {}", err); + Err(err.into()) + } + Ok(_) => { + info!("Running the HTTP server...STOPPED"); + Ok(()) } - }); - - // create server instance - let server = Server::bind(&self.options.http_address).serve(make_svc); - } - - async fn serve_req( - req: Request, - shared: Arc>, - ) -> Result, BoxError> { - match shared.handle(req).await { - Ok(response) => Ok(response), - Err(err) => panic!("Unexpected server error due to {}", err), } } + + // async fn serve_req( + // req: Request, + // shared: Arc>, + // ) -> Result, BoxError> { + // match shared.handle(req).await { + // Ok(response) => Ok(response), + // Err(err) => panic!("Unexpected server error due to {}", err), + // } + // } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 2394bbb..e0f1d97 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,16 +1,3 @@ -use log::debug; - -use hyper::{ - body::to_bytes, - header::{ - ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, - ACCESS_CONTROL_MAX_AGE, CONTENT_TYPE, - }, - Body, HeaderMap, Method, Request, Response, StatusCode, -}; - -use std::convert::Infallible; - use crate::{MovieStorage, MoviesIndex, Options}; pub struct ServiceHandler @@ -37,109 +24,4 @@ where Self { index, storage } } - - /// Handles the HTTP request. - /// - /// # Arguments - /// * `req` - The HTTP request to handle. - pub async fn handle(&self, req: Request) -> Result, Infallible> { - debug!("Got Request {:?}", req); - - // check for options request - if req.method() == Method::OPTIONS { - let mut response = Self::status_ok(); - Self::patch_cors_header(response.headers_mut()); - - return Ok(response); - } - - let uri = req.uri().clone(); - let path = uri.path(); - - if path.starts_with("/api/v1/") { - return self.handle_v1(req).await; - } else { - Ok(Self::invalid_request( - "Invalid or missing version".to_owned(), - )) - } - } - - /// Handles the HTTP request for the v1 API. - /// - /// # Arguments - /// * `req` - The HTTP request to handle. - async fn handle_v1(&self, req: Request) -> Result, Infallible> { - let uri = req.uri().clone(); - let path = uri.path(); - - if path == "/api/v1/movies" { - match req.method() { - Method::GET => { - let response = self.handle_v1_movies_get(req).await; - return Ok(response); - } - _ => { - let mut response = Self::invalid_request("Invalid Request".to_owned()); - Self::patch_cors_header(response.headers_mut()); - - return Ok(response); - } - } - } - - let mut response = Self::status_ok(); - Self::patch_cors_header(response.headers_mut()); - - Ok(response) - } - - /// Handles the HTTP GET request for the v1 API by returning a list of movies. - /// - /// # Arguments - /// * `req` - The HTTP request to handle. - async fn handle_v1_movies_get(&self, req: Request) -> Result, Infallible> { - let mut response = Self::status_ok(); - Self::patch_cors_header(response.headers_mut()); - - let movies = self.index.list_movies(); - let movies = serde_json::to_string(&movies).unwrap(); - - *response.body_mut() = Body::from(movies); - - Ok(response) - } - - /// Sets the CORS headers in the header map. - /// - /// # Arguments - /// * `header` - The header map to set the CORS headers in. - fn patch_cors_header(header: &mut HeaderMap) { - header.insert(ACCESS_CONTROL_ALLOW_ORIGIN, "*".parse().unwrap()); - header.insert(ACCESS_CONTROL_ALLOW_METHODS, "*".parse().unwrap()); - header.insert(ACCESS_CONTROL_ALLOW_HEADERS, "*".parse().unwrap()); - header.insert(ACCESS_CONTROL_MAX_AGE, "1728000".parse().unwrap()); - } - - /// Creates a new response with status code 200. - fn status_ok() -> Response { - let response = Response::new(Body::empty()); - response - } - - /// Creates a new response with status code 400. - /// - /// # Arguments - /// * `err_str` - The error string to return in the response. - fn invalid_request(err_str: String) -> Response { - let mut response = Response::new(Body::from(err_str)); - - response - .headers_mut() - .insert(CONTENT_TYPE, "text/plain".parse().unwrap()); - - *response.status_mut() = StatusCode::BAD_REQUEST; - - response - } } diff --git a/movies-db-service/movies-db/src/storage/file_storage.rs b/movies-db-service/movies-db/src/storage/file_storage.rs index a3c99c8..7c53332 100644 --- a/movies-db-service/movies-db/src/storage/file_storage.rs +++ b/movies-db-service/movies-db/src/storage/file_storage.rs @@ -141,10 +141,8 @@ mod test { #[test] fn test_write_movie_data() { let root_dir = TempDir::new("movies-db").unwrap(); - - let options = crate::Options { - root_dir: root_dir.path().to_path_buf(), - }; + let mut options: Options = Default::default(); + options.root_dir = root_dir.path().to_path_buf(); let storage = FileStorage::new(&options).unwrap(); From b9b49be49446a71f99d40bc10d3f2f1668d5ffde Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 12:21:37 +0200 Subject: [PATCH 04/17] changed: Service handler can now be shared as mutable state --- .../movies-db/src/db/movies_index.rs | 2 +- .../movies-db/src/service/service.rs | 48 +++++++++++-------- .../movies-db/src/service/service_handler.rs | 10 ++-- .../movies-db/src/storage/movies_storage.rs | 2 +- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/movies-db-service/movies-db/src/db/movies_index.rs b/movies-db-service/movies-db/src/db/movies_index.rs index 811788f..729110d 100644 --- a/movies-db-service/movies-db/src/db/movies_index.rs +++ b/movies-db-service/movies-db/src/db/movies_index.rs @@ -82,7 +82,7 @@ pub struct MovieSearchQuery { /// The movies index manages a list of all movies in the database. /// Additionally, it provides methods for managing and searching movies. -pub trait MoviesIndex { +pub trait MoviesIndex: Send + Sync { /// Creates a new instance of the movies index /// /// # Arguments diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 6b68d55..ebf9bb1 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{marker::PhantomData, sync::RwLock}; use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; @@ -10,13 +10,13 @@ pub type BoxError = Box; use super::service_handler::ServiceHandler; -pub struct Service +pub struct Service where I: MoviesIndex, S: MovieStorage, { - handler: Arc>, options: Options, + phantom: PhantomData<(I, S)>, } impl Service @@ -29,12 +29,10 @@ where /// # Arguments /// * `options` - The options for the service. pub fn new(options: &Options) -> Result { - let handler: ServiceHandler = ServiceHandler::new(options.clone()); - let handler = Arc::new(handler); - let options = options.clone(); + let phantom = PhantomData {}; - Ok(Self { handler, options }) + Ok(Self { phantom, options }) } /// Runs the service. @@ -55,13 +53,19 @@ where Ok(()) } + /// Runs the HTTP server. async fn run_http_server(&self) -> Result<(), Error> { - info!("Running the HTTP server..."); + let handler = self.create_service_handler()?; + let handler = RwLock::new(handler); + let handler = web::Data::new(handler); + info!("Running the HTTP server..."); info!("Listening on {}", self.options.http_address); - match HttpServer::new(|| { - App::new().service(web::resource("/").to(|| async { "hello world" })) + match HttpServer::new(move || { + App::new() + .app_data(handler.clone()) + .service(web::resource("/api/v1").to(|| async { "hello world" })) }) .bind(self.options.http_address.clone())? .run() @@ -79,13 +83,19 @@ where } } - // async fn serve_req( - // req: Request, - // shared: Arc>, - // ) -> Result, BoxError> { - // match shared.handle(req).await { - // Ok(response) => Ok(response), - // Err(err) => panic!("Unexpected server error due to {}", err), - // } - // } + /// Creates a new instance of the service handler. + fn create_service_handler(&self) -> Result, Error> { + info!("Creating the service handler..."); + match ServiceHandler::new(self.options.clone()) { + Err(err) => { + error!("Creating the service handler...FAILED"); + error!("Error: {}", err); + Err(err) + } + Ok(handler) => { + info!("Creating the service handler...OK"); + Ok(handler) + } + } + } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index e0f1d97..29d127b 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,4 +1,4 @@ -use crate::{MovieStorage, MoviesIndex, Options}; +use crate::{Error, MovieStorage, MoviesIndex, Options}; pub struct ServiceHandler where @@ -18,10 +18,10 @@ where /// /// # Arguments /// * `options` - The options for the service handler. - pub fn new(options: Options) -> Self { - let index = I::new(&options).unwrap(); - let storage = S::new(&options).unwrap(); + pub fn new(options: Options) -> Result { + let index = I::new(&options)?; + let storage = S::new(&options)?; - Self { index, storage } + Ok(Self { index, storage }) } } diff --git a/movies-db-service/movies-db/src/storage/movies_storage.rs b/movies-db-service/movies-db/src/storage/movies_storage.rs index 77819f7..ab2061e 100644 --- a/movies-db-service/movies-db/src/storage/movies_storage.rs +++ b/movies-db-service/movies-db/src/storage/movies_storage.rs @@ -11,7 +11,7 @@ pub enum MovieDataType { } /// The trait for storing movie data. -pub trait MovieStorage { +pub trait MovieStorage: Send + Sync { type W: Write; type R: Read; From a2f8a21366d4d2e5f2df05be994af1a4da1273dc Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 14:43:29 +0200 Subject: [PATCH 05/17] added: endpoint for list of all movies --- .../movies-db/src/service/service.rs | 15 +++++++--- .../movies-db/src/service/service_handler.rs | 29 ++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index ebf9bb1..881cfd0 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,6 +1,6 @@ use std::{marker::PhantomData, sync::RwLock}; -use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder}; +use actix_web::{get, post, web, App, HttpServer, Responder, Result}; use log::{error, info}; @@ -63,9 +63,10 @@ where info!("Listening on {}", self.options.http_address); match HttpServer::new(move || { - App::new() - .app_data(handler.clone()) - .service(web::resource("/api/v1").to(|| async { "hello world" })) + let api_v1 = + web::scope("/api/v1").route("/movie", web::get().to(Self::handle_show_list)); + + App::new().app_data(handler.clone()).service(api_v1) }) .bind(self.options.http_address.clone())? .run() @@ -98,4 +99,10 @@ where } } } + + async fn handle_show_list( + handler: web::Data>>, + ) -> Result { + handler.read().unwrap().handle_show_list().await + } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 29d127b..f4bf312 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,4 +1,7 @@ -use crate::{Error, MovieStorage, MoviesIndex, Options}; +use crate::{Error, MovieId, MovieStorage, MoviesIndex, Options}; + +use actix_web::{web, Responder, Result}; +use serde::{Deserialize, Serialize}; pub struct ServiceHandler where @@ -9,6 +12,12 @@ where storage: S, } +#[derive(Debug, Serialize, Deserialize)] +struct MovieListEntry { + id: MovieId, + title: String, +} + impl ServiceHandler where I: MoviesIndex, @@ -24,4 +33,22 @@ where Ok(Self { index, storage }) } + + /// Handles the request to show the list of all movies. + pub async fn handle_show_list(&self) -> Result { + let movies_ids = self.index.list_movies(); + + let movies: Vec = movies_ids + .iter() + .map(|id| { + let movie = self.index.get_movie(id).unwrap(); + MovieListEntry { + id: id.clone(), + title: movie.movie.title, + } + }) + .collect(); + + Ok(web::Json(movies)) + } } From 40b714bd73f4f01f097fcdda170ed1347eefc4be Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 17:10:45 +0200 Subject: [PATCH 06/17] changed: renamed to GET handler for movie endpoint --- movies-db-service/movies-db/src/service/service.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 881cfd0..c4c8aed 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -64,7 +64,7 @@ where match HttpServer::new(move || { let api_v1 = - web::scope("/api/v1").route("/movie", web::get().to(Self::handle_show_list)); + web::scope("/api/v1").route("/movie", web::get().to(Self::handle_get_movie)); App::new().app_data(handler.clone()).service(api_v1) }) @@ -100,7 +100,7 @@ where } } - async fn handle_show_list( + async fn handle_get_movie( handler: web::Data>>, ) -> Result { handler.read().unwrap().handle_show_list().await From 20610b29ed708c7fdc679be28f029e92bb999f94 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 18:55:04 +0200 Subject: [PATCH 07/17] fixed: Replaced removed method by new one --- .../movies-db/src/service/service_handler.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index f4bf312..4ece7ec 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,6 +1,7 @@ use crate::{Error, MovieId, MovieStorage, MoviesIndex, Options}; use actix_web::{web, Responder, Result}; +use log::error; use serde::{Deserialize, Serialize}; pub struct ServiceHandler @@ -36,7 +37,13 @@ where /// Handles the request to show the list of all movies. pub async fn handle_show_list(&self) -> Result { - let movies_ids = self.index.list_movies(); + let movies_ids = match self.index.search_movies(Default::default()) { + Ok(movies_ids) => movies_ids, + Err(err) => { + error!("Internal error: {}", err); + return Err(actix_web::error::ErrorInternalServerError(err)); + } + }; let movies: Vec = movies_ids .iter() From 9380443758bff0fc047db1aa7b34367a1c7b7c75 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 21:44:57 +0200 Subject: [PATCH 08/17] added: code for adding new movie --- .../movies-db/src/db/movies_index.rs | 2 ++ .../movies-db/src/service/service.rs | 29 +++++++++++++++---- .../movies-db/src/service/service_handler.rs | 28 ++++++++++++++++-- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/movies-db-service/movies-db/src/db/movies_index.rs b/movies-db-service/movies-db/src/db/movies_index.rs index 729110d..fc48f63 100644 --- a/movies-db-service/movies-db/src/db/movies_index.rs +++ b/movies-db-service/movies-db/src/db/movies_index.rs @@ -12,9 +12,11 @@ pub struct Movie { pub title: String, /// An optional description of the movie. + #[serde(default)] pub description: String, /// A list of tags associated with the movie. + #[serde(default)] pub tags: Vec, } diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index c4c8aed..7f59bf1 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,10 +1,14 @@ use std::{marker::PhantomData, sync::RwLock}; -use actix_web::{get, post, web, App, HttpServer, Responder, Result}; +use actix_web::{ + get, post, + web::{self, Json}, + App, HttpServer, Responder, Result, +}; -use log::{error, info}; +use log::{debug, error, info, trace}; -use crate::{Error, MovieStorage, MoviesIndex, Options}; +use crate::{Error, Movie, MovieSearchQuery, MovieStorage, MoviesIndex, Options}; pub type BoxError = Box; @@ -64,7 +68,7 @@ where match HttpServer::new(move || { let api_v1 = - web::scope("/api/v1").route("/movie", web::get().to(Self::handle_get_movie)); + web::scope("/api/v1").route("/movie", web::post().to(Self::handle_post_movie)); App::new().app_data(handler.clone()).service(api_v1) }) @@ -100,9 +104,22 @@ where } } - async fn handle_get_movie( + /// Handles the POST /api/v1/movie endpoint. + /// + /// # Arguments + /// * `handler` - The service handler. + /// * `movie` - The movie to add. + async fn handle_post_movie( handler: web::Data>>, + movie: web::Json, ) -> Result { - handler.read().unwrap().handle_show_list().await + debug!("Handling POST /api/v1/movie"); + trace!("Request body: {:?}", movie); + + let movie: Movie = movie.into_inner(); + + let mut handler: std::sync::RwLockWriteGuard<'_, ServiceHandler> = + handler.write().unwrap(); + handler.handle_add_movie(movie) } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 4ece7ec..93558c2 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,4 +1,4 @@ -use crate::{Error, MovieId, MovieStorage, MoviesIndex, Options}; +use crate::{Error, Movie, MovieId, MovieSearchQuery, MovieStorage, MoviesIndex, Options}; use actix_web::{web, Responder, Result}; use log::error; @@ -35,9 +35,31 @@ where Ok(Self { index, storage }) } + /// Handles the request to add a new movie. + /// + /// # Arguments + /// * `movie` - The movie to add. + pub fn handle_add_movie(&mut self, movie: Movie) -> Result { + let movie_id = match self.index.add_movie(movie) { + Ok(movie_id) => movie_id, + Err(err) => match err { + Error::InvalidArgument(e) => { + error!("Invalid argument: {}", e); + return Err(actix_web::error::ErrorBadRequest(e)); + } + _ => { + error!("Internal error: {}", err); + return Err(actix_web::error::ErrorInternalServerError(err)); + } + }, + }; + + Ok(web::Json(movie_id)) + } + /// Handles the request to show the list of all movies. - pub async fn handle_show_list(&self) -> Result { - let movies_ids = match self.index.search_movies(Default::default()) { + pub async fn handle_search_movies(&self, query: MovieSearchQuery) -> Result { + let movies_ids = match self.index.search_movies(query) { Ok(movies_ids) => movies_ids, Err(err) => { error!("Internal error: {}", err); From 04bbfa88c05520bd04875b10f5b20737c074d200 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 22:30:40 +0200 Subject: [PATCH 09/17] added: search endpoint --- .../movies-db/src/db/movies_index.rs | 58 +++++++------- .../movies-db/src/db/simple_movies_index.rs | 79 +++++++++---------- .../movies-db/src/service/service.rs | 19 ++++- .../movies-db/src/service/service_handler.rs | 2 +- 4 files changed, 85 insertions(+), 73 deletions(-) diff --git a/movies-db-service/movies-db/src/db/movies_index.rs b/movies-db-service/movies-db/src/db/movies_index.rs index fc48f63..d081342 100644 --- a/movies-db-service/movies-db/src/db/movies_index.rs +++ b/movies-db-service/movies-db/src/db/movies_index.rs @@ -1,5 +1,3 @@ -use std::ops::Range; - use crate::{Error, MovieId, Options}; use chrono::{DateTime, Utc}; @@ -37,6 +35,12 @@ pub enum SortingField { Date, } +impl Default for SortingField { + fn default() -> Self { + Self::Date + } +} + /// The sorting order for the movies. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub enum SortingOrder { @@ -47,27 +51,22 @@ pub enum SortingOrder { Descending, } -/// A sorting for the movies. -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct MovieSorting { - pub field: SortingField, - pub order: SortingOrder, -} - -impl Default for MovieSorting { +impl Default for SortingOrder { fn default() -> Self { - Self { - field: SortingField::Date, - order: SortingOrder::Descending, - } + Self::Descending } } /// A query for searching movies in the database. #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct MovieSearchQuery { - /// The sorting being used for the movies. - pub sorting: MovieSorting, + /// The field used for sorting + #[serde(default)] + pub sorting_field: SortingField, + + /// The order used for sorting + #[serde(default)] + pub sorting_order: SortingOrder, /// Optionally, a search string for the title of the movie. If provided, only movies whose /// title matches the search string will be returned. @@ -78,8 +77,11 @@ pub struct MovieSearchQuery { #[serde(default)] pub tags: Vec, - /// Optionally, a range for the items to return can be specified. - pub range: Option>, + /// Optionally, the start index of the movies to return. + pub start_index: Option, + + /// Optionally, the maximal number of results to return. + pub num_results: Option, } /// The movies index manages a list of all movies in the database. @@ -147,10 +149,8 @@ mod test { fn test_query_serialization() { let query_string = r#" { - "sorting": { - "field": "title", - "order": "ascending" - }, + "sorting_field": "title", + "sorting_order": "ascending", "title": "foo", "tags": ["bar", "baz"] } @@ -160,15 +160,13 @@ mod test { assert_eq!(query.title, Some("foo".to_string())); assert_eq!(query.tags, vec!["bar".to_string(), "baz".to_string()]); - assert_eq!(query.sorting.field, SortingField::Title); - assert_eq!(query.sorting.order, SortingOrder::Ascending); + assert_eq!(query.sorting_field, SortingField::Title); + assert_eq!(query.sorting_order, SortingOrder::Ascending); let query_string = r#" { - "sorting": { - "field": "date", - "order": "descending" - }, + "sorting_field": "date", + "sorting_order": "descending", "title": "foo" } "#; @@ -177,7 +175,7 @@ mod test { assert_eq!(query.title, Some("foo".to_string())); assert!(query.tags.is_empty()); - assert_eq!(query.sorting.field, SortingField::Date); - assert_eq!(query.sorting.order, SortingOrder::Descending); + assert_eq!(query.sorting_field, SortingField::Date); + assert_eq!(query.sorting_order, SortingOrder::Descending); } } diff --git a/movies-db-service/movies-db/src/db/simple_movies_index.rs b/movies-db-service/movies-db/src/db/simple_movies_index.rs index 6518756..221b03c 100644 --- a/movies-db-service/movies-db/src/db/simple_movies_index.rs +++ b/movies-db-service/movies-db/src/db/simple_movies_index.rs @@ -5,8 +5,8 @@ use log::{debug, error, info}; use wildmatch::WildMatch; use crate::{ - generate_movie_id, Error, Movie, MovieId, MovieSearchQuery, MovieSorting, MovieWithDate, - MoviesIndex, Options, SortingField, SortingOrder, + generate_movie_id, Error, Movie, MovieId, MovieSearchQuery, MovieWithDate, MoviesIndex, + Options, SortingField, SortingOrder, }; /// A very simple and naive in-memory implementation of the movies index. @@ -141,15 +141,17 @@ impl MoviesIndex for SimpleMoviesIndex { Self::process_tags(&mut query.tags); // get sorted movie ids - let in_movie_ids = self.get_movies_sorted(query.sorting); + let in_movie_ids = self.get_movies_sorted(query.sorting_field, query.sorting_order); // create wildcard query if provided let title_query: Option = query.title.map(|s| WildMatch::new(&s)); - let range = query.range.unwrap_or(Range { - start: 0, - end: usize::MAX, - }); + let start_index = query.start_index.unwrap_or(0); + let end_index = match query.num_results { + Some(num_results) => start_index + num_results, + None => usize::MAX, + }; + let mut num_hits = 0usize; let mut movie_ids = Vec::new(); for id in in_movie_ids.iter() { @@ -183,9 +185,9 @@ impl MoviesIndex for SimpleMoviesIndex { } // add movie id if index is within range - if num_hits >= range.start && range.end > num_hits { + if num_hits >= start_index && end_index > num_hits { movie_ids.push(id.clone()); - } else if num_hits >= range.end { + } else if num_hits >= end_index { break; } @@ -200,11 +202,12 @@ impl SimpleMoviesIndex { /// Returns a list of all movies sorted according to the given sorting parameter. /// /// # Arguments - /// * `sorting` - The sorting parameter. - fn get_movies_sorted(&self, sorting: MovieSorting) -> Vec { - match sorting.field { - SortingField::Title => self.get_movies_sorted_by_title(sorting.order), - SortingField::Date => self.get_movies_sorted_by_date(sorting.order), + /// * `field` - The field by which the movies should be sorted. + /// * `order` - The order in which the movies should be sorted. + fn get_movies_sorted(&self, field: SortingField, order: SortingOrder) -> Vec { + match field { + SortingField::Title => self.get_movies_sorted_by_title(order), + SortingField::Date => self.get_movies_sorted_by_date(order), } } @@ -324,10 +327,8 @@ mod test { // test query 1A: Search all movies (ascending order by title) let mut query: MovieSearchQuery = Default::default(); - query.sorting = MovieSorting { - field: SortingField::Title, - order: SortingOrder::Ascending, - }; + query.sorting_field = SortingField::Title; + query.sorting_order = SortingOrder::Ascending; let movie_title: Vec = index .search_movies(query) .unwrap() @@ -346,10 +347,8 @@ mod test { // test query 1B: Search all movies (descending order by title) let mut query: MovieSearchQuery = Default::default(); - query.sorting = MovieSorting { - field: SortingField::Title, - order: SortingOrder::Descending, - }; + query.sorting_field = SortingField::Title; + query.sorting_order = SortingOrder::Descending; let movie_title: Vec = index .search_movies(query) .unwrap() @@ -369,10 +368,8 @@ mod test { // // test query 2: Search only science fiction movies let mut query: MovieSearchQuery = Default::default(); query.tags = vec!["Sci-Fi".to_owned()]; - query.sorting = MovieSorting { - field: SortingField::Title, - order: SortingOrder::Ascending, - }; + query.sorting_field = SortingField::Title; + query.sorting_order = SortingOrder::Ascending; let search_result = index.search_movies(query).unwrap(); let title_list = search_result .iter() @@ -388,17 +385,21 @@ mod test { // test query 3: Search 'Das Boot' let query = MovieSearchQuery { - sorting: Default::default(), + sorting_field: Default::default(), + sorting_order: Default::default(), title: Some("Boot".to_owned()), tags: vec![], - range: None, + start_index: None, + num_results: None, }; assert_eq!(index.search_movies(query).unwrap().len(), 0); let query = MovieSearchQuery { - sorting: Default::default(), + sorting_field: Default::default(), + sorting_order: Default::default(), title: Some("*Boot".to_owned()), tags: vec![], - range: None, + start_index: None, + num_results: None, }; assert_eq!( index @@ -412,13 +413,12 @@ mod test { // test query 4: Limited ranges let query = MovieSearchQuery { - sorting: MovieSorting { - field: SortingField::Title, - order: SortingOrder::Ascending, - }, + sorting_field: SortingField::Title, + sorting_order: SortingOrder::Ascending, title: None, tags: vec![], - range: Some(Range { start: 0, end: 1 }), + start_index: Some(0), + num_results: Some(1), }; assert_eq!( index @@ -430,13 +430,12 @@ mod test { ["Das Boot"] ); let query = MovieSearchQuery { - sorting: MovieSorting { - field: SortingField::Title, - order: SortingOrder::Ascending, - }, + sorting_field: SortingField::Title, + sorting_order: SortingOrder::Ascending, title: None, tags: vec![], - range: Some(Range { start: 1, end: 3 }), + start_index: Some(1), + num_results: Some(2), }; assert_eq!( index diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 7f59bf1..3783ecd 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -67,8 +67,9 @@ where info!("Listening on {}", self.options.http_address); match HttpServer::new(move || { - let api_v1 = - web::scope("/api/v1").route("/movie", web::post().to(Self::handle_post_movie)); + let api_v1 = web::scope("/api/v1") + .route("/movie", web::post().to(Self::handle_post_movie)) + .route("/movie/search", web::get().to(Self::handle_search_movie)); App::new().app_data(handler.clone()).service(api_v1) }) @@ -122,4 +123,18 @@ where handler.write().unwrap(); handler.handle_add_movie(movie) } + + async fn handle_search_movie( + handler: web::Data>>, + query: web::Query, + ) -> Result { + debug!("Handling GET /api/v1/movie/search"); + trace!("Request query: {:?}", query); + + let query: MovieSearchQuery = query.into_inner(); + + let handler = handler.read().unwrap(); + + handler.handle_search_movies(query) + } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 93558c2..c452883 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -58,7 +58,7 @@ where } /// Handles the request to show the list of all movies. - pub async fn handle_search_movies(&self, query: MovieSearchQuery) -> Result { + pub fn handle_search_movies(&self, query: MovieSearchQuery) -> Result { let movies_ids = match self.index.search_movies(query) { Ok(movies_ids) => movies_ids, Err(err) => { From e2872e2eeaf4e57eebb5f2e3a748c6064f476ef2 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sat, 19 Aug 2023 23:03:44 +0200 Subject: [PATCH 10/17] added: get and delete single movie --- .../movies-db/src/db/simple_movies_index.rs | 2 +- .../movies-db/src/service/service.rs | 61 +++++++++++++++-- .../movies-db/src/service/service_handler.rs | 67 ++++++++++++++----- .../movies-db/src/storage/file_storage.rs | 16 +++++ .../movies-db/src/storage/movies_storage.rs | 6 ++ 5 files changed, 130 insertions(+), 22 deletions(-) diff --git a/movies-db-service/movies-db/src/db/simple_movies_index.rs b/movies-db-service/movies-db/src/db/simple_movies_index.rs index 221b03c..31d49e1 100644 --- a/movies-db-service/movies-db/src/db/simple_movies_index.rs +++ b/movies-db-service/movies-db/src/db/simple_movies_index.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ops::Range}; +use std::collections::HashMap; use chrono::DateTime; use log::{debug, error, info}; diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 3783ecd..6ae1703 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,19 +1,17 @@ use std::{marker::PhantomData, sync::RwLock}; -use actix_web::{ - get, post, - web::{self, Json}, - App, HttpServer, Responder, Result, -}; +use actix_web::{web, App, HttpServer, Responder, Result}; use log::{debug, error, info, trace}; -use crate::{Error, Movie, MovieSearchQuery, MovieStorage, MoviesIndex, Options}; +use crate::{Error, Movie, MovieId, MovieSearchQuery, MovieStorage, MoviesIndex, Options}; pub type BoxError = Box; use super::service_handler::ServiceHandler; +use serde::{Deserialize, Serialize}; + pub struct Service where I: MoviesIndex, @@ -23,6 +21,12 @@ where phantom: PhantomData<(I, S)>, } +/// The query for the GET /api/v1/movie endpoint. +#[derive(Debug, Deserialize, Serialize)] +struct MovieIdQuery { + id: MovieId, +} + impl Service where I: MoviesIndex, @@ -69,6 +73,8 @@ where match HttpServer::new(move || { let api_v1 = web::scope("/api/v1") .route("/movie", web::post().to(Self::handle_post_movie)) + .route("/movie", web::get().to(Self::handle_get_movie)) + .route("/movie", web::delete().to(Self::handle_delete_movie)) .route("/movie/search", web::get().to(Self::handle_search_movie)); App::new().app_data(handler.clone()).service(api_v1) @@ -124,6 +130,11 @@ where handler.handle_add_movie(movie) } + /// Handles the GET /api/v1/movie endpoint. + /// + /// # Arguments + /// * `handler` - The service handler. + /// * `query` - The query parameters. async fn handle_search_movie( handler: web::Data>>, query: web::Query, @@ -137,4 +148,42 @@ where handler.handle_search_movies(query) } + + /// Handles the GET /api/v1/movie endpoint. + /// + /// # Arguments + /// * `handler` - The service handler. + /// * `query` - The query parameters. + async fn handle_get_movie( + handler: web::Data>>, + query: web::Query, + ) -> Result { + debug!("Handling GET /api/v1/movie"); + trace!("Request query: {:?}", query); + + let id: MovieId = query.into_inner().id; + + let handler = handler.read().unwrap(); + + handler.handle_get_movie(id) + } + + /// Handles the DELETE /api/v1/movie endpoint. + /// + /// # Arguments + /// * `handler` - The service handler. + /// * `query` - The query parameters. + async fn handle_delete_movie( + handler: web::Data>>, + query: web::Query, + ) -> Result { + debug!("Handling DELETE /api/v1/movie"); + trace!("Request query: {:?}", query); + + let id: MovieId = query.into_inner().id; + + let mut handler = handler.write().unwrap(); + + handler.handle_delete_movie(id) + } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index c452883..95fb9b4 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -40,21 +40,41 @@ where /// # Arguments /// * `movie` - The movie to add. pub fn handle_add_movie(&mut self, movie: Movie) -> Result { - let movie_id = match self.index.add_movie(movie) { - Ok(movie_id) => movie_id, - Err(err) => match err { - Error::InvalidArgument(e) => { - error!("Invalid argument: {}", e); - return Err(actix_web::error::ErrorBadRequest(e)); - } - _ => { - error!("Internal error: {}", err); - return Err(actix_web::error::ErrorInternalServerError(err)); - } + match self.index.add_movie(movie) { + Ok(movie_id) => match self.storage.allocate_movie_data(movie_id.clone()) { + Ok(()) => Ok(web::Json(movie_id)), + Err(err) => Self::handle_error(err), }, - }; + Err(err) => Self::handle_error(err), + } + } - Ok(web::Json(movie_id)) + /// Handles the request to get a new movie. + /// + /// # Arguments + /// * `movie` - The movie to get. + pub fn handle_get_movie(&self, id: MovieId) -> Result { + match self.index.get_movie(&id) { + Ok(movie) => Ok(web::Json(movie)), + Err(err) => Self::handle_error(err), + } + } + + /// Handles the request to delete a new movie. + /// + /// # Arguments + /// * `movie` - The movie to get. + pub fn handle_delete_movie(&mut self, id: MovieId) -> Result { + match self.index.remove_movie(&id) { + Ok(()) => match self.storage.remove_movie_data(id) { + Ok(_) => Ok(actix_web::HttpResponse::Ok()), + Err(err) => { + error!("Error deleting movie: {}", err); + Err(actix_web::error::ErrorInternalServerError(err)) + } + }, + Err(err) => Self::handle_error(err), + } } /// Handles the request to show the list of all movies. @@ -62,8 +82,8 @@ where let movies_ids = match self.index.search_movies(query) { Ok(movies_ids) => movies_ids, Err(err) => { - error!("Internal error: {}", err); - return Err(actix_web::error::ErrorInternalServerError(err)); + error!("Error searching: {}", err); + return Self::handle_error(err); } }; @@ -80,4 +100,21 @@ where Ok(web::Json(movies)) } + + fn handle_error(err: Error) -> Result { + match err { + Error::InvalidArgument(e) => { + error!("Invalid argument: {}", e); + Err(actix_web::error::ErrorBadRequest(e)) + } + Error::NotFound(e) => { + error!("Not found: {}", e); + Err(actix_web::error::ErrorNotFound(e)) + } + _ => { + error!("Internal error: {}", err); + Err(actix_web::error::ErrorInternalServerError(err)) + } + } + } } diff --git a/movies-db-service/movies-db/src/storage/file_storage.rs b/movies-db-service/movies-db/src/storage/file_storage.rs index 7c53332..22471b5 100644 --- a/movies-db-service/movies-db/src/storage/file_storage.rs +++ b/movies-db-service/movies-db/src/storage/file_storage.rs @@ -35,6 +35,22 @@ impl MovieStorage for FileStorage { Ok(Self { root_dir }) } + fn allocate_movie_data(&self, id: MovieId) -> Result<(), Error> { + let movie_data_path = self.get_movie_data_path(&id); + + create_dir_all(&movie_data_path).map_err(|e| { + Error::Internal(format!( + "Failed to create movie data directory '{}': {}", + movie_data_path.display(), + e + )) + })?; + + info!("Created movie data directory '{}'", id); + + Ok(()) + } + fn write_movie_data(&self, id: MovieId, data_type: MovieDataType) -> Result { let file_path = self.get_file_path(&id, data_type, true)?; diff --git a/movies-db-service/movies-db/src/storage/movies_storage.rs b/movies-db-service/movies-db/src/storage/movies_storage.rs index ab2061e..51cf128 100644 --- a/movies-db-service/movies-db/src/storage/movies_storage.rs +++ b/movies-db-service/movies-db/src/storage/movies_storage.rs @@ -23,6 +23,12 @@ pub trait MovieStorage: Send + Sync { where Self: Sized; + /// Allocates space for a new movie. + /// + /// # Arguments + /// * `id` - The movie id for which to allocate the data. + fn allocate_movie_data(&self, id: MovieId) -> Result<(), Error>; + /// Returns a writer for the given movie id and data type to store the data. /// /// # Arguments From 9a7c7475750fca1c95fa886d458dc3a5f69adc75 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 07:16:32 +0200 Subject: [PATCH 11/17] added: endpoint for uploading movie files --- movies-db-service/movies-db/Cargo.toml | 1 + .../movies-db/src/service/service.rs | 25 +++++- .../movies-db/src/service/service_handler.rs | 88 ++++++++++++++++++- 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/movies-db-service/movies-db/Cargo.toml b/movies-db-service/movies-db/Cargo.toml index 0b14964..39f2989 100644 --- a/movies-db-service/movies-db/Cargo.toml +++ b/movies-db-service/movies-db/Cargo.toml @@ -11,6 +11,7 @@ uuid = { version = "1.4", features = ["v4", "fast-rng", "macro-diagnostics"] } chrono = { version = "0.4", features = ["serde"] } wildmatch = "2.1" actix-web = "4" +actix-multipart = "0.6" futures = { version = "0.3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 6ae1703..1db6fa0 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,5 +1,6 @@ use std::{marker::PhantomData, sync::RwLock}; +use actix_multipart::Multipart; use actix_web::{web, App, HttpServer, Responder, Result}; use log::{debug, error, info, trace}; @@ -75,7 +76,8 @@ where .route("/movie", web::post().to(Self::handle_post_movie)) .route("/movie", web::get().to(Self::handle_get_movie)) .route("/movie", web::delete().to(Self::handle_delete_movie)) - .route("/movie/search", web::get().to(Self::handle_search_movie)); + .route("/movie/search", web::get().to(Self::handle_search_movie)) + .route("/movie/file", web::post().to(Self::handle_upload_movie)); App::new().app_data(handler.clone()).service(api_v1) }) @@ -186,4 +188,25 @@ where handler.handle_delete_movie(id) } + + /// Handles the POST /api/v1/movie/file endpoint. + /// + /// # Arguments + /// * `handler` - The service handler. + /// * `query` - The query parameters. + /// * `multipart` - The multipart data. + async fn handle_upload_movie( + handler: web::Data>>, + query: web::Query, + multipart: Multipart, + ) -> Result { + debug!("Handling POST /api/v1/movie/file"); + trace!("Request query: {:?}", query); + + let id: MovieId = query.into_inner().id; + + let mut handler = handler.write().unwrap(); + + handler.handle_upload_movie(id, multipart).await + } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 95fb9b4..b71e7bc 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,8 +1,13 @@ -use crate::{Error, Movie, MovieId, MovieSearchQuery, MovieStorage, MoviesIndex, Options}; +use crate::{ + Error, Movie, MovieDataType, MovieId, MovieSearchQuery, MovieStorage, MoviesIndex, Options, +}; +use actix_multipart::Multipart; use actix_web::{web, Responder, Result}; -use log::error; +use futures::{StreamExt, TryStreamExt}; +use log::{debug, error, info}; use serde::{Deserialize, Serialize}; +use std::{io::Write, path::PathBuf}; pub struct ServiceHandler where @@ -77,6 +82,85 @@ where } } + /// Handles the request to upload a movie. + /// + /// # Arguments + /// * `id` - The id of the movie to upload. + /// * `multipart` - The multipart data of the movie. + pub async fn handle_upload_movie( + &mut self, + id: MovieId, + mut multipart: Multipart, + ) -> Result { + info!("Uploading movie {} ...", id); + + // iterate over multipart stream + while let Ok(Some(mut field)) = multipart.try_next().await { + // extract the filename + let content_type = field.content_disposition(); + let filename: PathBuf = match content_type.get_filename() { + Some(filename) => PathBuf::from(filename), + None => { + error!("Invalid filename"); + return Err(actix_web::error::ErrorBadRequest("Invalid filename")); + } + }; + + debug!("Uploading file: {:?}", filename); + + // extract the extension + let ext = match filename.extension() { + Some(ext) => match ext.to_str() { + Some(ext) => ext.to_string(), + None => { + error!("Invalid extension"); + return Err(actix_web::error::ErrorBadRequest("Invalid extension")); + } + }, + None => { + error!("Invalid extension"); + return Err(actix_web::error::ErrorBadRequest("Invalid extension")); + } + }; + + debug!("Uploading file with extension: {:?}", ext); + + // open writer for storing movie data + let mut writer = match self + .storage + .write_movie_data(id.clone(), MovieDataType::MovieData { ext }) + { + Ok(writer) => writer, + Err(err) => { + return Self::handle_error(err); + } + }; + + // // Field in turn is stream of *Bytes* object + while let Some(chunk) = field.next().await { + let data = match chunk { + Ok(data) => data, + Err(err) => { + error!("Error reading chunk: {}", err); + return Err(actix_web::error::ErrorInternalServerError(err)); + } + }; + + match writer.write_all(&data) { + Ok(_) => (), + Err(err) => { + error!("Error writing chunk: {}", err); + return Err(actix_web::error::ErrorInternalServerError(err)); + } + } + } + } + + info!("Uploading movie {} ... DONE", id); + + Ok(actix_web::HttpResponse::Ok()) + } + /// Handles the request to show the list of all movies. pub fn handle_search_movies(&self, query: MovieSearchQuery) -> Result { let movies_ids = match self.index.search_movies(query) { From f6c2ac4355f5e27d4342e2b61897899b8480a354 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 11:27:01 +0200 Subject: [PATCH 12/17] added: movie storage resource now also have a size --- movies-db-service/movies-db/src/storage/file_storage.rs | 8 +++++++- movies-db-service/movies-db/src/storage/movies_storage.rs | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/movies-db-service/movies-db/src/storage/file_storage.rs b/movies-db-service/movies-db/src/storage/file_storage.rs index 22471b5..b58d92c 100644 --- a/movies-db-service/movies-db/src/storage/file_storage.rs +++ b/movies-db-service/movies-db/src/storage/file_storage.rs @@ -5,7 +5,7 @@ use std::{ use log::info; -use crate::{Error, MovieId, Options}; +use crate::{Error, MovieId, Options, ReadResource}; use super::movies_storage::{MovieDataType, MovieStorage}; @@ -13,6 +13,12 @@ pub struct FileStorage { root_dir: PathBuf, } +impl ReadResource for File { + fn get_size(&self) -> usize { + self.metadata().map(|m| m.len() as usize).unwrap_or(0) + } +} + impl MovieStorage for FileStorage { type W = File; type R = File; diff --git a/movies-db-service/movies-db/src/storage/movies_storage.rs b/movies-db-service/movies-db/src/storage/movies_storage.rs index 51cf128..e613e15 100644 --- a/movies-db-service/movies-db/src/storage/movies_storage.rs +++ b/movies-db-service/movies-db/src/storage/movies_storage.rs @@ -10,10 +10,15 @@ pub enum MovieDataType { }, } +/// The trait for reading movie data. +pub trait ReadResource: Read { + fn get_size(&self) -> usize; +} + /// The trait for storing movie data. pub trait MovieStorage: Send + Sync { type W: Write; - type R: Read; + type R: ReadResource; /// Creates a new instance of the storage. /// From 29a9fa8f5f1cfa3797faa9a014358ba6b35f6f8a Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 12:55:05 +0200 Subject: [PATCH 13/17] changed: the read/write to async operations --- movies-db-service/movies-db/Cargo.toml | 3 + .../movies-db/src/db/movies_index.rs | 26 +++- .../movies-db/src/db/simple_movies_index.rs | 30 ++++- .../movies-db/src/service/service.rs | 30 ++++- .../movies-db/src/service/service_handler.rs | 123 ++++++++++++++++-- .../movies-db/src/storage/file_storage.rs | 96 ++++++++------ .../movies-db/src/storage/movies_storage.rs | 28 ++-- 7 files changed, 266 insertions(+), 70 deletions(-) diff --git a/movies-db-service/movies-db/Cargo.toml b/movies-db-service/movies-db/Cargo.toml index 39f2989..9dd68d6 100644 --- a/movies-db-service/movies-db/Cargo.toml +++ b/movies-db-service/movies-db/Cargo.toml @@ -12,9 +12,12 @@ chrono = { version = "0.4", features = ["serde"] } wildmatch = "2.1" actix-web = "4" actix-multipart = "0.6" +tokio = { version = "1", features = ["full"] } +tokio-util = "0.7" futures = { version = "0.3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +async-trait = "0.1" [dev-dependencies] tempdir = "0.3" diff --git a/movies-db-service/movies-db/src/db/movies_index.rs b/movies-db-service/movies-db/src/db/movies_index.rs index d081342..16b5cc0 100644 --- a/movies-db-service/movies-db/src/db/movies_index.rs +++ b/movies-db-service/movies-db/src/db/movies_index.rs @@ -20,8 +20,9 @@ pub struct Movie { /// A single movie entry with timestamp. #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct MovieWithDate { +pub struct MovieDetailed { pub movie: Movie, + pub movie_file_info: Option, pub date: DateTime, } @@ -57,6 +58,16 @@ impl Default for SortingOrder { } } +/// The file info for a stored movie file. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MovieFileInfo { + /// the extension of the movie file in lower case, e.g., "mp4" + pub extension: String, + + // the mime type of the movie file, e.g., "video/mp4" + pub mime_type: String, +} + /// A query for searching movies in the database. #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct MovieSearchQuery { @@ -105,7 +116,18 @@ pub trait MoviesIndex: Send + Sync { /// /// # Arguments /// `id` - The ID of the movie to return. - fn get_movie(&self, id: &MovieId) -> Result; + fn get_movie(&self, id: &MovieId) -> Result; + + /// Updates the movie file info for the given ID. + /// + /// # Arguments + /// `id` - The ID of the movie to update. + /// `movie_file_info` - The new movie file info. + fn update_movie_file_info( + &mut self, + id: &MovieId, + movie_file_info: MovieFileInfo, + ) -> Result<(), Error>; /// Removes the movie for the given ID. /// diff --git a/movies-db-service/movies-db/src/db/simple_movies_index.rs b/movies-db-service/movies-db/src/db/simple_movies_index.rs index 31d49e1..59221db 100644 --- a/movies-db-service/movies-db/src/db/simple_movies_index.rs +++ b/movies-db-service/movies-db/src/db/simple_movies_index.rs @@ -5,13 +5,13 @@ use log::{debug, error, info}; use wildmatch::WildMatch; use crate::{ - generate_movie_id, Error, Movie, MovieId, MovieSearchQuery, MovieWithDate, MoviesIndex, - Options, SortingField, SortingOrder, + generate_movie_id, Error, Movie, MovieDetailed, MovieFileInfo, MovieId, MovieSearchQuery, + MoviesIndex, Options, SortingField, SortingOrder, }; /// A very simple and naive in-memory implementation of the movies index. pub struct SimpleMoviesIndex { - movies: HashMap, + movies: HashMap, } impl SimpleMoviesIndex { @@ -50,8 +50,9 @@ impl MoviesIndex for SimpleMoviesIndex { id ); - let mut movie_with_date = MovieWithDate { + let mut movie_with_date = MovieDetailed { movie, + movie_file_info: None, date: chrono::Utc::now(), }; Self::process_tags(&mut movie_with_date.movie.tags); @@ -61,7 +62,7 @@ impl MoviesIndex for SimpleMoviesIndex { Ok(id) } - fn get_movie(&self, id: &MovieId) -> Result { + fn get_movie(&self, id: &MovieId) -> Result { info!("Getting movie with id {}", id); match self.movies.get(id) { @@ -73,6 +74,25 @@ impl MoviesIndex for SimpleMoviesIndex { } } + fn update_movie_file_info( + &mut self, + id: &MovieId, + movie_file_info: MovieFileInfo, + ) -> Result<(), Error> { + info!("Updating movie file info for movie with id {}", id); + + match self.movies.get_mut(id) { + Some(movie) => { + movie.movie_file_info = Some(movie_file_info); + Ok(()) + } + None => { + error!("Movie with id {} not found", id); + Err(Error::NotFound(format!("Movie with id {} not found", id))) + } + } + } + fn remove_movie(&mut self, id: &MovieId) -> Result<(), Error> { info!("Removing movie with id {}", id); diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 1db6fa0..a4fdffe 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -77,7 +77,8 @@ where .route("/movie", web::get().to(Self::handle_get_movie)) .route("/movie", web::delete().to(Self::handle_delete_movie)) .route("/movie/search", web::get().to(Self::handle_search_movie)) - .route("/movie/file", web::post().to(Self::handle_upload_movie)); + .route("/movie/file", web::post().to(Self::handle_upload_movie)) + .route("/movie/file", web::get().to(Self::handle_download_movie)); App::new().app_data(handler.clone()).service(api_v1) }) @@ -129,7 +130,7 @@ where let mut handler: std::sync::RwLockWriteGuard<'_, ServiceHandler> = handler.write().unwrap(); - handler.handle_add_movie(movie) + handler.handle_add_movie(movie).await } /// Handles the GET /api/v1/movie endpoint. @@ -148,7 +149,7 @@ where let handler = handler.read().unwrap(); - handler.handle_search_movies(query) + handler.handle_search_movies(query).await } /// Handles the GET /api/v1/movie endpoint. @@ -167,7 +168,7 @@ where let handler = handler.read().unwrap(); - handler.handle_get_movie(id) + handler.handle_get_movie(id).await } /// Handles the DELETE /api/v1/movie endpoint. @@ -186,7 +187,7 @@ where let mut handler = handler.write().unwrap(); - handler.handle_delete_movie(id) + handler.handle_delete_movie(id).await } /// Handles the POST /api/v1/movie/file endpoint. @@ -209,4 +210,23 @@ where handler.handle_upload_movie(id, multipart).await } + + /// Handles the GET /api/v1/movie/file endpoint. + /// + /// # Arguments + /// * `handler` - The service handler. + /// * `query` - The query parameters. + async fn handle_download_movie( + handler: web::Data>>, + query: web::Query, + ) -> Result { + debug!("Handling GET /api/v1/movie/file"); + trace!("Request query: {:?}", query); + + let id: MovieId = query.into_inner().id; + + let mut handler = handler.read().unwrap(); + + handler.handle_download_movie(id).await + } } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index b71e7bc..3ec5ad7 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -1,13 +1,20 @@ use crate::{ Error, Movie, MovieDataType, MovieId, MovieSearchQuery, MovieStorage, MoviesIndex, Options, + ReadResource, }; use actix_multipart::Multipart; +use actix_web::body::SizedStream; +use actix_web::web::Bytes; +use actix_web::HttpResponse; use actix_web::{web, Responder, Result}; -use futures::{StreamExt, TryStreamExt}; +use futures::{Stream, StreamExt, TryStreamExt}; use log::{debug, error, info}; use serde::{Deserialize, Serialize}; -use std::{io::Write, path::PathBuf}; +use std::ops::DerefMut; +use std::path::PathBuf; +use tokio::io::AsyncWriteExt; +use tokio_util::codec::{BytesCodec, FramedRead}; pub struct ServiceHandler where @@ -44,9 +51,9 @@ where /// /// # Arguments /// * `movie` - The movie to add. - pub fn handle_add_movie(&mut self, movie: Movie) -> Result { + pub async fn handle_add_movie(&mut self, movie: Movie) -> Result { match self.index.add_movie(movie) { - Ok(movie_id) => match self.storage.allocate_movie_data(movie_id.clone()) { + Ok(movie_id) => match self.storage.allocate_movie_data(movie_id.clone()).await { Ok(()) => Ok(web::Json(movie_id)), Err(err) => Self::handle_error(err), }, @@ -58,7 +65,7 @@ where /// /// # Arguments /// * `movie` - The movie to get. - pub fn handle_get_movie(&self, id: MovieId) -> Result { + pub async fn handle_get_movie(&self, id: MovieId) -> Result { match self.index.get_movie(&id) { Ok(movie) => Ok(web::Json(movie)), Err(err) => Self::handle_error(err), @@ -69,9 +76,9 @@ where /// /// # Arguments /// * `movie` - The movie to get. - pub fn handle_delete_movie(&mut self, id: MovieId) -> Result { + pub async fn handle_delete_movie(&mut self, id: MovieId) -> Result { match self.index.remove_movie(&id) { - Ok(()) => match self.storage.remove_movie_data(id) { + Ok(()) => match self.storage.remove_movie_data(id).await { Ok(_) => Ok(actix_web::HttpResponse::Ok()), Err(err) => { error!("Error deleting movie: {}", err); @@ -129,6 +136,7 @@ where let mut writer = match self .storage .write_movie_data(id.clone(), MovieDataType::MovieData { ext }) + .await { Ok(writer) => writer, Err(err) => { @@ -136,7 +144,7 @@ where } }; - // // Field in turn is stream of *Bytes* object + // Field in turn is stream of *Bytes* object while let Some(chunk) = field.next().await { let data = match chunk { Ok(data) => data, @@ -146,7 +154,7 @@ where } }; - match writer.write_all(&data) { + match writer.write_all(&data).await { Ok(_) => (), Err(err) => { error!("Error writing chunk: {}", err); @@ -161,8 +169,67 @@ where Ok(actix_web::HttpResponse::Ok()) } + /// Handles the request to upload a movie. + /// + /// # Arguments + /// * `id` - The id of the movie to upload. + pub async fn handle_download_movie(&self, id: MovieId) -> Result { + info!("Downloading movie {} ...", id); + + // get the movie file info, needed for requesting the movie data + let movie_file_info = match self.index.get_movie(&id) { + Ok(movie) => match movie.movie_file_info { + Some(movie_file_info) => movie_file_info, + None => { + error!("Movie {} has no movie file info", id); + return Err(actix_web::error::ErrorConflict(format!( + "Movie {} is not yet ready", + id + ))); + } + }, + Err(err) => { + error!("Error getting movie info: {}", err); + return Self::handle_error(err); + } + }; + + // create reader onto the movie data + let movie_data = match self + .storage + .read_movie_data( + id, + MovieDataType::MovieData { + ext: movie_file_info.extension.clone(), + }, + ) + .await + { + Ok(movie_data) => movie_data, + Err(err) => { + error!("Error reading movie data: {}", err); + return Self::handle_error(err); + } + }; + + // create response + let mut res = HttpResponse::Ok(); + res.content_type(movie_file_info.mime_type); + + FramedRead::new(movie_data, BytesCodec::new()); + + todo!("Implement streaming of movie data"); + + // let stream = + // SizedStream::new(movie_data.get_size() as u64, stream); + + // Ok(HttpResponse::Ok().content_type(movie_file_info.mime_type)) + // .body(movie_data)) + Ok(res) + } + /// Handles the request to show the list of all movies. - pub fn handle_search_movies(&self, query: MovieSearchQuery) -> Result { + pub async fn handle_search_movies(&self, query: MovieSearchQuery) -> Result { let movies_ids = match self.index.search_movies(query) { Ok(movies_ids) => movies_ids, Err(err) => { @@ -202,3 +269,39 @@ where } } } + +// struct StreamReadResource { +// inner: R, +// buf: [u8; 1024], +// } + +// impl Unpin for StreamReadResource {} + +// impl StreamReadResource { +// pub fn new(inner: R) -> Self { +// Self { +// inner, +// buf: [0u8; 1024], +// } +// } +// } + +// impl Stream for StreamReadResource { +// type Item = std::io::Result; + +// fn poll_next( +// mut self: std::pin::Pin<&mut Self>, +// cx: &mut std::task::Context<'_>, +// ) -> std::task::Poll> { +// let s = self.deref_mut(); + +// let reader = &mut s.inner; +// let buffer = &mut s.buf; + +// match reader.read(buffer.as_mut()) { +// Ok(0) => std::task::Poll::Ready(None), +// Ok(n) => std::task::Poll::Ready(Some(Ok(Bytes::copy_from_slice(&buffer[..n])))), +// Err(err) => std::task::Poll::Ready(Some(Err(err))), +// } +// } +// } diff --git a/movies-db-service/movies-db/src/storage/file_storage.rs b/movies-db-service/movies-db/src/storage/file_storage.rs index b58d92c..6ff655a 100644 --- a/movies-db-service/movies-db/src/storage/file_storage.rs +++ b/movies-db-service/movies-db/src/storage/file_storage.rs @@ -1,9 +1,8 @@ -use std::{ - fs::{create_dir_all, File}, - path::PathBuf, -}; +use std::{fs, path::PathBuf}; +use async_trait::async_trait; use log::info; +use tokio::fs as tokio_fs; use crate::{Error, MovieId, Options, ReadResource}; @@ -13,15 +12,17 @@ pub struct FileStorage { root_dir: PathBuf, } -impl ReadResource for File { - fn get_size(&self) -> usize { - self.metadata().map(|m| m.len() as usize).unwrap_or(0) +#[async_trait] +impl ReadResource for tokio_fs::File { + async fn get_size(&self) -> usize { + self.metadata().await.map(|m| m.len() as usize).unwrap_or(0) } } +#[async_trait] impl MovieStorage for FileStorage { - type W = File; - type R = File; + type W = tokio_fs::File; + type R = tokio_fs::File; fn new(options: &Options) -> Result where @@ -30,7 +31,7 @@ impl MovieStorage for FileStorage { let root_dir = options.root_dir.clone(); // make sure the root directory exists - create_dir_all(&root_dir).map_err(|e| { + fs::create_dir_all(&root_dir).map_err(|e| { Error::Internal(format!( "Failed to create root directory '{}': {}", root_dir.display(), @@ -41,26 +42,32 @@ impl MovieStorage for FileStorage { Ok(Self { root_dir }) } - fn allocate_movie_data(&self, id: MovieId) -> Result<(), Error> { + async fn allocate_movie_data(&self, id: MovieId) -> Result<(), Error> { let movie_data_path = self.get_movie_data_path(&id); - create_dir_all(&movie_data_path).map_err(|e| { - Error::Internal(format!( - "Failed to create movie data directory '{}': {}", - movie_data_path.display(), - e - )) - })?; + tokio_fs::create_dir_all(&movie_data_path) + .await + .map_err(|e| { + Error::Internal(format!( + "Failed to create movie data directory '{}': {}", + movie_data_path.display(), + e + )) + })?; info!("Created movie data directory '{}'", id); Ok(()) } - fn write_movie_data(&self, id: MovieId, data_type: MovieDataType) -> Result { - let file_path = self.get_file_path(&id, data_type, true)?; + async fn write_movie_data( + &self, + id: MovieId, + data_type: MovieDataType, + ) -> Result { + let file_path = self.get_file_path(&id, data_type, true).await?; - let file = File::create(&file_path).map_err(|e| { + let file = tokio_fs::File::create(&file_path).await.map_err(|e| { Error::Internal(format!( "Failed to create file '{}': {}", file_path.display(), @@ -71,10 +78,14 @@ impl MovieStorage for FileStorage { Ok(file) } - fn read_movie_data(&self, id: MovieId, data_type: MovieDataType) -> Result { - let file_path = self.get_file_path(&id, data_type, false)?; + async fn read_movie_data( + &self, + id: MovieId, + data_type: MovieDataType, + ) -> Result { + let file_path = self.get_file_path(&id, data_type, false).await?; - let file = File::open(&file_path).map_err(|e| { + let file = tokio_fs::File::open(&file_path).await.map_err(|e| { Error::Internal(format!( "Failed to open file '{}': {}", file_path.display(), @@ -85,16 +96,18 @@ impl MovieStorage for FileStorage { Ok(file) } - fn remove_movie_data(&self, id: MovieId) -> Result<(), Error> { + async fn remove_movie_data(&self, id: MovieId) -> Result<(), Error> { let movie_data_path = self.get_movie_data_path(&id); - std::fs::remove_dir_all(&movie_data_path).map_err(|e| { - Error::Internal(format!( - "Failed to remove movie data directory '{}': {}", - movie_data_path.display(), - e - )) - })?; + tokio_fs::remove_dir_all(&movie_data_path) + .await + .map_err(|e| { + Error::Internal(format!( + "Failed to remove movie data directory '{}': {}", + movie_data_path.display(), + e + )) + })?; info!("Removed movie data directory '{}'", id); @@ -121,7 +134,7 @@ impl FileStorage { /// * `id` - The movie id for which to return the file path. /// * `data_type` - The type of data to return the file path. /// * `create_dir` - Whether to create the directory if it doesn't exist. - fn get_file_path( + async fn get_file_path( &self, id: &MovieId, data_type: MovieDataType, @@ -131,7 +144,7 @@ impl FileStorage { // make sure the root directory exists if create_dir { - create_dir_all(&file_path).map_err(|e| { + tokio_fs::create_dir_all(&file_path).await.map_err(|e| { Error::Internal(format!( "Failed to create root directory '{}': {}", file_path.display(), @@ -156,12 +169,12 @@ mod test { use crate::generate_movie_id; - use std::io::{Read, Write}; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; use super::*; - #[test] - fn test_write_movie_data() { + #[tokio::test] + async fn test_write_movie_data() { let root_dir = TempDir::new("movies-db").unwrap(); let mut options: Options = Default::default(); options.root_dir = root_dir.path().to_path_buf(); @@ -179,9 +192,10 @@ mod test { ext: "mp4".to_string(), }, ) + .await .unwrap(); - writeln!(w, "Hello, world!").unwrap(); + w.write_all(b"Hello, world!\n").await.unwrap(); } // read movie data from id0 @@ -193,17 +207,18 @@ mod test { ext: "mp4".to_string(), }, ) + .await .unwrap(); let mut s = String::new(); - r.read_to_string(&mut s).unwrap(); + r.read_to_string(&mut s).await.unwrap(); assert_eq!(s, "Hello, world!\n"); } // remove movie data from id0 - storage.remove_movie_data(id0.clone()).unwrap(); + storage.remove_movie_data(id0.clone()).await.unwrap(); assert!(storage .read_movie_data( @@ -212,6 +227,7 @@ mod test { ext: "mp4".to_string(), } ) + .await .is_err()); } } diff --git a/movies-db-service/movies-db/src/storage/movies_storage.rs b/movies-db-service/movies-db/src/storage/movies_storage.rs index e613e15..d4e4cd4 100644 --- a/movies-db-service/movies-db/src/storage/movies_storage.rs +++ b/movies-db-service/movies-db/src/storage/movies_storage.rs @@ -1,7 +1,9 @@ -use std::io::{Read, Write}; +use tokio::io::{AsyncRead, AsyncWrite}; use crate::{Error, MovieId, Options}; +use async_trait::async_trait; + /// The type of data to store. pub enum MovieDataType { MovieData { @@ -11,13 +13,15 @@ pub enum MovieDataType { } /// The trait for reading movie data. -pub trait ReadResource: Read { - fn get_size(&self) -> usize; +#[async_trait] +pub trait ReadResource: AsyncRead { + async fn get_size(&self) -> usize; } /// The trait for storing movie data. +#[async_trait] pub trait MovieStorage: Send + Sync { - type W: Write; + type W: AsyncWrite + Unpin; type R: ReadResource; /// Creates a new instance of the storage. @@ -32,25 +36,33 @@ pub trait MovieStorage: Send + Sync { /// /// # Arguments /// * `id` - The movie id for which to allocate the data. - fn allocate_movie_data(&self, id: MovieId) -> Result<(), Error>; + async fn allocate_movie_data(&self, id: MovieId) -> Result<(), Error>; /// Returns a writer for the given movie id and data type to store the data. /// /// # Arguments /// * `id` - The movie id for which to store the data. /// * `data_type` - The type of data to store. - fn write_movie_data(&self, id: MovieId, data_type: MovieDataType) -> Result; + async fn write_movie_data( + &self, + id: MovieId, + data_type: MovieDataType, + ) -> Result; /// Removes the data for the given movie id. /// /// # Arguments /// * `id` - The movie id for which to remove the data. - fn remove_movie_data(&self, id: MovieId) -> Result<(), Error>; + async fn remove_movie_data(&self, id: MovieId) -> Result<(), Error>; /// Returns a reader for the given movie id and data type to read the data. /// /// # Arguments /// * `id` - The movie id for which to read the data. /// * `data_type` - The type of data to read. - fn read_movie_data(&self, id: MovieId, data_type: MovieDataType) -> Result; + async fn read_movie_data( + &self, + id: MovieId, + data_type: MovieDataType, + ) -> Result; } From 0d156b8c4ae43f419ba56be9eafdaecb8deb174d Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 13:47:58 +0200 Subject: [PATCH 14/17] added: CORS Header --- movies-db-service/movies-db/Cargo.toml | 1 + movies-db-service/movies-db/src/service/service.rs | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/movies-db-service/movies-db/Cargo.toml b/movies-db-service/movies-db/Cargo.toml index 9dd68d6..c7651aa 100644 --- a/movies-db-service/movies-db/Cargo.toml +++ b/movies-db-service/movies-db/Cargo.toml @@ -18,6 +18,7 @@ futures = { version = "0.3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" async-trait = "0.1" +actix-cors = "0.6" [dev-dependencies] tempdir = "0.3" diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index a4fdffe..56474d9 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -1,5 +1,6 @@ use std::{marker::PhantomData, sync::RwLock}; +use actix_cors::Cors; use actix_multipart::Multipart; use actix_web::{web, App, HttpServer, Responder, Result}; @@ -72,6 +73,11 @@ where info!("Listening on {}", self.options.http_address); match HttpServer::new(move || { + let mut cors = Cors::default() + .allow_any_header() + .allow_any_method() + .allow_any_origin(); + let api_v1 = web::scope("/api/v1") .route("/movie", web::post().to(Self::handle_post_movie)) .route("/movie", web::get().to(Self::handle_get_movie)) @@ -80,7 +86,10 @@ where .route("/movie/file", web::post().to(Self::handle_upload_movie)) .route("/movie/file", web::get().to(Self::handle_download_movie)); - App::new().app_data(handler.clone()).service(api_v1) + App::new() + .wrap(cors) + .app_data(handler.clone()) + .service(api_v1) }) .bind(self.options.http_address.clone())? .run() From 9d7842d07ffcd9e7e8c73e4ce517dfe305555f1a Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 14:08:28 +0200 Subject: [PATCH 15/17] added: proper video upload --- .../movies-db/src/service/service_handler.rs | 40 ++++++++++++++++- tools/file-upload/index.html | 44 +++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tools/file-upload/index.html diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 3ec5ad7..6c35ff2 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -5,6 +5,7 @@ use crate::{ use actix_multipart::Multipart; use actix_web::body::SizedStream; +use actix_web::http::header; use actix_web::web::Bytes; use actix_web::HttpResponse; use actix_web::{web, Responder, Result}; @@ -113,7 +114,27 @@ where } }; - debug!("Uploading file: {:?}", filename); + // extract content type information + let content_type: String = match field.headers().get(header::CONTENT_TYPE) { + Some(content_type) => content_type.to_str().unwrap().to_string(), + None => { + error!("Invalid content type"); + return Err(actix_web::error::ErrorBadRequest("Invalid content type")); + } + }; + + // check if the content type is a video + if !content_type.starts_with("video") { + error!("Invalid content type"); + return Err(actix_web::error::ErrorUnsupportedMediaType( + "Invalid content type", + )); + } + + info!( + "Uploading file {:?} with mime-type {}", + filename, content_type + ); // extract the extension let ext = match filename.extension() { @@ -135,7 +156,7 @@ where // open writer for storing movie data let mut writer = match self .storage - .write_movie_data(id.clone(), MovieDataType::MovieData { ext }) + .write_movie_data(id.clone(), MovieDataType::MovieData { ext: ext.clone() }) .await { Ok(writer) => writer, @@ -162,6 +183,21 @@ where } } } + + // update the movie file info + match self.index.update_movie_file_info( + &id, + crate::MovieFileInfo { + extension: ext.clone(), + mime_type: content_type, + }, + ) { + Ok(()) => (), + Err(err) => { + error!("Error updating movie file info: {}", err); + return Err(actix_web::error::ErrorInternalServerError(err)); + } + } } info!("Uploading movie {} ... DONE", id); diff --git a/tools/file-upload/index.html b/tools/file-upload/index.html new file mode 100644 index 0000000..c98e9ee --- /dev/null +++ b/tools/file-upload/index.html @@ -0,0 +1,44 @@ + + + + + + Document + + + +
+ + + +
+ + + + \ No newline at end of file From 264a74984fbd7d2195cac5ba9e3f44b95b2f55c4 Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 14:52:33 +0200 Subject: [PATCH 16/17] added: Working download of uploaded videos --- .../movies-db/src/service/service.rs | 4 +- .../movies-db/src/service/service_handler.rs | 61 +++---------------- .../movies-db/src/storage/movies_storage.rs | 2 +- 3 files changed, 12 insertions(+), 55 deletions(-) diff --git a/movies-db-service/movies-db/src/service/service.rs b/movies-db-service/movies-db/src/service/service.rs index 56474d9..cd64b89 100644 --- a/movies-db-service/movies-db/src/service/service.rs +++ b/movies-db-service/movies-db/src/service/service.rs @@ -73,7 +73,7 @@ where info!("Listening on {}", self.options.http_address); match HttpServer::new(move || { - let mut cors = Cors::default() + let cors = Cors::default() .allow_any_header() .allow_any_method() .allow_any_origin(); @@ -234,7 +234,7 @@ where let id: MovieId = query.into_inner().id; - let mut handler = handler.read().unwrap(); + let handler = handler.read().unwrap(); handler.handle_download_movie(id).await } diff --git a/movies-db-service/movies-db/src/service/service_handler.rs b/movies-db-service/movies-db/src/service/service_handler.rs index 6c35ff2..e2a7d95 100644 --- a/movies-db-service/movies-db/src/service/service_handler.rs +++ b/movies-db-service/movies-db/src/service/service_handler.rs @@ -6,16 +6,15 @@ use crate::{ use actix_multipart::Multipart; use actix_web::body::SizedStream; use actix_web::http::header; -use actix_web::web::Bytes; use actix_web::HttpResponse; use actix_web::{web, Responder, Result}; -use futures::{Stream, StreamExt, TryStreamExt}; +use futures::{StreamExt, TryStreamExt}; use log::{debug, error, info}; use serde::{Deserialize, Serialize}; -use std::ops::DerefMut; use std::path::PathBuf; use tokio::io::AsyncWriteExt; -use tokio_util::codec::{BytesCodec, FramedRead}; + +use tokio_util::io::ReaderStream; pub struct ServiceHandler where @@ -249,19 +248,13 @@ where }; // create response - let mut res = HttpResponse::Ok(); - res.content_type(movie_file_info.mime_type); - - FramedRead::new(movie_data, BytesCodec::new()); - - todo!("Implement streaming of movie data"); + let length = movie_data.get_size().await as u64; + let reader_stream = ReaderStream::new(movie_data); + let sized_stream = SizedStream::new(length, reader_stream); - // let stream = - // SizedStream::new(movie_data.get_size() as u64, stream); - - // Ok(HttpResponse::Ok().content_type(movie_file_info.mime_type)) - // .body(movie_data)) - Ok(res) + HttpResponse::Ok() + .content_type(movie_file_info.mime_type) + .message_body(sized_stream) } /// Handles the request to show the list of all movies. @@ -305,39 +298,3 @@ where } } } - -// struct StreamReadResource { -// inner: R, -// buf: [u8; 1024], -// } - -// impl Unpin for StreamReadResource {} - -// impl StreamReadResource { -// pub fn new(inner: R) -> Self { -// Self { -// inner, -// buf: [0u8; 1024], -// } -// } -// } - -// impl Stream for StreamReadResource { -// type Item = std::io::Result; - -// fn poll_next( -// mut self: std::pin::Pin<&mut Self>, -// cx: &mut std::task::Context<'_>, -// ) -> std::task::Poll> { -// let s = self.deref_mut(); - -// let reader = &mut s.inner; -// let buffer = &mut s.buf; - -// match reader.read(buffer.as_mut()) { -// Ok(0) => std::task::Poll::Ready(None), -// Ok(n) => std::task::Poll::Ready(Some(Ok(Bytes::copy_from_slice(&buffer[..n])))), -// Err(err) => std::task::Poll::Ready(Some(Err(err))), -// } -// } -// } diff --git a/movies-db-service/movies-db/src/storage/movies_storage.rs b/movies-db-service/movies-db/src/storage/movies_storage.rs index d4e4cd4..786803c 100644 --- a/movies-db-service/movies-db/src/storage/movies_storage.rs +++ b/movies-db-service/movies-db/src/storage/movies_storage.rs @@ -14,7 +14,7 @@ pub enum MovieDataType { /// The trait for reading movie data. #[async_trait] -pub trait ReadResource: AsyncRead { +pub trait ReadResource: AsyncRead + Unpin + 'static { async fn get_size(&self) -> usize; } From bab120b61a05ae0c72750edc6b9c039103811bba Mon Sep 17 00:00:00 2001 From: sascha-raesch Date: Sun, 20 Aug 2023 15:12:02 +0200 Subject: [PATCH 17/17] changed: Update gitignore file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6985cf1..b2f5a5c 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +.DS_Store +temp/ \ No newline at end of file