Error cleanup

This commit is contained in:
Antoine Gersant 2022-11-21 16:31:49 -08:00
parent 1e9d307a05
commit 4c5a6bc2d6
5 changed files with 89 additions and 81 deletions

View file

@ -16,14 +16,6 @@ pub enum QueryError {
SongNotFound(PathBuf),
#[error(transparent)]
Vfs(#[from] vfs::Error),
#[error("Unspecified")]
Unspecified,
}
impl From<anyhow::Error> for QueryError {
fn from(_: anyhow::Error) -> Self {
QueryError::Unspecified
}
}
sql_function!(
@ -44,8 +36,7 @@ impl Index {
// Browse top-level
let real_directories: Vec<Directory> = directories::table
.filter(directories::parent.is_null())
.load(&mut connection)
.map_err(anyhow::Error::new)?;
.load(&mut connection)?;
let virtual_directories = real_directories
.into_iter()
.filter_map(|d| d.virtualize(&vfs));
@ -58,8 +49,7 @@ impl Index {
let real_directories: Vec<Directory> = directories::table
.filter(directories::parent.eq(&real_path_string))
.order(sql::<sql_types::Bool>("path COLLATE NOCASE ASC"))
.load(&mut connection)
.map_err(anyhow::Error::new)?;
.load(&mut connection)?;
let virtual_directories = real_directories
.into_iter()
.filter_map(|d| d.virtualize(&vfs));
@ -68,8 +58,7 @@ impl Index {
let real_songs: Vec<Song> = songs::table
.filter(songs::parent.eq(&real_path_string))
.order(sql::<sql_types::Bool>("path COLLATE NOCASE ASC"))
.load(&mut connection)
.map_err(anyhow::Error::new)?;
.load(&mut connection)?;
let virtual_songs = real_songs.into_iter().filter_map(|s| s.virtualize(&vfs));
output.extend(virtual_songs.map(CollectionFile::Song));
}
@ -95,20 +84,16 @@ impl Index {
songs
.filter(path.like(&song_path_filter))
.order(path)
.load(&mut connection)
.map_err(anyhow::Error::new)?
.load(&mut connection)?
} else {
songs
.order(path)
.load(&mut connection)
.map_err(anyhow::Error::new)?
songs.order(path).load(&mut connection)?
};
let virtual_songs = real_songs.into_iter().filter_map(|s| s.virtualize(&vfs));
Ok(virtual_songs.collect::<Vec<_>>())
}
pub fn get_random_albums(&self, count: i64) -> anyhow::Result<Vec<Directory>> {
pub fn get_random_albums(&self, count: i64) -> Result<Vec<Directory>, QueryError> {
use self::directories::dsl::*;
let vfs = self.vfs_manager.get_vfs()?;
let mut connection = self.db.connect()?;
@ -123,7 +108,7 @@ impl Index {
Ok(virtual_directories.collect::<Vec<_>>())
}
pub fn get_recent_albums(&self, count: i64) -> anyhow::Result<Vec<Directory>> {
pub fn get_recent_albums(&self, count: i64) -> Result<Vec<Directory>, QueryError> {
use self::directories::dsl::*;
let vfs = self.vfs_manager.get_vfs()?;
let mut connection = self.db.connect()?;
@ -138,7 +123,7 @@ impl Index {
Ok(virtual_directories.collect::<Vec<_>>())
}
pub fn search(&self, query: &str) -> anyhow::Result<Vec<CollectionFile>> {
pub fn search(&self, query: &str) -> Result<Vec<CollectionFile>, QueryError> {
let vfs = self.vfs_manager.get_vfs()?;
let mut connection = self.db.connect()?;
let like_test = format!("%{}%", query);

View file

@ -1,4 +1,3 @@
use anyhow::*;
use image::{DynamicImage, GenericImage, GenericImageView, ImageBuffer, ImageOutputFormat};
use std::cmp;
use std::collections::hash_map::DefaultHasher;
@ -8,6 +7,24 @@ use std::path::{Path, PathBuf};
use crate::utils::{get_audio_format, AudioFormat};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("No embedded artwork was found in `{0}`")]
EmbeddedArtworkNotFound(PathBuf),
#[error(transparent)]
Id3(#[from] id3::Error),
#[error(transparent)]
Image(#[from] image::error::ImageError),
#[error("Filesystem error for `{0}`: `{1}`")]
Io(PathBuf, std::io::Error),
#[error(transparent)]
Metaflac(#[from] metaflac::Error),
#[error(transparent)]
Mp4aMeta(#[from] mp4ameta::Error),
#[error("This file format is not supported: {0}")]
UnsupportedFormat(&'static str),
}
#[derive(Debug, Hash)]
pub struct Options {
pub max_dimension: Option<u32>,
@ -37,7 +54,11 @@ impl Manager {
}
}
pub fn get_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Result<PathBuf> {
pub fn get_thumbnail(
&self,
image_path: &Path,
thumbnailoptions: &Options,
) -> Result<PathBuf, Error> {
match self.retrieve_thumbnail(image_path, thumbnailoptions) {
Some(path) => Ok(path),
None => self.create_thumbnail(image_path, thumbnailoptions),
@ -60,13 +81,19 @@ impl Manager {
}
}
fn create_thumbnail(&self, image_path: &Path, thumbnailoptions: &Options) -> Result<PathBuf> {
fn create_thumbnail(
&self,
image_path: &Path,
thumbnailoptions: &Options,
) -> Result<PathBuf, Error> {
let thumbnail = generate_thumbnail(image_path, thumbnailoptions)?;
let quality = 80;
fs::create_dir_all(&self.thumbnails_dir_path)?;
fs::create_dir_all(&self.thumbnails_dir_path)
.map_err(|e| Error::Io(self.thumbnails_dir_path.clone(), e))?;
let path = self.get_thumbnail_path(image_path, thumbnailoptions);
let mut out_file = File::create(&path)?;
let mut out_file =
File::create(&path).map_err(|e| Error::Io(self.thumbnails_dir_path.clone(), e))?;
thumbnail.write_to(&mut out_file, ImageOutputFormat::Jpeg(quality))?;
Ok(path)
}
@ -79,7 +106,7 @@ impl Manager {
}
}
fn generate_thumbnail(image_path: &Path, options: &Options) -> Result<DynamicImage> {
fn generate_thumbnail(image_path: &Path, options: &Options) -> Result<DynamicImage, Error> {
let source_image = DynamicImage::ImageRgb8(read(image_path)?.into_rgb8());
let (source_width, source_height) = source_image.dimensions();
let largest_dimension = cmp::max(source_width, source_height);
@ -115,7 +142,7 @@ fn generate_thumbnail(image_path: &Path, options: &Options) -> Result<DynamicIma
Ok(final_image)
}
fn read(image_path: &Path) -> Result<DynamicImage> {
fn read(image_path: &Path) -> Result<DynamicImage, Error> {
match get_audio_format(image_path) {
Some(AudioFormat::AIFF) => read_aiff(image_path),
Some(AudioFormat::APE) => read_ape(image_path),
@ -130,67 +157,53 @@ fn read(image_path: &Path) -> Result<DynamicImage> {
}
}
fn read_ape(_: &Path) -> Result<DynamicImage> {
bail!("Embedded images are not supported in APE files");
fn read_ape(_: &Path) -> Result<DynamicImage, Error> {
Err(Error::UnsupportedFormat("ape"))
}
fn read_flac(path: &Path) -> Result<DynamicImage> {
fn read_flac(path: &Path) -> Result<DynamicImage, Error> {
let tag = metaflac::Tag::read_from_path(path)?;
if let Some(p) = tag.pictures().next() {
return Ok(image::load_from_memory(&p.data)?);
}
bail!(
"Embedded flac artwork not found for file: {}",
path.display()
);
Err(Error::EmbeddedArtworkNotFound(path.to_owned()))
}
fn read_mp3(path: &Path) -> Result<DynamicImage> {
fn read_mp3(path: &Path) -> Result<DynamicImage, Error> {
let tag = id3::Tag::read_from_path(path)?;
read_id3(path, &tag)
}
fn read_aiff(path: &Path) -> Result<DynamicImage> {
fn read_aiff(path: &Path) -> Result<DynamicImage, Error> {
let tag = id3::Tag::read_from_aiff_path(path)?;
read_id3(path, &tag)
}
fn read_wave(path: &Path) -> Result<DynamicImage> {
fn read_wave(path: &Path) -> Result<DynamicImage, Error> {
let tag = id3::Tag::read_from_wav_path(path)?;
read_id3(path, &tag)
}
fn read_id3(path: &Path, tag: &id3::Tag) -> Result<DynamicImage> {
fn read_id3(path: &Path, tag: &id3::Tag) -> Result<DynamicImage, Error> {
if let Some(p) = tag.pictures().next() {
return Ok(image::load_from_memory(&p.data)?);
}
bail!(
"Embedded id3 artwork not found for file: {}",
path.display()
);
Err(Error::EmbeddedArtworkNotFound(path.to_owned()))
}
fn read_mp4(path: &Path) -> Result<DynamicImage> {
fn read_mp4(path: &Path) -> Result<DynamicImage, Error> {
let tag = mp4ameta::Tag::read_from_path(path)?;
match tag.artwork().map(|d| d.data) {
Some(v) => Ok(image::load_from_memory(v)?),
_ => bail!(
"Embedded mp4 artwork not found for file: {}",
path.display()
),
}
tag.artwork()
.and_then(|d| image::load_from_memory(d.data).ok())
.ok_or_else(|| Error::EmbeddedArtworkNotFound(path.to_owned()))
}
fn read_vorbis(_: &Path) -> Result<DynamicImage> {
bail!("Embedded images are not supported in Vorbis files");
fn read_vorbis(_: &Path) -> Result<DynamicImage, Error> {
Err(Error::UnsupportedFormat("vorbis"))
}
fn read_opus(_: &Path) -> Result<DynamicImage> {
bail!("Embedded images are not supported in Opus files");
fn read_opus(_: &Path) -> Result<DynamicImage, Error> {
Err(Error::UnsupportedFormat("opus"))
}
#[cfg(test)]

View file

@ -208,7 +208,7 @@ impl Manager {
.map(AuthToken)
}
pub fn count(&self) -> anyhow::Result<i64> {
pub fn count(&self) -> Result<i64, Error> {
use crate::db::users::dsl::*;
let mut connection = self.db.connect()?;
let count = users.count().get_result(&mut connection)?;

View file

@ -15,7 +15,7 @@ use actix_web_httpauth::extractors::bearer::BearerAuth;
use futures_util::future::err;
use percent_encoding::percent_decode_str;
use std::future::Future;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::str;
@ -73,9 +73,11 @@ pub fn make_config() -> impl FnOnce(&mut ServiceConfig) + Clone {
impl ResponseError for APIError {
fn status_code(&self) -> StatusCode {
match self {
APIError::AdminPermissionRequired => StatusCode::UNAUTHORIZED,
APIError::AudioFileIOError => StatusCode::NOT_FOUND,
APIError::AuthenticationRequired => StatusCode::UNAUTHORIZED,
APIError::DeletingOwnAccount => StatusCode::CONFLICT,
APIError::EmbeddedArtworkNotFound => StatusCode::NOT_FOUND,
APIError::EmptyPassword => StatusCode::BAD_REQUEST,
APIError::EmptyUsername => StatusCode::BAD_REQUEST,
APIError::IncorrectCredentials => StatusCode::UNAUTHORIZED,
@ -87,7 +89,6 @@ impl ResponseError for APIError {
APIError::PlaylistNotFound => StatusCode::NOT_FOUND,
APIError::SongMetadataNotFound => StatusCode::NOT_FOUND,
APIError::ThumbnailFileIOError => StatusCode::NOT_FOUND,
APIError::Unspecified => StatusCode::INTERNAL_SERVER_ERROR,
APIError::UserNotFound => StatusCode::NOT_FOUND,
APIError::VFSPathNotFound => StatusCode::NOT_FOUND,
}
@ -106,7 +107,7 @@ impl FromRequest for Auth {
fn from_request(request: &HttpRequest, payload: &mut Payload) -> Self::Future {
let user_manager = match request.app_data::<Data<user::Manager>>() {
Some(m) => m.clone(),
None => return Box::pin(err(ErrorInternalServerError(APIError::Unspecified))),
None => return Box::pin(err(ErrorInternalServerError(APIError::Internal))),
};
let bearer_auth_future = BearerAuth::from_request(request, payload);
@ -155,7 +156,7 @@ impl FromRequest for AdminRights {
fn from_request(request: &HttpRequest, payload: &mut Payload) -> Self::Future {
let user_manager = match request.app_data::<Data<user::Manager>>() {
Some(m) => m.clone(),
None => return Box::pin(err(ErrorInternalServerError(APIError::Unspecified))),
None => return Box::pin(err(ErrorInternalServerError(APIError::Internal))),
};
let auth_future = Auth::from_request(request, payload);
@ -164,7 +165,7 @@ impl FromRequest for AdminRights {
let user_manager_count = user_manager.clone();
let user_count = block(move || user_manager_count.count()).await;
match user_count {
Err(_) => return Err(ErrorInternalServerError(APIError::Unspecified)),
Err(e) => return Err(e.into()),
Ok(0) => return Ok(AdminRights { auth: None }),
_ => (),
};
@ -175,7 +176,7 @@ impl FromRequest for AdminRights {
if is_admin {
Ok(AdminRights { auth: Some(auth) })
} else {
Err(ErrorForbidden(APIError::Unspecified))
Err(ErrorForbidden(APIError::AdminPermissionRequired))
}
})
}
@ -212,7 +213,7 @@ where
{
actix_web::web::block(f)
.await
.map_err(|_| APIError::Unspecified)
.map_err(|_| APIError::Internal)
.and_then(|r| r.map_err(|e| e.into()))
}
@ -522,13 +523,13 @@ async fn get_thumbnail(
) -> Result<MediaFile, APIError> {
let options = thumbnail::Options::from(options_input.0);
let thumbnail_path = block(move || {
let thumbnail_path = block(move || -> Result<PathBuf, APIError> {
let vfs = vfs_manager.get_vfs()?;
let path = percent_decode_str(&path).decode_utf8_lossy();
let image_path = vfs.virtual_to_real(Path::new(path.as_ref()))?;
thumbnails_manager
.get_thumbnail(&image_path, &options)
.map_err(|_| APIError::Unspecified)
.map_err(|e| e.into())
})
.await?;

View file

@ -1,15 +1,19 @@
use thiserror::Error;
use crate::app::index::QueryError;
use crate::app::{config, ddns, lastfm, playlist, settings, user, vfs};
use crate::app::{config, ddns, lastfm, playlist, settings, thumbnail, user, vfs};
use crate::db;
#[derive(Error, Debug)]
pub enum APIError {
#[error("Administrator permission is required")]
AdminPermissionRequired,
#[error("Authentication is required")]
AuthenticationRequired,
#[error("Incorrect Credentials")]
IncorrectCredentials,
#[error("EmbeddedArtworkNotFound")]
EmbeddedArtworkNotFound,
#[error("EmptyUsername")]
EmptyUsername,
#[error("EmptyPassword")]
@ -38,14 +42,6 @@ pub enum APIError {
SongMetadataNotFound,
#[error("Internal server error")]
Internal,
#[error("Unspecified")]
Unspecified,
}
impl From<anyhow::Error> for APIError {
fn from(_: anyhow::Error) -> Self {
APIError::Unspecified
}
}
impl From<config::Error> for APIError {
@ -78,7 +74,6 @@ impl From<QueryError> for APIError {
QueryError::DatabaseConnection(e) => e.into(),
QueryError::SongNotFound(_) => APIError::SongMetadataNotFound,
QueryError::Vfs(e) => e.into(),
QueryError::Unspecified => APIError::Unspecified,
}
}
}
@ -158,3 +153,17 @@ impl From<lastfm::Error> for APIError {
}
}
}
impl From<thumbnail::Error> for APIError {
fn from(error: thumbnail::Error) -> APIError {
match error {
thumbnail::Error::EmbeddedArtworkNotFound(_) => APIError::EmbeddedArtworkNotFound,
thumbnail::Error::Id3(_) => APIError::Internal,
thumbnail::Error::Image(_) => APIError::Internal,
thumbnail::Error::Io(_, _) => APIError::Internal,
thumbnail::Error::Metaflac(_) => APIError::Internal,
thumbnail::Error::Mp4aMeta(_) => APIError::Internal,
thumbnail::Error::UnsupportedFormat(_) => APIError::Internal,
}
}
}