From 9d823fdc51cd4d7ee19dc184f560572cf4e08ecc Mon Sep 17 00:00:00 2001 From: Cameron Cordes Date: Wed, 19 May 2021 08:53:20 -0400 Subject: [PATCH] Create file metadata endpoint This allows retrieving create/modify date as well as file size for any file in the BASE_PATH. --- src/data/mod.rs | 26 +++++++++++++++++++++++++- src/database/mod.rs | 7 ++----- src/files.rs | 9 ++++++++- src/main.rs | 33 +++++++++++++++++++++++++++------ 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/data/mod.rs b/src/data/mod.rs index 1aed598..b5e4c97 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,5 +1,6 @@ -use std::str::FromStr; +use std::{fs, str::FromStr}; +use chrono::{DateTime, Utc}; use log::error; use actix_web::error::ErrorUnauthorized; @@ -92,6 +93,29 @@ pub struct AddFavoriteRequest { pub path: String, } +#[derive(Debug, Serialize)] +pub struct MetadataResponse { + pub created: Option, + pub modified: Option, + pub size: u64, +} + +impl From for MetadataResponse { + fn from(metadata: fs::Metadata) -> Self { + MetadataResponse { + created: metadata.created().ok().map(|created| { + let utc: DateTime = created.into(); + utc.timestamp() + }), + modified: metadata.modified().ok().map(|modified| { + let utc: DateTime = modified.into(); + utc.timestamp() + }), + size: metadata.len(), + } + } +} + #[cfg(test)] mod tests { use super::Claims; diff --git a/src/database/mod.rs b/src/database/mod.rs index 185a176..e29ba6b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -44,15 +44,12 @@ impl UserDao for SqliteUserDao { .execute(&self.connection) .unwrap(); - match users + users .filter(username.eq(username)) .load::(&self.connection) .unwrap() .first() - { - Some(u) => Some(u.clone()), - None => None, - } + .cloned() } else { None } diff --git a/src/files.rs b/src/files.rs index 512ac88..8c6acea 100644 --- a/src/files.rs +++ b/src/files.rs @@ -5,7 +5,7 @@ use std::path::{Path, PathBuf}; use path_absolutize::*; -pub fn list_files(dir: PathBuf) -> io::Result> { +pub fn list_files(dir: &Path) -> io::Result> { let files = read_dir(dir)? .map(|res| res.unwrap()) .filter(|entry| is_image_or_video(&entry.path()) || entry.file_type().unwrap().is_dir()) @@ -151,6 +151,13 @@ mod tests { assert!(is_image_or_video(Path::new("image.MoV"))); } + #[test] + fn nef_valid_extension_test() { + assert!(is_image_or_video(Path::new("image.nef"))); + assert!(is_image_or_video(Path::new("image.NEF"))); + assert!(is_image_or_video(Path::new("image.NeF"))); + } + #[test] fn hidden_file_not_valid_test() { assert!(!is_image_or_video(Path::new(".DS_store"))); diff --git a/src/main.rs b/src/main.rs index 16ee93b..f98f3fc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,13 @@ use database::{DbError, DbErrorKind, FavoriteDao, SqliteFavoriteDao, SqliteUserD use futures::stream::StreamExt; use lazy_static::lazy_static; use prometheus::{self, IntGauge}; -use std::path::{Path, PathBuf}; -use std::sync::mpsc::channel; -use std::sync::Arc; +use std::sync::{mpsc::channel, Arc}; use std::{collections::HashMap, io::prelude::*}; use std::{env, fs::File}; +use std::{ + io::ErrorKind, + path::{Path, PathBuf}, +}; use walkdir::{DirEntry, WalkDir}; use actix::prelude::*; @@ -29,7 +31,7 @@ use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher}; use rayon::prelude::*; use serde::Serialize; -use data::{AddFavoriteRequest, ThumbnailRequest}; +use data::{AddFavoriteRequest, MetadataResponse, ThumbnailRequest}; use log::{debug, error, info}; use crate::data::Claims; @@ -61,7 +63,7 @@ async fn list_photos(_claims: Claims, req: Query) -> impl Resp let path = &req.path; if let Some(path) = is_valid_path(path) { - let files = list_files(path).unwrap_or_default(); + let files = list_files(&path).unwrap_or_default(); let photos = &files .iter() @@ -119,6 +121,24 @@ async fn get_image( } } +#[get("/image/metadata")] +async fn get_file_metadata(_: Claims, path: web::Query) -> impl Responder { + match is_valid_path(&path.path) + .ok_or_else(|| ErrorKind::InvalidData.into()) + .and_then(File::open) + .and_then(|file| file.metadata()) + { + Ok(metadata) => { + let response: MetadataResponse = metadata.into(); + HttpResponse::Ok().json(response) + } + Err(e) => { + error!("Error getting metadata for file '{}': {:?}", path.path, e); + HttpResponse::InternalServerError().finish() + } + } +} + #[post("/image")] async fn upload_image(_: Claims, mut payload: mp::Multipart) -> impl Responder { let mut file_content: BytesMut = BytesMut::new(); @@ -370,7 +390,7 @@ fn create_thumbnails() { update_media_counts(&images); } -fn update_media_counts(media_dir: &PathBuf) { +fn update_media_counts(media_dir: &Path) { let mut image_count = 0; let mut video_count = 0; for ref entry in WalkDir::new(media_dir).into_iter().filter_map(|e| e.ok()) { @@ -485,6 +505,7 @@ fn main() -> std::io::Result<()> { .service(favorites) .service(put_add_favorite) .service(delete_favorite) + .service(get_file_metadata) .app_data(app_data.clone()) .data::>(Box::new(user_dao)) .data::>(Box::new(favorites_dao))