Error types consolidation
This commit is contained in:
parent
8f2566f574
commit
cd45836924
16 changed files with 185 additions and 350 deletions
102
src/app.rs
102
src/app.rs
|
@ -1,7 +1,7 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::db::{self, DB};
|
use crate::db::DB;
|
||||||
use crate::paths::Paths;
|
use crate::paths::Paths;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
@ -22,15 +22,105 @@ pub mod test;
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Collection(#[from] index::Error),
|
ThreadPoolBuilder(#[from] rayon::ThreadPoolBuildError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Config(#[from] config::Error),
|
ThreadJoining(#[from] tokio::task::JoinError),
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] db::Error),
|
|
||||||
#[error("Filesystem error for `{0}`: `{1}`")]
|
#[error("Filesystem error for `{0}`: `{1}`")]
|
||||||
Io(PathBuf, std::io::Error),
|
Io(PathBuf, std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Settings(#[from] settings::Error),
|
Ape(#[from] ape::Error),
|
||||||
|
#[error("ID3 error in `{0}`: `{1}`")]
|
||||||
|
Id3(PathBuf, id3::Error),
|
||||||
|
#[error("Metaflac error in `{0}`: `{1}`")]
|
||||||
|
Metaflac(PathBuf, metaflac::Error),
|
||||||
|
#[error("Mp4aMeta error in `{0}`: `{1}`")]
|
||||||
|
Mp4aMeta(PathBuf, mp4ameta::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Opus(#[from] opus_headers::ParseError),
|
||||||
|
#[error(transparent)]
|
||||||
|
Vorbis(#[from] lewton::VorbisError),
|
||||||
|
#[error("Could not find a Vorbis comment within flac file")]
|
||||||
|
VorbisCommentNotFoundInFlacFile,
|
||||||
|
#[error("Could not read thumbnail image in `{0}`:\n\n{1}")]
|
||||||
|
Image(PathBuf, image::error::ImageError),
|
||||||
|
#[error("This file format is not supported: {0}")]
|
||||||
|
UnsupportedFormat(&'static str),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Database(#[from] sqlx::Error),
|
||||||
|
#[error("Could not initialize database connection pool")]
|
||||||
|
ConnectionPoolBuild,
|
||||||
|
#[error("Could not acquire database connection from pool")]
|
||||||
|
ConnectionPool,
|
||||||
|
#[error("Could not apply database migrations: {0}")]
|
||||||
|
Migration(sqlx::migrate::MigrateError),
|
||||||
|
|
||||||
|
#[error("DDNS update query failed with HTTP status code `{0}`")]
|
||||||
|
UpdateQueryFailed(u16),
|
||||||
|
#[error("DDNS update query failed due to a transport error")]
|
||||||
|
UpdateQueryTransport,
|
||||||
|
|
||||||
|
#[error("Auth secret does not have the expected format")]
|
||||||
|
AuthenticationSecretInvalid,
|
||||||
|
#[error("Missing auth secret")]
|
||||||
|
AuthenticationSecretNotFound,
|
||||||
|
#[error("Missing settings")]
|
||||||
|
MiscSettingsNotFound,
|
||||||
|
#[error("Index album art pattern is not a valid regex")]
|
||||||
|
IndexAlbumArtPatternInvalid,
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Toml(#[from] toml::de::Error),
|
||||||
|
#[error("Could not deserialize collection")]
|
||||||
|
IndexDeserializationError,
|
||||||
|
#[error("Could not serialize collection")]
|
||||||
|
IndexSerializationError,
|
||||||
|
|
||||||
|
#[error("The following virtual path could not be mapped to a real path: `{0}`")]
|
||||||
|
CouldNotMapToRealPath(PathBuf),
|
||||||
|
#[error("User not found")]
|
||||||
|
UserNotFound,
|
||||||
|
#[error("Directory not found: {0}")]
|
||||||
|
DirectoryNotFound(PathBuf),
|
||||||
|
#[error("Artist not found")]
|
||||||
|
ArtistNotFound,
|
||||||
|
#[error("Album not found")]
|
||||||
|
AlbumNotFound,
|
||||||
|
#[error("Song not found")]
|
||||||
|
SongNotFound,
|
||||||
|
#[error("Playlist not found")]
|
||||||
|
PlaylistNotFound,
|
||||||
|
#[error("No embedded artwork was found in `{0}`")]
|
||||||
|
EmbeddedArtworkNotFound(PathBuf),
|
||||||
|
|
||||||
|
#[error("Cannot use empty username")]
|
||||||
|
EmptyUsername,
|
||||||
|
#[error("Cannot use empty password")]
|
||||||
|
EmptyPassword,
|
||||||
|
#[error("Username does not exist")]
|
||||||
|
IncorrectUsername,
|
||||||
|
#[error("Password does not match username")]
|
||||||
|
IncorrectPassword,
|
||||||
|
#[error("Invalid auth token")]
|
||||||
|
InvalidAuthToken,
|
||||||
|
#[error("Incorrect authorization scope")]
|
||||||
|
IncorrectAuthorizationScope,
|
||||||
|
#[error("Last.fm session key is missing")]
|
||||||
|
MissingLastFMSessionKey,
|
||||||
|
#[error("Failed to hash password")]
|
||||||
|
PasswordHashing,
|
||||||
|
#[error("Failed to encode authorization token")]
|
||||||
|
AuthorizationTokenEncoding,
|
||||||
|
#[error("Failed to encode Branca token")]
|
||||||
|
BrancaTokenEncoding,
|
||||||
|
|
||||||
|
#[error("Failed to authenticate with last.fm")]
|
||||||
|
ScrobblerAuthentication(rustfm_scrobble::ScrobblerError),
|
||||||
|
#[error("Failed to emit last.fm scrobble")]
|
||||||
|
Scrobble(rustfm_scrobble::ScrobblerError),
|
||||||
|
#[error("Failed to emit last.fm now playing update")]
|
||||||
|
NowPlaying(rustfm_scrobble::ScrobblerError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -1,24 +1,7 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::io::Read;
|
use std::{io::Read, path::Path};
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use crate::app::{ddns, settings, user, vfs};
|
use crate::app::{ddns, settings, user, vfs, Error};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Ddns(#[from] ddns::Error),
|
|
||||||
#[error("Filesystem error for `{0}`: `{1}`")]
|
|
||||||
Io(PathBuf, std::io::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Settings(#[from] settings::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Toml(#[from] toml::de::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
User(#[from] user::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Vfs(#[from] vfs::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Deserialize)]
|
#[derive(Default, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
|
|
|
@ -3,22 +3,11 @@ use log::{debug, error};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::db::{self, DB};
|
use crate::app::Error;
|
||||||
|
use crate::db::DB;
|
||||||
|
|
||||||
const DDNS_UPDATE_URL: &str = "https://ydns.io/api/v1/update/";
|
const DDNS_UPDATE_URL: &str = "https://ydns.io/api/v1/update/";
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("DDNS update query failed with HTTP status code `{0}`")]
|
|
||||||
UpdateQueryFailed(u16),
|
|
||||||
#[error("DDNS update query failed due to a transport error")]
|
|
||||||
UpdateQueryTransport,
|
|
||||||
#[error(transparent)]
|
|
||||||
DatabaseConnection(#[from] db::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] sqlx::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub ddns_host: String,
|
pub ddns_host: String,
|
||||||
|
|
|
@ -2,31 +2,12 @@ use id3::TagLike;
|
||||||
use lewton::inside_ogg::OggStreamReader;
|
use lewton::inside_ogg::OggStreamReader;
|
||||||
use log::error;
|
use log::error;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
|
use crate::app::Error;
|
||||||
use crate::utils;
|
use crate::utils;
|
||||||
use crate::utils::AudioFormat;
|
use crate::utils::AudioFormat;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Ape(#[from] ape::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Id3(#[from] id3::Error),
|
|
||||||
#[error("Filesystem error for `{0}`: `{1}`")]
|
|
||||||
Io(PathBuf, std::io::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Metaflac(#[from] metaflac::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Mp4aMeta(#[from] mp4ameta::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Opus(#[from] opus_headers::ParseError),
|
|
||||||
#[error(transparent)]
|
|
||||||
Vorbis(#[from] lewton::VorbisError),
|
|
||||||
#[error("Could not find a Vorbis comment within flac file")]
|
|
||||||
VorbisCommentNotFoundInFlacFile,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||||
pub struct SongMetadata {
|
pub struct SongMetadata {
|
||||||
pub disc_number: Option<u32>,
|
pub disc_number: Option<u32>,
|
||||||
|
@ -83,13 +64,15 @@ impl ID3Ext for id3::Tag {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_id3<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
fn read_id3<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
||||||
let tag = id3::Tag::read_from_path(path).or_else(|error| {
|
let tag = id3::Tag::read_from_path(&path)
|
||||||
if let Some(tag) = error.partial_tag {
|
.or_else(|error| {
|
||||||
Ok(tag)
|
if let Some(tag) = error.partial_tag {
|
||||||
} else {
|
Ok(tag)
|
||||||
Err(error)
|
} else {
|
||||||
}
|
Err(error)
|
||||||
})?;
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| Error::Id3(path.as_ref().to_owned(), e))?;
|
||||||
|
|
||||||
let artists = tag.get_text_values("TPE1");
|
let artists = tag.get_text_values("TPE1");
|
||||||
let album_artists = tag.get_text_values("TPE2");
|
let album_artists = tag.get_text_values("TPE2");
|
||||||
|
@ -255,7 +238,8 @@ fn read_opus<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_flac<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
fn read_flac<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
||||||
let tag = metaflac::Tag::read_from_path(path)?;
|
let tag = metaflac::Tag::read_from_path(&path)
|
||||||
|
.map_err(|e| Error::Metaflac(path.as_ref().to_owned(), e))?;
|
||||||
let vorbis = tag
|
let vorbis = tag
|
||||||
.vorbis_comments()
|
.vorbis_comments()
|
||||||
.ok_or(Error::VorbisCommentNotFoundInFlacFile)?;
|
.ok_or(Error::VorbisCommentNotFoundInFlacFile)?;
|
||||||
|
@ -290,7 +274,8 @@ fn read_flac<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_mp4<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
fn read_mp4<P: AsRef<Path>>(path: P) -> Result<SongMetadata, Error> {
|
||||||
let mut tag = mp4ameta::Tag::read_from_path(path)?;
|
let mut tag = mp4ameta::Tag::read_from_path(&path)
|
||||||
|
.map_err(|e| Error::Mp4aMeta(path.as_ref().to_owned(), e))?;
|
||||||
let label_ident = mp4ameta::FreeformIdent::new("com.apple.iTunes", "Label");
|
let label_ident = mp4ameta::FreeformIdent::new("com.apple.iTunes", "Label");
|
||||||
|
|
||||||
Ok(SongMetadata {
|
Ok(SongMetadata {
|
||||||
|
|
|
@ -7,9 +7,8 @@ use log::{error, info};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
use crate::app::scanner;
|
use crate::app::{scanner, Error};
|
||||||
use crate::app::vfs;
|
use crate::db::DB;
|
||||||
use crate::db::{self, DB};
|
|
||||||
|
|
||||||
mod browser;
|
mod browser;
|
||||||
mod collection;
|
mod collection;
|
||||||
|
@ -18,32 +17,6 @@ mod search;
|
||||||
pub use browser::File;
|
pub use browser::File;
|
||||||
pub use collection::{Album, AlbumKey, Artist, ArtistKey, Song, SongKey};
|
pub use collection::{Album, AlbumKey, Artist, ArtistKey, Song, SongKey};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Directory not found: {0}")]
|
|
||||||
DirectoryNotFound(PathBuf),
|
|
||||||
#[error("Artist not found")]
|
|
||||||
ArtistNotFound,
|
|
||||||
#[error("Album not found")]
|
|
||||||
AlbumNotFound,
|
|
||||||
#[error("Song not found")]
|
|
||||||
SongNotFound,
|
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] sqlx::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
DatabaseConnection(#[from] db::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Vfs(#[from] vfs::Error),
|
|
||||||
#[error("Could not deserialize collection")]
|
|
||||||
IndexDeserializationError,
|
|
||||||
#[error("Could not serialize collection")]
|
|
||||||
IndexSerializationError,
|
|
||||||
#[error(transparent)]
|
|
||||||
ThreadPoolBuilder(#[from] rayon::ThreadPoolBuildError),
|
|
||||||
#[error(transparent)]
|
|
||||||
ThreadJoining(#[from] tokio::task::JoinError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
db: DB,
|
db: DB,
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use trie_rs::{Trie, TrieBuilder};
|
use trie_rs::{Trie, TrieBuilder};
|
||||||
|
|
||||||
use crate::app::{index::Error, scanner};
|
use crate::app::{scanner, Error};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum File {
|
pub enum File {
|
||||||
|
|
|
@ -2,25 +2,11 @@ use rustfm_scrobble::{Scrobble, Scrobbler};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use user::AuthToken;
|
use user::AuthToken;
|
||||||
|
|
||||||
use crate::app::{index, user};
|
use crate::app::{index, user, Error};
|
||||||
|
|
||||||
const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
|
const LASTFM_API_KEY: &str = "02b96c939a2b451c31dfd67add1f696e";
|
||||||
const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
|
const LASTFM_API_SECRET: &str = "0f25a80ceef4b470b5cb97d99d4b3420";
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Failed to authenticate with last.fm")]
|
|
||||||
ScrobblerAuthentication(rustfm_scrobble::ScrobblerError),
|
|
||||||
#[error("Failed to emit last.fm scrobble")]
|
|
||||||
Scrobble(rustfm_scrobble::ScrobblerError),
|
|
||||||
#[error("Failed to emit last.fm now playing update")]
|
|
||||||
NowPlaying(rustfm_scrobble::ScrobblerError),
|
|
||||||
#[error(transparent)]
|
|
||||||
Query(#[from] index::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
User(#[from] user::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
index_manager: index::Manager,
|
index_manager: index::Manager,
|
||||||
|
|
|
@ -2,22 +2,8 @@ use core::clone::Clone;
|
||||||
use sqlx::{Acquire, QueryBuilder, Sqlite};
|
use sqlx::{Acquire, QueryBuilder, Sqlite};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::app::vfs;
|
use crate::app::{vfs, Error};
|
||||||
use crate::db::{self, DB};
|
use crate::db::DB;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] sqlx::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
DatabaseConnection(#[from] db::Error),
|
|
||||||
#[error("User not found")]
|
|
||||||
UserNotFound,
|
|
||||||
#[error("Playlist not found")]
|
|
||||||
PlaylistNotFound,
|
|
||||||
#[error(transparent)]
|
|
||||||
Vfs(#[from] vfs::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Manager {
|
pub struct Manager {
|
||||||
|
@ -191,7 +177,7 @@ impl Manager {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::app::test;
|
use crate::app::test;
|
||||||
use crate::test_name;
|
use crate::test_name;
|
||||||
|
|
|
@ -11,7 +11,7 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
use tokio::time::Instant;
|
use tokio::time::Instant;
|
||||||
|
|
||||||
use crate::app::{formats, index, settings, vfs};
|
use crate::app::{formats, index, settings, vfs, Error};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct Directory {
|
pub struct Directory {
|
||||||
|
@ -53,7 +53,7 @@ impl Scanner {
|
||||||
index_manager: index::Manager,
|
index_manager: index::Manager,
|
||||||
settings_manager: settings::Manager,
|
settings_manager: settings::Manager,
|
||||||
vfs_manager: vfs::Manager,
|
vfs_manager: vfs::Manager,
|
||||||
) -> Result<Self, index::Error> {
|
) -> Result<Self, Error> {
|
||||||
let scanner = Self {
|
let scanner = Self {
|
||||||
index_manager,
|
index_manager,
|
||||||
vfs_manager,
|
vfs_manager,
|
||||||
|
@ -100,7 +100,7 @@ impl Scanner {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&mut self) -> Result<(), index::Error> {
|
pub async fn update(&mut self) -> Result<(), Error> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
info!("Beginning collection scan");
|
info!("Beginning collection scan");
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ impl Scan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(self) -> Result<(), index::Error> {
|
pub async fn start(self) -> Result<(), Error> {
|
||||||
let vfs = self.vfs_manager.get_vfs().await?;
|
let vfs = self.vfs_manager.get_vfs().await?;
|
||||||
let roots = vfs.mounts().clone();
|
let roots = vfs.mounts().clone();
|
||||||
|
|
||||||
|
|
|
@ -2,23 +2,8 @@ use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::db::{self, DB};
|
use crate::app::Error;
|
||||||
|
use crate::db::DB;
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Auth secret does not have the expected format")]
|
|
||||||
AuthenticationSecretInvalid,
|
|
||||||
#[error("Missing auth secret")]
|
|
||||||
AuthenticationSecretNotFound,
|
|
||||||
#[error(transparent)]
|
|
||||||
DatabaseConnection(#[from] db::Error),
|
|
||||||
#[error("Missing settings")]
|
|
||||||
MiscSettingsNotFound,
|
|
||||||
#[error("Index album art pattern is not a valid regex")]
|
|
||||||
IndexAlbumArtPatternInvalid,
|
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] sqlx::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct AuthSecret {
|
pub struct AuthSecret {
|
||||||
|
|
|
@ -6,26 +6,9 @@ use std::fs::{self, File};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use crate::app::Error;
|
||||||
use crate::utils::{get_audio_format, AudioFormat};
|
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("Could not read thumbnail from ID3 tag in `{0}`:\n\n{1}")]
|
|
||||||
Id3(PathBuf, id3::Error),
|
|
||||||
#[error("Could not read thumbnail image in `{0}`:\n\n{1}")]
|
|
||||||
Image(PathBuf, image::error::ImageError),
|
|
||||||
#[error("Filesystem error for `{0}`: `{1}`")]
|
|
||||||
Io(PathBuf, std::io::Error),
|
|
||||||
#[error("Could not read thumbnail from flac file in `{0}`:\n\n{1}")]
|
|
||||||
Metaflac(PathBuf, metaflac::Error),
|
|
||||||
#[error("Could not read thumbnail from mp4 file in `{0}`:\n\n{1}")]
|
|
||||||
Mp4aMeta(PathBuf, mp4ameta::Error),
|
|
||||||
#[error("This file format is not supported: {0}")]
|
|
||||||
UnsupportedFormat(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Hash)]
|
#[derive(Debug, Hash)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub max_dimension: Option<u32>,
|
pub max_dimension: Option<u32>,
|
||||||
|
|
|
@ -5,35 +5,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use crate::app::settings::AuthSecret;
|
use crate::app::settings::AuthSecret;
|
||||||
use crate::db::{self, DB};
|
use crate::app::Error;
|
||||||
|
use crate::db::DB;
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] sqlx::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
DatabaseConnection(#[from] db::Error),
|
|
||||||
#[error("Cannot use empty username")]
|
|
||||||
EmptyUsername,
|
|
||||||
#[error("Cannot use empty password")]
|
|
||||||
EmptyPassword,
|
|
||||||
#[error("Username does not exist")]
|
|
||||||
IncorrectUsername,
|
|
||||||
#[error("Password does not match username")]
|
|
||||||
IncorrectPassword,
|
|
||||||
#[error("Invalid auth token")]
|
|
||||||
InvalidAuthToken,
|
|
||||||
#[error("Incorrect authorization scope")]
|
|
||||||
IncorrectAuthorizationScope,
|
|
||||||
#[error("Last.fm session key is missing")]
|
|
||||||
MissingLastFMSessionKey,
|
|
||||||
#[error("Failed to hash password")]
|
|
||||||
PasswordHashing,
|
|
||||||
#[error("Failed to encode authorization token")]
|
|
||||||
AuthorizationTokenEncoding,
|
|
||||||
#[error("Failed to encode Branca token")]
|
|
||||||
BrancaTokenEncoding,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
|
|
|
@ -4,17 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{Acquire, QueryBuilder, Sqlite};
|
use sqlx::{Acquire, QueryBuilder, Sqlite};
|
||||||
use std::path::{self, Path, PathBuf};
|
use std::path::{self, Path, PathBuf};
|
||||||
|
|
||||||
use crate::db::{self, DB};
|
use crate::app::Error;
|
||||||
|
use crate::db::DB;
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("The following virtual path could not be mapped to a real path: `{0}`")]
|
|
||||||
CouldNotMapToRealPath(PathBuf),
|
|
||||||
#[error(transparent)]
|
|
||||||
DatabaseConnection(#[from] db::Error),
|
|
||||||
#[error(transparent)]
|
|
||||||
Database(#[from] sqlx::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||||
pub struct MountDir {
|
pub struct MountDir {
|
||||||
|
|
16
src/db.rs
16
src/db.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::Path;
|
||||||
|
|
||||||
use sqlx::{
|
use sqlx::{
|
||||||
migrate::Migrator,
|
migrate::Migrator,
|
||||||
|
@ -7,19 +7,9 @@ use sqlx::{
|
||||||
Sqlite,
|
Sqlite,
|
||||||
};
|
};
|
||||||
|
|
||||||
static MIGRATOR: Migrator = sqlx::migrate!("src/db");
|
use crate::app::Error;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
static MIGRATOR: Migrator = sqlx::migrate!("src/db");
|
||||||
pub enum Error {
|
|
||||||
#[error("Could not initialize database connection pool")]
|
|
||||||
ConnectionPoolBuild,
|
|
||||||
#[error("Could not acquire database connection from pool")]
|
|
||||||
ConnectionPool,
|
|
||||||
#[error("Filesystem error for `{0}`: `{1}`")]
|
|
||||||
Io(PathBuf, std::io::Error),
|
|
||||||
#[error("Could not apply database migrations: {0}")]
|
|
||||||
Migration(sqlx::migrate::MigrateError),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DB {
|
pub struct DB {
|
||||||
|
|
|
@ -39,13 +39,11 @@ impl IntoResponse for APIError {
|
||||||
APIError::OwnAdminPrivilegeRemoval => StatusCode::CONFLICT,
|
APIError::OwnAdminPrivilegeRemoval => StatusCode::CONFLICT,
|
||||||
APIError::PasswordHashing => StatusCode::INTERNAL_SERVER_ERROR,
|
APIError::PasswordHashing => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
APIError::PlaylistNotFound => StatusCode::NOT_FOUND,
|
APIError::PlaylistNotFound => StatusCode::NOT_FOUND,
|
||||||
APIError::Settings(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
APIError::ThumbnailFlacDecoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
APIError::ThumbnailFlacDecoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
APIError::ThumbnailFileIOError => StatusCode::NOT_FOUND,
|
APIError::ThumbnailFileIOError => StatusCode::NOT_FOUND,
|
||||||
APIError::ThumbnailId3Decoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
APIError::ThumbnailId3Decoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
APIError::ThumbnailImageDecoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
APIError::ThumbnailImageDecoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
APIError::ThumbnailMp4Decoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
APIError::ThumbnailMp4Decoding(_, _) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
APIError::TomlDeserialization(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
APIError::UnsupportedThumbnailFormat(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
APIError::UnsupportedThumbnailFormat(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
APIError::UserNotFound => StatusCode::NOT_FOUND,
|
APIError::UserNotFound => StatusCode::NOT_FOUND,
|
||||||
APIError::VFSPathNotFound => StatusCode::NOT_FOUND,
|
APIError::VFSPathNotFound => StatusCode::NOT_FOUND,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::app::{config, ddns, index, lastfm, playlist, settings, thumbnail, user, vfs};
|
use crate::app;
|
||||||
use crate::db;
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum APIError {
|
pub enum APIError {
|
||||||
|
@ -66,8 +65,6 @@ pub enum APIError {
|
||||||
PasswordHashing,
|
PasswordHashing,
|
||||||
#[error("Playlist not found")]
|
#[error("Playlist not found")]
|
||||||
PlaylistNotFound,
|
PlaylistNotFound,
|
||||||
#[error("Settings error:\n\n{0}")]
|
|
||||||
Settings(settings::Error),
|
|
||||||
#[error("Could not decode thumbnail from flac file `{0}`:\n\n{1}")]
|
#[error("Could not decode thumbnail from flac file `{0}`:\n\n{1}")]
|
||||||
ThumbnailFlacDecoding(PathBuf, metaflac::Error),
|
ThumbnailFlacDecoding(PathBuf, metaflac::Error),
|
||||||
#[error("Thumbnail file could not be opened")]
|
#[error("Thumbnail file could not be opened")]
|
||||||
|
@ -78,8 +75,6 @@ pub enum APIError {
|
||||||
ThumbnailImageDecoding(PathBuf, image::error::ImageError),
|
ThumbnailImageDecoding(PathBuf, image::error::ImageError),
|
||||||
#[error("Could not decode thumbnail from mp4 file `{0}`:\n\n{1}")]
|
#[error("Could not decode thumbnail from mp4 file `{0}`:\n\n{1}")]
|
||||||
ThumbnailMp4Decoding(PathBuf, mp4ameta::Error),
|
ThumbnailMp4Decoding(PathBuf, mp4ameta::Error),
|
||||||
#[error("Toml deserialization error:\n\n{0}")]
|
|
||||||
TomlDeserialization(toml::de::Error),
|
|
||||||
#[error("Unsupported thumbnail format: `{0}`")]
|
#[error("Unsupported thumbnail format: `{0}`")]
|
||||||
UnsupportedThumbnailFormat(&'static str),
|
UnsupportedThumbnailFormat(&'static str),
|
||||||
#[error("User not found")]
|
#[error("User not found")]
|
||||||
|
@ -88,135 +83,63 @@ pub enum APIError {
|
||||||
VFSPathNotFound,
|
VFSPathNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<index::Error> for APIError {
|
impl From<app::Error> for APIError {
|
||||||
fn from(error: index::Error) -> APIError {
|
fn from(error: app::Error) -> APIError {
|
||||||
match error {
|
match error {
|
||||||
index::Error::DirectoryNotFound(d) => APIError::DirectoryNotFound(d),
|
app::Error::ThreadPoolBuilder(_) => APIError::Internal,
|
||||||
index::Error::ArtistNotFound => APIError::ArtistNotFound,
|
app::Error::ThreadJoining(_) => APIError::Internal,
|
||||||
index::Error::AlbumNotFound => APIError::AlbumNotFound,
|
|
||||||
index::Error::SongNotFound => APIError::SongNotFound,
|
|
||||||
index::Error::Database(e) => APIError::Database(e),
|
|
||||||
index::Error::DatabaseConnection(e) => e.into(),
|
|
||||||
index::Error::Vfs(e) => e.into(),
|
|
||||||
index::Error::IndexDeserializationError => APIError::Internal,
|
|
||||||
index::Error::IndexSerializationError => APIError::Internal,
|
|
||||||
index::Error::ThreadPoolBuilder(_) => APIError::Internal,
|
|
||||||
index::Error::ThreadJoining(_) => APIError::Internal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<config::Error> for APIError {
|
app::Error::Io(p, e) => APIError::Io(p, e),
|
||||||
fn from(error: config::Error) -> APIError {
|
app::Error::Ape(_) => APIError::Internal,
|
||||||
match error {
|
app::Error::Id3(p, e) => APIError::ThumbnailId3Decoding(p, e),
|
||||||
config::Error::Ddns(e) => e.into(),
|
app::Error::Metaflac(p, e) => APIError::ThumbnailFlacDecoding(p, e),
|
||||||
config::Error::Io(p, e) => APIError::Io(p, e),
|
app::Error::Mp4aMeta(p, e) => APIError::ThumbnailMp4Decoding(p, e),
|
||||||
config::Error::Settings(e) => e.into(),
|
app::Error::Opus(_) => APIError::Internal,
|
||||||
config::Error::Toml(e) => APIError::TomlDeserialization(e),
|
app::Error::Vorbis(_) => APIError::Internal,
|
||||||
config::Error::User(e) => e.into(),
|
app::Error::VorbisCommentNotFoundInFlacFile => APIError::Internal,
|
||||||
config::Error::Vfs(e) => e.into(),
|
app::Error::Image(p, e) => APIError::ThumbnailImageDecoding(p, e),
|
||||||
}
|
app::Error::UnsupportedFormat(f) => APIError::UnsupportedThumbnailFormat(f),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<playlist::Error> for APIError {
|
app::Error::Database(e) => APIError::Database(e),
|
||||||
fn from(error: playlist::Error) -> APIError {
|
app::Error::ConnectionPoolBuild => APIError::Internal,
|
||||||
match error {
|
app::Error::ConnectionPool => APIError::Internal,
|
||||||
playlist::Error::Database(e) => APIError::Database(e),
|
app::Error::Migration(_) => APIError::Internal,
|
||||||
playlist::Error::DatabaseConnection(e) => e.into(),
|
|
||||||
playlist::Error::PlaylistNotFound => APIError::PlaylistNotFound,
|
|
||||||
playlist::Error::UserNotFound => APIError::UserNotFound,
|
|
||||||
playlist::Error::Vfs(e) => e.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<settings::Error> for APIError {
|
app::Error::UpdateQueryFailed(s) => APIError::DdnsUpdateQueryFailed(s),
|
||||||
fn from(error: settings::Error) -> APIError {
|
app::Error::UpdateQueryTransport => APIError::DdnsUpdateQueryFailed(0),
|
||||||
match error {
|
|
||||||
settings::Error::AuthenticationSecretNotFound => APIError::Settings(error),
|
|
||||||
settings::Error::DatabaseConnection(e) => e.into(),
|
|
||||||
settings::Error::AuthenticationSecretInvalid => APIError::Settings(error),
|
|
||||||
settings::Error::MiscSettingsNotFound => APIError::Settings(error),
|
|
||||||
settings::Error::IndexAlbumArtPatternInvalid => APIError::Settings(error),
|
|
||||||
settings::Error::Database(e) => APIError::Database(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<user::Error> for APIError {
|
app::Error::AuthenticationSecretNotFound => APIError::Internal,
|
||||||
fn from(error: user::Error) -> APIError {
|
app::Error::AuthenticationSecretInvalid => APIError::Internal,
|
||||||
match error {
|
app::Error::MiscSettingsNotFound => APIError::Internal,
|
||||||
user::Error::AuthorizationTokenEncoding => APIError::AuthorizationTokenEncoding,
|
app::Error::IndexAlbumArtPatternInvalid => APIError::Internal,
|
||||||
user::Error::BrancaTokenEncoding => APIError::BrancaTokenEncoding,
|
|
||||||
user::Error::Database(e) => APIError::Database(e),
|
|
||||||
user::Error::DatabaseConnection(e) => e.into(),
|
|
||||||
user::Error::EmptyPassword => APIError::EmptyPassword,
|
|
||||||
user::Error::EmptyUsername => APIError::EmptyUsername,
|
|
||||||
user::Error::IncorrectAuthorizationScope => APIError::IncorrectCredentials,
|
|
||||||
user::Error::IncorrectPassword => APIError::IncorrectCredentials,
|
|
||||||
user::Error::IncorrectUsername => APIError::IncorrectCredentials,
|
|
||||||
user::Error::InvalidAuthToken => APIError::IncorrectCredentials,
|
|
||||||
user::Error::MissingLastFMSessionKey => APIError::IncorrectCredentials,
|
|
||||||
user::Error::PasswordHashing => APIError::PasswordHashing,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<vfs::Error> for APIError {
|
app::Error::Toml(_) => APIError::Internal,
|
||||||
fn from(error: vfs::Error) -> APIError {
|
app::Error::IndexDeserializationError => APIError::Internal,
|
||||||
match error {
|
app::Error::IndexSerializationError => APIError::Internal,
|
||||||
vfs::Error::CouldNotMapToRealPath(_) => APIError::VFSPathNotFound,
|
|
||||||
vfs::Error::Database(e) => APIError::Database(e),
|
|
||||||
vfs::Error::DatabaseConnection(e) => e.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ddns::Error> for APIError {
|
app::Error::CouldNotMapToRealPath(_) => APIError::VFSPathNotFound,
|
||||||
fn from(error: ddns::Error) -> APIError {
|
app::Error::UserNotFound => APIError::UserNotFound,
|
||||||
match error {
|
app::Error::DirectoryNotFound(d) => APIError::DirectoryNotFound(d),
|
||||||
ddns::Error::Database(e) => APIError::Database(e),
|
app::Error::ArtistNotFound => APIError::ArtistNotFound,
|
||||||
ddns::Error::DatabaseConnection(e) => e.into(),
|
app::Error::AlbumNotFound => APIError::AlbumNotFound,
|
||||||
ddns::Error::UpdateQueryFailed(s) => APIError::DdnsUpdateQueryFailed(s),
|
app::Error::SongNotFound => APIError::SongNotFound,
|
||||||
ddns::Error::UpdateQueryTransport => APIError::DdnsUpdateQueryFailed(0),
|
app::Error::PlaylistNotFound => APIError::PlaylistNotFound,
|
||||||
}
|
app::Error::EmbeddedArtworkNotFound(_) => APIError::EmbeddedArtworkNotFound,
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<db::Error> for APIError {
|
app::Error::EmptyUsername => APIError::EmptyUsername,
|
||||||
fn from(error: db::Error) -> APIError {
|
app::Error::EmptyPassword => APIError::EmptyPassword,
|
||||||
match error {
|
app::Error::IncorrectUsername => APIError::IncorrectCredentials,
|
||||||
db::Error::ConnectionPoolBuild => APIError::Internal,
|
app::Error::IncorrectPassword => APIError::IncorrectCredentials,
|
||||||
db::Error::ConnectionPool => APIError::Internal,
|
app::Error::InvalidAuthToken => APIError::IncorrectCredentials,
|
||||||
db::Error::Io(p, e) => APIError::Io(p, e),
|
app::Error::IncorrectAuthorizationScope => APIError::IncorrectCredentials,
|
||||||
db::Error::Migration(_) => APIError::Internal,
|
app::Error::MissingLastFMSessionKey => APIError::IncorrectCredentials,
|
||||||
}
|
app::Error::PasswordHashing => APIError::PasswordHashing,
|
||||||
}
|
app::Error::AuthorizationTokenEncoding => APIError::AuthorizationTokenEncoding,
|
||||||
}
|
app::Error::BrancaTokenEncoding => APIError::BrancaTokenEncoding,
|
||||||
|
|
||||||
impl From<lastfm::Error> for APIError {
|
app::Error::ScrobblerAuthentication(e) => APIError::LastFMScrobblerAuthentication(e),
|
||||||
fn from(error: lastfm::Error) -> APIError {
|
app::Error::Scrobble(e) => APIError::LastFMScrobble(e),
|
||||||
match error {
|
app::Error::NowPlaying(e) => APIError::LastFMNowPlaying(e),
|
||||||
lastfm::Error::ScrobblerAuthentication(e) => APIError::LastFMScrobblerAuthentication(e),
|
|
||||||
lastfm::Error::Scrobble(e) => APIError::LastFMScrobble(e),
|
|
||||||
lastfm::Error::NowPlaying(e) => APIError::LastFMNowPlaying(e),
|
|
||||||
lastfm::Error::Query(e) => e.into(),
|
|
||||||
lastfm::Error::User(e) => e.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<thumbnail::Error> for APIError {
|
|
||||||
fn from(error: thumbnail::Error) -> APIError {
|
|
||||||
match error {
|
|
||||||
thumbnail::Error::EmbeddedArtworkNotFound(_) => APIError::EmbeddedArtworkNotFound,
|
|
||||||
thumbnail::Error::Id3(p, e) => APIError::ThumbnailId3Decoding(p, e),
|
|
||||||
thumbnail::Error::Image(p, e) => APIError::ThumbnailImageDecoding(p, e),
|
|
||||||
thumbnail::Error::Io(p, e) => APIError::Io(p, e),
|
|
||||||
thumbnail::Error::Metaflac(p, e) => APIError::ThumbnailFlacDecoding(p, e),
|
|
||||||
thumbnail::Error::Mp4aMeta(p, e) => APIError::ThumbnailMp4Decoding(p, e),
|
|
||||||
thumbnail::Error::UnsupportedFormat(f) => APIError::UnsupportedThumbnailFormat(f),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue