use anyhow::Context;
use entertainarr_adapter_rss::RssClient;
use entertainarr_adapter_sqlite::Pool;
use entertainarr_adapter_worker::Publisher;
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 scheduler: entertainarr_adapter_scheduler::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 scheduler = self.scheduler.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 publisher = worker.publisher(sqlite_pool.clone());
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_publisher(publisher.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())
.task_publisher(publisher.clone())
.build();
let task_service = TaskService::builder()
.task_repository(sqlite_pool.clone())
.build();
let tvshow_service = TvShowService::builder()
.task_publisher(publisher.clone())
.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())
.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 scheduler = scheduler
.with_media_service(media_service.clone())
.with_podcast_service(podcast_service.clone())
.with_tvshow_service(tvshow_service.clone());
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,
scheduler,
worker,
})
}
}
pub struct Application {
http_server: entertainarr_adapter_http::server::HttpServer,
#[allow(clippy::type_complexity)]
scheduler: entertainarr_adapter_scheduler::Runner<
MediaService<Pool, entertainarr_adapter_filesystem::Client, Publisher>,
PodcastService<RssClient, Pool, Pool, Pool, Pool, Publisher>,
TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Publisher>,
>,
#[allow(clippy::type_complexity)]
worker: entertainarr_adapter_worker::Runner<
MediaService<Pool, entertainarr_adapter_filesystem::Client, Publisher>,
PodcastService<RssClient, Pool, Pool, Pool, Pool, Publisher>,
TaskService<Pool>,
TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Publisher>,
>,
}
impl Application {
pub async fn run(self) -> anyhow::Result<()> {
let scheduler = self.scheduler.start();
let worker = self.worker.start();
let server_res = self.http_server.run().await;
let scheduler_res = scheduler.shutdown().await;
let worker_res = worker.shutdown().await;
match (server_res, scheduler_res, worker_res) {
(Ok(_), Ok(_), Ok(_)) => Ok(()),
other => Err(anyhow::anyhow!(
"server, scheduler or worker failed {other:?}"
)),
}
}
}