use anyhow::Context;
use entertainarr_adapter_rss::RssClient;
use entertainarr_adapter_sqlite::Pool;
use entertainarr_domain::auth::AuthenticationService;
use entertainarr_domain::media::MediaService;
use entertainarr_domain::podcast::PodcastService;
use entertainarr_domain::task::TaskService;
use entertainarr_domain::tvshow::TvShowService;
mod client;
pub mod tracing;
#[derive(serde::Deserialize)]
pub struct Config {
#[serde(default)]
pub http_server: entertainarr_adapter_http::server::Config,
#[serde(default)]
pub jsonwebtoken: entertainarr_adapter_jsonwebtoken::Config,
#[serde(default)]
pub rss: entertainarr_adapter_rss::Config,
#[serde(default)]
pub sqlite: entertainarr_adapter_sqlite::Config,
#[serde(default)]
pub storage: entertainarr_adapter_filesystem::Config,
pub tmdb: entertainarr_adapter_tmdb::Config,
#[serde(default)]
pub worker: entertainarr_adapter_worker::Config,
}
impl Config {
pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> anyhow::Result<Self> {
let data = std::fs::read(path.as_ref())
.with_context(|| format!("unable to open configuration file on {:?}", path.as_ref()))?;
toml::from_slice(data.as_ref()).context("unable to deserialize config")
}
pub async fn build(self) -> anyhow::Result<Application> {
let http_server = self.http_server.builder()?;
let jsonwebtoken = self.jsonwebtoken.build()?;
let rss_client = self.rss.build()?;
let sqlite_pool = self.sqlite.build().await?;
let storage = self.storage.build().await?;
let tmdb = self.tmdb.build()?;
let worker = self.worker.build();
let authentication_service = AuthenticationService::builder()
.authentication_repository(sqlite_pool.clone())
.token_repository(jsonwebtoken)
.build();
let media_service = MediaService::builder()
.media_repository(sqlite_pool.clone())
.media_store(storage)
.task_repository(sqlite_pool.clone())
.build();
let podcast_service = PodcastService::builder()
.rss_feed_loader(rss_client)
.podcast_repository(sqlite_pool.clone())
.podcast_episode_repository(sqlite_pool.clone())
.podcast_subscription_repository(sqlite_pool.clone())
.podcast_task_repository(sqlite_pool.clone())
.build();
let task_service = TaskService::builder()
.task_repository(sqlite_pool.clone())
.build();
let tvshow_service = TvShowService::builder()
.tvshow_provider(tmdb)
.tvshow_repository(sqlite_pool.clone())
.tvshow_episode_repository(sqlite_pool.clone())
.tvshow_episode_file_repository(sqlite_pool.clone())
.tvshow_season_repository(sqlite_pool.clone())
.tvshow_subscription_repository(sqlite_pool.clone())
.tvshow_task_repository(sqlite_pool.clone())
.build();
let http_server = http_server
.with_authentication_service(authentication_service)
.with_client_service(crate::client::ClientService)
.with_media_service(media_service.clone())
.with_podcast_service(podcast_service.clone())
.with_tvshow_service(tvshow_service.clone())
.build()?;
let worker = worker
.with_media_service(media_service)
.with_podcast_service(podcast_service)
.with_task_service(task_service)
.with_tvshow_service(tvshow_service);
Ok(Application {
http_server,
worker,
})
}
}
pub struct Application {
http_server: entertainarr_adapter_http::server::HttpServer,
#[allow(clippy::type_complexity)]
worker: entertainarr_adapter_worker::Runner<
MediaService<Pool, entertainarr_adapter_filesystem::Client, Pool>,
PodcastService<RssClient, Pool, Pool, Pool, Pool>,
TaskService<Pool>,
TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Pool>,
>,
}
impl Application {
pub async fn run(self) -> anyhow::Result<()> {
let worker = self.worker.start();
let server_res = self.http_server.run().await;
let worker_res = worker.shutdown().await;
match (server_res, worker_res) {
(Ok(_), Ok(_)) => Ok(()),
(Ok(_), Err(err)) | (Err(err), Ok(_)) => Err(err),
(Err(server_err), Err(worker_err)) => Err(anyhow::anyhow!(
"server and worker failed, {server_err:?} and {worker_err:?}"
)),
}
}
}