polaris-mirror/src/app.rs
2024-10-08 23:38:11 -07:00

215 lines
6.5 KiB
Rust

use std::fs;
use std::path::{Path, PathBuf};
use rand::rngs::OsRng;
use rand::RngCore;
use crate::paths::Paths;
pub mod auth;
pub mod config;
pub mod ddns;
pub mod formats;
pub mod index;
pub mod ndb;
pub mod peaks;
pub mod playlist;
pub mod scanner;
pub mod thumbnail;
#[cfg(test)]
pub mod test;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
ThreadPoolBuilder(#[from] rayon::ThreadPoolBuildError),
#[error(transparent)]
ThreadJoining(#[from] tokio::task::JoinError),
#[error("Filesystem error for `{0}`: `{1}`")]
Io(PathBuf, std::io::Error),
#[error(transparent)]
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("No tracks found in audio file: {0}")]
MediaEmpty(PathBuf),
#[error(transparent)]
MediaDecodeError(symphonia::core::errors::Error),
#[error(transparent)]
MediaDecoderError(symphonia::core::errors::Error),
#[error(transparent)]
MediaPacketError(symphonia::core::errors::Error),
#[error(transparent)]
MediaProbeError(symphonia::core::errors::Error),
#[error(transparent)]
PeaksSerialization(bitcode::Error),
#[error(transparent)]
PeaksDeserialization(bitcode::Error),
#[error(transparent)]
NativeDatabase(#[from] native_db::db_type::Error),
#[error("Could not initialize database")]
NativeDatabaseCreationError(native_db::db_type::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("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("DDNS update URL is invalid")]
DDNSUpdateURLInvalid,
#[error("Could not deserialize configuration: `{0}`")]
ConfigDeserialization(toml::de::Error),
#[error("Could not serialize configuration: `{0}`")]
ConfigSerialization(toml::ser::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("Genre not found")]
GenreNotFound,
#[error("Song not found")]
SongNotFound,
#[error("Invalid search query syntax")]
SearchQueryParseError,
#[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 already exists")]
DuplicateUsername,
#[error("Username does not exist")]
IncorrectUsername,
#[error("Password does not match username")]
IncorrectPassword,
#[error("Invalid auth token")]
InvalidAuthToken,
#[error("Incorrect authorization scope")]
IncorrectAuthorizationScope,
#[error("Failed to hash password")]
PasswordHashing,
#[error("Failed to encode authorization token")]
AuthorizationTokenEncoding,
#[error("Failed to encode Branca token")]
BrancaTokenEncoding,
}
#[derive(Clone)]
pub struct App {
pub port: u16,
pub web_dir_path: PathBuf,
pub swagger_dir_path: PathBuf,
pub ddns_manager: ddns::Manager,
pub scanner: scanner::Scanner,
pub index_manager: index::Manager,
pub config_manager: config::Manager,
pub peaks_manager: peaks::Manager,
pub playlist_manager: playlist::Manager,
pub thumbnail_manager: thumbnail::Manager,
}
impl App {
pub async fn new(port: u16, paths: Paths) -> Result<Self, Error> {
fs::create_dir_all(&paths.data_dir_path)
.map_err(|e| Error::Io(paths.data_dir_path.clone(), e))?;
fs::create_dir_all(&paths.web_dir_path)
.map_err(|e| Error::Io(paths.web_dir_path.clone(), e))?;
fs::create_dir_all(&paths.swagger_dir_path)
.map_err(|e| Error::Io(paths.swagger_dir_path.clone(), e))?;
let peaks_dir_path = paths.cache_dir_path.join("peaks");
fs::create_dir_all(&peaks_dir_path).map_err(|e| Error::Io(peaks_dir_path.clone(), e))?;
let thumbnails_dir_path = paths.cache_dir_path.join("thumbnails");
fs::create_dir_all(&thumbnails_dir_path)
.map_err(|e| Error::Io(thumbnails_dir_path.clone(), e))?;
let auth_secret_file_path = paths.data_dir_path.join("auth.secret");
let auth_secret = Self::get_or_create_auth_secret(&auth_secret_file_path).await?;
let config_manager = config::Manager::new(&paths.config_file_path, auth_secret).await?;
let ddns_manager = ddns::Manager::new(config_manager.clone());
let ndb_manager = ndb::Manager::new(&paths.data_dir_path)?;
let index_manager = index::Manager::new(&paths.data_dir_path).await?;
let scanner = scanner::Scanner::new(index_manager.clone(), config_manager.clone()).await?;
let peaks_manager = peaks::Manager::new(peaks_dir_path);
let playlist_manager = playlist::Manager::new(ndb_manager);
let thumbnail_manager = thumbnail::Manager::new(thumbnails_dir_path);
Ok(Self {
port,
web_dir_path: paths.web_dir_path,
swagger_dir_path: paths.swagger_dir_path,
ddns_manager,
scanner,
index_manager,
config_manager,
peaks_manager,
playlist_manager,
thumbnail_manager,
})
}
async fn get_or_create_auth_secret(path: &Path) -> Result<auth::Secret, Error> {
match tokio::fs::read(&path).await {
Ok(s) => Ok(auth::Secret(
s.try_into()
.map_err(|_| Error::AuthenticationSecretInvalid)?,
)),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
let mut secret = auth::Secret::default();
OsRng.fill_bytes(secret.as_mut());
tokio::fs::write(&path, &secret)
.await
.map_err(|_| Error::AuthenticationSecretInvalid)?;
Ok(secret)
}
Err(e) => return Err(Error::Io(path.to_owned(), e)),
}
}
}