diff --git a/Cargo.lock b/Cargo.lock index f15073f..6a473bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,15 @@ dependencies = [ "generic-array 0.12.4", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "block-padding" version = "0.1.5" @@ -196,6 +205,12 @@ dependencies = [ "safemem 0.2.0", ] +[[package]] +name = "build_html" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3108fe6fe7ac796fb7625bdde8fa2b67b5a7731496251ca57c7b8cadd78a16a1" + [[package]] name = "bumpalo" version = "3.14.0" @@ -404,6 +419,15 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -452,6 +476,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + [[package]] name = "cyberpunk-splash" version = "0.1.0" @@ -489,6 +523,12 @@ dependencies = [ "unic-langid", ] +[[package]] +name = "data-encoding" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" + [[package]] name = "deflate" version = "0.8.6" @@ -508,6 +548,16 @@ dependencies = [ "generic-array 0.12.4", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] + [[package]] name = "dimensioned" version = "0.7.0" @@ -639,8 +689,10 @@ dependencies = [ name = "file-service" version = "0.1.0" dependencies = [ + "build_html", "chrono", "hex-string", + "http", "image 0.23.14", "iron", "logger", @@ -654,7 +706,9 @@ dependencies = [ "serde_json", "sha2", "thiserror", + "tokio", "uuid 0.4.0", + "warp", ] [[package]] @@ -940,6 +994,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check 0.9.4", +] + [[package]] name = "geo-types" version = "0.1.0" @@ -1353,6 +1417,30 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.4", + "bytes", + "headers-core", + "http", + "httpdate", + "mime 0.3.17", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.4.1" @@ -1972,6 +2060,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f5c9112cb662acd3b204077e0de5bc66305fa8df65c8019d5adb10e9ab6e58" +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log 0.4.20", + "memchr 2.6.4", + "mime 0.3.17", + "spin", + "version_check 0.9.4", +] + [[package]] name = "multipart" version = "0.13.6" @@ -2432,6 +2538,26 @@ dependencies = [ "siphasher 0.3.11", ] +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.37", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -2928,6 +3054,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64 0.21.4", +] + [[package]] name = "rusty-fork" version = "0.3.0" @@ -2967,6 +3102,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -3091,14 +3232,25 @@ dependencies = [ "typeshare", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sha2" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.7.3", + "digest 0.8.1", "fake-simd", "opaque-debug", ] @@ -3391,6 +3543,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log 0.4.20", + "tokio", + "tungstenite", +] + [[package]] name = "tokio-util" version = "0.7.9" @@ -3463,6 +3638,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log 0.4.20", "pin-project-lite", "tracing-core", ] @@ -3488,6 +3664,25 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log 0.4.20", + "rand 0.8.5", + "sha1", + "thiserror", + "url 2.4.1", + "utf-8", +] + [[package]] name = "twoway" version = "0.1.8" @@ -3667,6 +3862,12 @@ dependencies = [ "url 1.7.2", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "0.4.0" @@ -3744,6 +3945,37 @@ dependencies = [ "try-lock", ] +[[package]] +name = "warp" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e92e22e03ff1230c03a1a8ee37d2f89cd489e2e541b7550d6afad96faed169" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "headers", + "http", + "hyper 0.14.27", + "log 0.4.20", + "mime 0.3.17", + "mime_guess 2.0.4", + "multer", + "percent-encoding 2.3.0", + "pin-project", + "rustls-pemfile", + "scoped-tls", + "serde 1.0.188", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tokio-util", + "tower-service", + "tracing", +] + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/file-service/Cargo.toml b/file-service/Cargo.toml index d1e2f81..0afba1a 100644 --- a/file-service/Cargo.toml +++ b/file-service/Cargo.toml @@ -7,8 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +build_html = { version = "2" } chrono = { version = "0.4", features = ["serde"] } hex-string = "0.1.0" +http = { version = "0.2" } image = "0.23.5" iron = "0.6.1" logger = "*" @@ -22,4 +24,6 @@ serde_json = "*" serde = { version = "1.0", features = ["derive"] } sha2 = "0.8.2" thiserror = "1.0.20" -uuid = { version = "0.4", features = ["serde", "v4"] } +tokio = { version = "1", features = [ "full" ] } +uuid = { version = "0.4", features = [ "serde", "v4" ] } +warp = { version = "0.3" } diff --git a/file-service/fixtures/.metadata/rawr.png.json b/file-service/fixtures/.metadata/rawr.png.json new file mode 100644 index 0000000..0c6259a --- /dev/null +++ b/file-service/fixtures/.metadata/rawr.png.json @@ -0,0 +1 @@ +{"id":"rawr.png","size":23777,"created":"2020-06-04T16:04:10.085680927Z","file_type":"image/png","hash":"b6cd35e113b95d62f53d9cbd27ccefef47d3e324aef01a2db6c0c6d3a43c89ee"} \ No newline at end of file diff --git a/file-service/fixtures/.thumbnails/rawr.png b/file-service/fixtures/.thumbnails/rawr.png new file mode 100644 index 0000000..94ecf15 Binary files /dev/null and b/file-service/fixtures/.thumbnails/rawr.png differ diff --git a/file-service/src/html.rs b/file-service/src/html.rs new file mode 100644 index 0000000..d83b34a --- /dev/null +++ b/file-service/src/html.rs @@ -0,0 +1,135 @@ +use build_html::{self, Html, HtmlContainer}; + +#[derive(Clone, Debug)] +pub struct Form { + method: String, + encoding: Option, + elements: String, +} + +impl Form { + pub fn new() -> Self { + Self { + method: "get".to_owned(), + encoding: None, + elements: "".to_owned(), + } + } + + pub fn with_method(mut self, method: &str) -> Self { + self.method = method.to_owned(); + self + } + + pub fn with_encoding(mut self, encoding: &str) -> Self { + self.encoding = Some(encoding.to_owned()); + self + } +} + +impl Html for Form { + fn to_html_string(&self) -> String { + let encoding = match self.encoding { + Some(ref encoding) => format!("encoding={encoding}", encoding = encoding), + None => format!(""), + }; + format!( + "
\n{elements}\n
\n", + method = self.method, + encoding = encoding, + elements = self.elements.to_html_string() + ) + } +} + +impl HtmlContainer for Form { + fn add_html(&mut self, html: H) { + self.elements.push_str(&html.to_html_string()); + } +} + +#[derive(Clone, Debug)] +pub struct Input { + ty: String, + name: String, + id: String, + value: Option, +} + +impl Html for Input { + fn to_html_string(&self) -> String { + format!( + "{value}\n", + ty = self.ty, + name = self.name, + id = self.id, + value = self.value.clone().unwrap_or("".to_owned()), + ) + } +} + +impl Input { + pub fn new(ty: &str, name: &str, id: &str) -> Self { + Self { + ty: ty.to_owned(), + name: name.to_owned(), + id: id.to_owned(), + value: None, + } + } + + pub fn with_value(mut self, val: &str) -> Self { + self.value = Some(val.to_owned()); + self + } +} + +#[derive(Clone, Debug)] +pub struct Label { + target: String, + text: String, +} + +impl Label { + pub fn new(target: &str, text: &str) -> Self { + Self { + target: target.to_owned(), + text: text.to_owned(), + } + } +} + +impl Html for Label { + fn to_html_string(&self) -> String { + format!( + "", + target = self.target, + text = self.text + ) + } +} + +#[derive(Clone, Debug)] +pub struct Button { + name: String, + text: String, +} + +impl Button { + pub fn new(name: &str, text: &str) -> Self { + Self { + name: name.to_owned(), + text: text.to_owned(), + } + } +} + +impl Html for Button { + fn to_html_string(&self) -> String { + format!( + "", + name = self.name, + text = self.text + ) + } +} diff --git a/file-service/src/lib/error.rs b/file-service/src/lib/error.rs deleted file mode 100644 index 53dc55f..0000000 --- a/file-service/src/lib/error.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::path::PathBuf; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum Error { - #[error("not implemented")] - NotImplemented, - - #[error("file not found: `{0}`")] - FileNotFound(PathBuf), - - #[error("file is not an image: `{0}`")] - NotAnImage(PathBuf), - - #[error("path is not a file: `{0}`")] - NotAFile(PathBuf), - - #[error("Image loading error")] - ImageError(#[from] image::ImageError), - - #[error("IO error")] - IOError(#[from] std::io::Error), - - #[error("JSON error")] - JSONError(#[from] serde_json::error::Error), - - #[error("UTF8 Error")] - UTF8Error(#[from] std::str::Utf8Error), -} - -pub type Result = std::result::Result; diff --git a/file-service/src/lib/file.rs b/file-service/src/lib/file.rs index 3a085fd..fad9e08 100644 --- a/file-service/src/lib/file.rs +++ b/file-service/src/lib/file.rs @@ -1,9 +1,38 @@ -use super::error::{Error, Result}; use super::fileinfo::FileInfo; use super::thumbnail::Thumbnail; use std::fs::{copy, read_dir, remove_file}; use std::path::{Path, PathBuf}; +use thiserror::Error; +#[derive(Error, Debug)] +pub enum FileError { + #[error("not implemented")] + NotImplemented, + + #[error("file not found: `{0}`")] + FileNotFound(PathBuf), + + #[error("file is not an image: `{0}`")] + NotAnImage(PathBuf), + + #[error("path is not a file: `{0}`")] + NotAFile(PathBuf), + + #[error("Image loading error")] + ImageError(#[from] image::ImageError), + + #[error("IO error")] + IOError(#[from] std::io::Error), + + #[error("JSON error")] + JSONError(#[from] serde_json::error::Error), + + #[error("UTF8 Error")] + UTF8Error(#[from] std::str::Utf8Error), +} + +/// One file in the database, complete with the path of the file and information about the +/// thumbnail of the file. #[derive(Debug)] pub struct File { info: FileInfo, @@ -17,7 +46,7 @@ impl File { root: &Path, temp_path: &PathBuf, filename: &Option, - ) -> Result { + ) -> Result { let mut dest_path = PathBuf::from(root); dest_path.push(id); match filename { @@ -33,27 +62,27 @@ impl File { copy(temp_path, dest_path.clone())?; let info = FileInfo::from_path(&dest_path)?; let tn = Thumbnail::from_path(&dest_path)?; - Ok(File { + Ok(Self { info, tn, root: PathBuf::from(root), }) } - pub fn open(id: &str, root: &Path) -> Result { + pub fn open(id: &str, root: &Path) -> Result { let mut file_path = PathBuf::from(root); file_path.push(id.clone()); if !file_path.exists() { - return Err(Error::FileNotFound(file_path)); + return Err(FileError::FileNotFound(file_path)); } if !file_path.is_file() { - return Err(Error::NotAFile(file_path)); + return Err(FileError::NotAFile(file_path)); } let info = match FileInfo::open(id, root) { Ok(i) => Ok(i), - Err(Error::FileNotFound(_)) => { + Err(FileError::FileNotFound(_)) => { let info = FileInfo::from_path(&file_path)?; info.save(&root)?; Ok(info) @@ -63,14 +92,14 @@ impl File { let tn = Thumbnail::open(id, root)?; - Ok(File { + Ok(Self { info, tn, root: PathBuf::from(root), }) } - pub fn list(root: &Path) -> Vec> { + pub fn list(root: &Path) -> Vec> { let dir_iter = read_dir(&root).unwrap(); dir_iter .filter(|entry| { @@ -81,7 +110,7 @@ impl File { .map(|entry| { let entry_ = entry.unwrap(); let id = entry_.file_name().into_string().unwrap(); - File::open(&id, root) + Self::open(&id, root) }) .collect() } @@ -94,13 +123,13 @@ impl File { self.tn.clone() } - pub fn stream(&self) -> Result { + pub fn stream(&self) -> Result { let mut path = self.root.clone(); path.push(self.info.id.clone()); - std::fs::File::open(path).map_err(Error::from) + std::fs::File::open(path).map_err(FileError::from) } - pub fn delete(&self) -> Result<()> { + pub fn delete(&self) -> Result<(), FileError> { let mut path = self.root.clone(); path.push(self.info.id.clone()); remove_file(path)?; diff --git a/file-service/src/lib/fileinfo.rs b/file-service/src/lib/fileinfo.rs index ee8350e..b3e6d49 100644 --- a/file-service/src/lib/fileinfo.rs +++ b/file-service/src/lib/fileinfo.rs @@ -7,8 +7,7 @@ use std::fs::remove_file; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; -use super::error::{Error, Result}; -use super::utils::append_extension; +use crate::{append_extension, FileError}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct FileInfo { @@ -23,45 +22,45 @@ pub struct FileInfo { } impl FileInfo { - pub fn save(&self, root: &Path) -> Result<()> { + pub fn save(&self, root: &Path) -> Result<(), FileError> { let ser = serde_json::to_string(self).unwrap(); std::fs::File::create(FileInfo::metadata_path(&self.id, root)) .and_then(|mut stream| stream.write(ser.as_bytes()).map(|_| (()))) - .map_err(Error::from) + .map_err(FileError::from) } - pub fn open(id: &str, root: &Path) -> Result { + pub fn open(id: &str, root: &Path) -> Result { let mut buf = Vec::new(); let md_path = FileInfo::metadata_path(id, root); std::fs::File::open(md_path.clone()) .and_then(|mut stream| stream.read_to_end(&mut buf)) .map_err(move |err| match err.kind() { - std::io::ErrorKind::NotFound => Error::FileNotFound(md_path), - _ => Error::IOError(err), + std::io::ErrorKind::NotFound => FileError::FileNotFound(md_path), + _ => FileError::IOError(err), })?; let str_repr = std::str::from_utf8(&buf)?; - serde_json::from_str(&str_repr).map_err(Error::from) + serde_json::from_str(&str_repr).map_err(FileError::from) } - pub fn from_path(path: &Path) -> Result { + pub fn from_path(path: &Path) -> Result { match (path.is_file(), path.is_dir()) { - (false, false) => Err(Error::FileNotFound(PathBuf::from(path))), - (false, true) => Err(Error::NotAFile(PathBuf::from(path))), + (false, false) => Err(FileError::FileNotFound(PathBuf::from(path))), + (false, true) => Err(FileError::NotAFile(PathBuf::from(path))), (true, _) => Ok(()), }?; - let metadata = path.metadata().map_err(Error::IOError)?; + let metadata = path.metadata().map_err(FileError::IOError)?; let id = path .file_name() .map(|s| String::from(s.to_string_lossy())) - .ok_or(Error::NotAFile(PathBuf::from(path)))?; + .ok_or(FileError::NotAFile(PathBuf::from(path)))?; let created = metadata .created() .map(|m| DateTime::from(m)) - .map_err(|err| Error::IOError(err))?; + .map_err(|err| FileError::IOError(err))?; let file_type = String::from( mime_guess::from_path(path) .first_or_octet_stream() @@ -71,18 +70,18 @@ impl FileInfo { Ok(FileInfo { id, size: metadata.len(), - created: created, + created, file_type, hash: hash.as_string(), root: PathBuf::from(path.parent().unwrap()), }) } - fn hash_file(path: &Path) -> Result { + fn hash_file(path: &Path) -> Result { let mut buf = Vec::new(); - let mut file = std::fs::File::open(path).map_err(Error::from)?; + let mut file = std::fs::File::open(path).map_err(FileError::from)?; - file.read_to_end(&mut buf).map_err(Error::from)?; + file.read_to_end(&mut buf).map_err(FileError::from)?; let mut vec = Vec::new(); vec.extend_from_slice(Sha256::digest(&buf).as_slice()); Ok(HexString::from_bytes(&vec)) @@ -95,9 +94,9 @@ impl FileInfo { append_extension(&path, "json") } - pub fn delete(&self) -> Result<()> { + pub fn delete(&self) -> Result<(), FileError> { let path = FileInfo::metadata_path(&self.id, &self.root); - remove_file(path).map_err(Error::from) + remove_file(path).map_err(FileError::from) } } diff --git a/file-service/src/lib/mod.rs b/file-service/src/lib/mod.rs index 40e80c2..3c9d0af 100644 --- a/file-service/src/lib/mod.rs +++ b/file-service/src/lib/mod.rs @@ -1,14 +1,12 @@ use std::path::{Path, PathBuf}; use uuid::Uuid; -mod error; mod file; mod fileinfo; mod thumbnail; -mod utils; +pub mod utils; -pub use error::{Error, Result}; -pub use file::File; +pub use file::{File, FileError}; pub use fileinfo::FileInfo; pub use thumbnail::Thumbnail; @@ -23,32 +21,36 @@ impl App { } } - pub fn list_files(&self) -> Vec> { + pub fn list_files(&self) -> Vec> { File::list(&self.files_root) } - pub fn add_file(&mut self, temp_path: &PathBuf, filename: &Option) -> Result { + pub fn add_file( + &mut self, + temp_path: &PathBuf, + filename: &Option, + ) -> Result { let id = Uuid::new_v4().hyphenated().to_string(); File::new(&id, &self.files_root, temp_path, filename) } - pub fn delete_file(&mut self, id: String) -> Result<()> { + pub fn delete_file(&mut self, id: String) -> Result<(), FileError> { let f = File::open(&id, &self.files_root)?; f.delete() } - pub fn get_metadata(&self, id: String) -> Result { + pub fn get_metadata(&self, id: String) -> Result { FileInfo::open(&id, &self.files_root) } - pub fn get_file(&self, id: String) -> Result<(FileInfo, std::fs::File)> { + pub fn get_file(&self, id: String) -> Result<(FileInfo, std::fs::File), FileError> { let f = File::open(&id, &self.files_root)?; let info = f.info(); let stream = f.stream()?; Ok((info, stream)) } - pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File)> { + pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File), FileError> { let f = File::open(id, &self.files_root)?; let stream = f.thumbnail().stream()?; Ok((f.info(), stream)) diff --git a/file-service/src/lib/thumbnail.rs b/file-service/src/lib/thumbnail.rs index 738b4aa..1e458ad 100644 --- a/file-service/src/lib/thumbnail.rs +++ b/file-service/src/lib/thumbnail.rs @@ -2,7 +2,7 @@ use image::imageops::FilterType; use std::fs::remove_file; use std::path::{Path, PathBuf}; -use super::error::{Error, Result}; +use crate::FileError; #[derive(Clone, Debug, PartialEq)] pub struct Thumbnail { @@ -11,7 +11,7 @@ pub struct Thumbnail { } impl Thumbnail { - pub fn open(id: &str, root: &Path) -> Result { + pub fn open(id: &str, root: &Path) -> Result { let mut source_path = PathBuf::from(root); source_path.push(id); @@ -30,15 +30,15 @@ impl Thumbnail { Ok(self_) } - pub fn from_path(path: &Path) -> Result { + pub fn from_path(path: &Path) -> Result { let id = path .file_name() .map(|s| String::from(s.to_string_lossy())) - .ok_or(Error::NotAnImage(PathBuf::from(path)))?; + .ok_or(FileError::NotAnImage(PathBuf::from(path)))?; let root = path .parent() - .ok_or(Error::FileNotFound(PathBuf::from(path)))?; + .ok_or(FileError::FileNotFound(PathBuf::from(path)))?; Thumbnail::open(&id, root) } @@ -50,20 +50,20 @@ impl Thumbnail { path } - pub fn stream(&self) -> Result { + pub fn stream(&self) -> Result { let thumbnail_path = Thumbnail::thumbnail_path(&self.id, &self.root); std::fs::File::open(thumbnail_path.clone()).map_err(|err| { if err.kind() == std::io::ErrorKind::NotFound { - Error::FileNotFound(thumbnail_path) + FileError::FileNotFound(thumbnail_path) } else { - Error::from(err) + FileError::from(err) } }) } - pub fn delete(&self) -> Result<()> { + pub fn delete(&self) -> Result<(), FileError> { let path = Thumbnail::thumbnail_path(&self.id, &self.root); - remove_file(path).map_err(Error::from) + remove_file(path).map_err(FileError::from) } } diff --git a/file-service/src/main.rs b/file-service/src/main.rs index 67f1401..addd532 100644 --- a/file-service/src/main.rs +++ b/file-service/src/main.rs @@ -1,28 +1,31 @@ +/* use iron::headers; use iron::middleware::Handler; use iron::modifiers::{Header, Redirect}; use iron::prelude::*; use iron::response::BodyReader; use iron::status; -use mustache::{compile_path, Template}; -use orizentic::{Permissions, ResourceName, Secret}; -use params::{Params, Value}; -use router::Router; -use serde::Serialize; -use std::collections::HashMap; -use std::fs::File; -use std::io::Read; -use std::path::Path; -use std::path::PathBuf; -use std::sync::{Arc, RwLock}; +*/ +use http::status::StatusCode; +// use mustache::{compile_path, Template}; +// use orizentic::{Permissions, ResourceName, Secret}; +use build_html::Html; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + path::Path, + sync::{Arc, RwLock}, +}; +use warp::Filter; mod cookies; +mod html; mod lib; mod middleware; +mod pages; -use lib::{App, FileInfo}; -use middleware::{Authentication, RestForm}; +use lib::{utils::append_extension, App, File, FileError, FileInfo}; +/* fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { let Permissions(perms) = permissions; ResourceName(String::from( @@ -280,18 +283,17 @@ fn script(_: &mut Request) -> IronResult { js, ))) } +*/ -fn main() { +#[tokio::main] +pub async fn main() { + /* let auth_db_path = std::env::var("ORIZENTIC_DB").unwrap(); let secret = Secret(Vec::from( std::env::var("ORIZENTIC_SECRET").unwrap().as_bytes(), )); let auth_middleware = Authentication::new(secret, auth_db_path); - let app = Arc::new(RwLock::new(App::new(Path::new( - &std::env::var("FILE_SHARE_DIR").unwrap(), - )))); - let mut router = Router::new(); router.get( "/", @@ -338,4 +340,39 @@ fn main() { chain.link_before(RestForm {}); Iron::new(chain).http("0.0.0.0:3000").unwrap(); + */ + + /* + let root = warp::path!().and(warp::get()).map({ + || { + warp::http::Response::builder() + .header("content-type", "text/html") + .status(StatusCode::NOT_MODIFIED) + .body(()) + } + }); + let server = warp::serve(root); + server + .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002)) + .await; + */ + + let app = Arc::new(RwLock::new(App::new(Path::new( + &std::env::var("FILE_SHARE_DIR").unwrap(), + )))); + + let root = warp::path!().map({ + let app = app.clone(); + move || { + warp::http::Response::builder() + .header("content-type", "text/html") + .status(StatusCode::OK) + .body(pages::index(app.read().unwrap().list_files()).to_html_string()) + } + }); + + let server = warp::serve(root); + server + .run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002)) + .await; } diff --git a/file-service/src/middleware/authentication.rs b/file-service/src/middleware/authentication.rs deleted file mode 100644 index 658bee8..0000000 --- a/file-service/src/middleware/authentication.rs +++ /dev/null @@ -1,51 +0,0 @@ -use iron::headers; -use iron::middleware::BeforeMiddleware; -use iron::prelude::*; -use iron::typemap::Key; -use orizentic::{filedb, OrizenticCtx, Secret}; -use params::{FromValue, Params}; - -use crate::cookies::{Cookie, CookieJar}; - -pub struct Authentication { - pub auth: OrizenticCtx, -} - -impl Key for Authentication { - type Value = orizentic::VerifiedToken; -} - -impl Authentication { - pub fn new(secret: Secret, auth_db_path: String) -> Authentication { - let claims = filedb::load_claims_from_file(&auth_db_path).expect("claims did not load"); - let orizentic = OrizenticCtx::new(secret, claims); - Authentication { auth: orizentic } - } - - fn authenticate_user( - &self, - token_str: String, - ) -> Result { - self.auth.decode_and_validate_text(token_str) - } -} - -impl BeforeMiddleware for Authentication { - fn before(&self, req: &mut Request) -> IronResult<()> { - let params = req.get_ref::().unwrap(); - let token = match params.get("auth").and_then(|v| String::from_value(v)) { - Some(token_str) => self.authenticate_user(token_str).ok(), - None => { - let m_jar = req - .headers - .get::() - .map(|cookies| CookieJar::from(cookies)); - m_jar - .and_then(|jar| jar.lookup("auth").cloned()) - .and_then(|Cookie { value, .. }| self.authenticate_user(value.clone()).ok()) - } - }; - token.map(|t| req.extensions.insert::(t)); - Ok(()) - } -} diff --git a/file-service/src/middleware/logging.rs b/file-service/src/middleware/logging.rs deleted file mode 100644 index 2aceb1d..0000000 --- a/file-service/src/middleware/logging.rs +++ /dev/null @@ -1,16 +0,0 @@ -use iron::middleware::{AfterMiddleware, BeforeMiddleware}; -use iron::prelude::*; - -pub struct Logging {} - -impl BeforeMiddleware for Logging { - fn before(&self, _: &mut Request) -> IronResult<()> { - Ok(()) - } -} - -impl AfterMiddleware for Logging { - fn after(&self, _: &mut Request, res: Response) -> IronResult { - Ok(res) - } -} diff --git a/file-service/src/middleware/mod.rs b/file-service/src/middleware/mod.rs deleted file mode 100644 index 1bab46b..0000000 --- a/file-service/src/middleware/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod authentication; -mod logging; -mod restform; - -pub use authentication::Authentication; -pub use restform::RestForm; diff --git a/file-service/src/middleware/restform.rs b/file-service/src/middleware/restform.rs deleted file mode 100644 index 5cfadd9..0000000 --- a/file-service/src/middleware/restform.rs +++ /dev/null @@ -1,34 +0,0 @@ -use iron::method::Method; -use iron::middleware::BeforeMiddleware; -use iron::prelude::*; -use params::{Params, Value}; - -pub struct RestForm {} - -impl RestForm { - fn method(&self, v: &Value) -> Option { - match v { - Value::String(method_str) => match method_str.as_str() { - "delete" => Some(Method::Delete), - _ => None, - }, - _ => None, - } - } -} - -impl BeforeMiddleware for RestForm { - fn before(&self, req: &mut Request) -> IronResult<()> { - if req.method == Method::Post { - let method = { - let params = req.get_ref::().unwrap(); - params - .get("_method") - .and_then(|m| self.method(m)) - .unwrap_or(Method::Post) - }; - req.method = method; - } - Ok(()) - } -} diff --git a/file-service/src/pages.rs b/file-service/src/pages.rs new file mode 100644 index 0000000..e1385df --- /dev/null +++ b/file-service/src/pages.rs @@ -0,0 +1,40 @@ +use crate::{html::*, File, FileError}; +use build_html::{self, Container, ContainerType, HtmlContainer}; + +pub fn index(files: Vec>) -> build_html::HtmlPage { + let mut page = build_html::HtmlPage::new() + .with_title("Admin list of files") + .with_header(1, "Admin list of files") + .with_html( + Form::new() + .with_method("post") + .with_encoding("multipart/form-data") + .with_container( + Container::new(ContainerType::Div) + .with_html(Input::new("file", "file", "file-selector-input")) + .with_html(Label::new("for-selector-input", "Select a file")), + ) + .with_html(Button::new("upload", "Upload file")), + ); + + for file in files { + let mut container = + Container::new(ContainerType::Div).with_attributes(vec![("class", "file")]); + match file { + Ok(file) => { + let tn = Container::new(ContainerType::Div) + .with_attributes(vec![("class", "thumbnail")]) + .with_link( + format!("/file/{}", file.info().id), + "

paragraph within the link

".to_owned(), + ); + container.add_html(tn); + } + Err(err) => { + container.add_paragraph(format!("{:?}", err)); + } + } + page.add_container(container); + } + page +}