Skip to main content

entertainarr/
lib.rs

1use anyhow::Context;
2use entertainarr_adapter_rss::RssClient;
3use entertainarr_adapter_sqlite::Pool;
4use entertainarr_adapter_worker::Publisher;
5use entertainarr_domain::auth::AuthenticationService;
6use entertainarr_domain::media::MediaService;
7use entertainarr_domain::podcast::PodcastService;
8use entertainarr_domain::task::TaskService;
9use entertainarr_domain::tvshow::TvShowService;
10
11mod client;
12pub mod tracing;
13
14/// Entertainarr main configuration
15#[derive(serde::Deserialize)]
16pub struct Config {
17    #[serde(default)]
18    pub http_server: entertainarr_adapter_http::server::Config,
19    #[serde(default)]
20    pub jsonwebtoken: entertainarr_adapter_jsonwebtoken::Config,
21    #[serde(default)]
22    pub rss: entertainarr_adapter_rss::Config,
23    #[serde(default)]
24    pub scheduler: entertainarr_adapter_scheduler::Config,
25    #[serde(default)]
26    pub sqlite: entertainarr_adapter_sqlite::Config,
27    #[serde(default)]
28    pub storage: entertainarr_adapter_filesystem::Config,
29    pub tmdb: entertainarr_adapter_tmdb::Config,
30    #[serde(default)]
31    pub worker: entertainarr_adapter_worker::Config,
32}
33
34impl Config {
35    pub fn from_path<P: AsRef<std::path::Path>>(path: P) -> anyhow::Result<Self> {
36        let data = std::fs::read(path.as_ref())
37            .with_context(|| format!("unable to open configuration file on {:?}", path.as_ref()))?;
38        toml::from_slice(data.as_ref()).context("unable to deserialize config")
39    }
40
41    pub async fn build(self) -> anyhow::Result<Application> {
42        let http_server = self.http_server.builder()?;
43        let jsonwebtoken = self.jsonwebtoken.build()?;
44        let rss_client = self.rss.build()?;
45        let scheduler = self.scheduler.build();
46        let sqlite_pool = self.sqlite.build().await?;
47        let storage = self.storage.build().await?;
48        let tmdb = self.tmdb.build()?;
49        let worker = self.worker.build();
50        let publisher = worker.publisher(sqlite_pool.clone());
51        let authentication_service = AuthenticationService::builder()
52            .authentication_repository(sqlite_pool.clone())
53            .token_repository(jsonwebtoken)
54            .build();
55        let media_service = MediaService::builder()
56            .media_repository(sqlite_pool.clone())
57            .media_store(storage)
58            .task_publisher(publisher.clone())
59            .build();
60        let podcast_service = PodcastService::builder()
61            .rss_feed_loader(rss_client)
62            .podcast_repository(sqlite_pool.clone())
63            .podcast_episode_repository(sqlite_pool.clone())
64            .podcast_subscription_repository(sqlite_pool.clone())
65            .podcast_task_repository(sqlite_pool.clone())
66            .task_publisher(publisher.clone())
67            .build();
68        let task_service = TaskService::builder()
69            .task_repository(sqlite_pool.clone())
70            .build();
71        let tvshow_service = TvShowService::builder()
72            .task_publisher(publisher.clone())
73            .tvshow_provider(tmdb)
74            .tvshow_repository(sqlite_pool.clone())
75            .tvshow_episode_repository(sqlite_pool.clone())
76            .tvshow_episode_file_repository(sqlite_pool.clone())
77            .tvshow_season_repository(sqlite_pool.clone())
78            .tvshow_subscription_repository(sqlite_pool.clone())
79            .build();
80
81        let http_server = http_server
82            .with_authentication_service(authentication_service)
83            .with_client_service(crate::client::ClientService)
84            .with_media_service(media_service.clone())
85            .with_podcast_service(podcast_service.clone())
86            .with_tvshow_service(tvshow_service.clone())
87            .build()?;
88        let scheduler = scheduler
89            .with_media_service(media_service.clone())
90            .with_podcast_service(podcast_service.clone())
91            .with_tvshow_service(tvshow_service.clone());
92        let worker = worker
93            .with_media_service(media_service)
94            .with_podcast_service(podcast_service)
95            .with_task_service(task_service)
96            .with_tvshow_service(tvshow_service);
97
98        Ok(Application {
99            http_server,
100            scheduler,
101            worker,
102        })
103    }
104}
105
106/// Entertainarr application
107pub struct Application {
108    http_server: entertainarr_adapter_http::server::HttpServer,
109    #[allow(clippy::type_complexity)]
110    scheduler: entertainarr_adapter_scheduler::Runner<
111        MediaService<Pool, entertainarr_adapter_filesystem::Client, Publisher>,
112        PodcastService<RssClient, Pool, Pool, Pool, Pool, Publisher>,
113        TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Publisher>,
114    >,
115    #[allow(clippy::type_complexity)]
116    worker: entertainarr_adapter_worker::Runner<
117        MediaService<Pool, entertainarr_adapter_filesystem::Client, Publisher>,
118        PodcastService<RssClient, Pool, Pool, Pool, Pool, Publisher>,
119        TaskService<Pool>,
120        TvShowService<entertainarr_adapter_tmdb::Client, Pool, Pool, Pool, Pool, Pool, Publisher>,
121    >,
122}
123
124impl Application {
125    pub async fn run(self) -> anyhow::Result<()> {
126        let scheduler = self.scheduler.start();
127        let worker = self.worker.start();
128        let server_res = self.http_server.run().await;
129        let scheduler_res = scheduler.shutdown().await;
130        let worker_res = worker.shutdown().await;
131        match (server_res, scheduler_res, worker_res) {
132            (Ok(_), Ok(_), Ok(_)) => Ok(()),
133            other => Err(anyhow::anyhow!(
134                "server, scheduler or worker failed {other:?}"
135            )),
136        }
137    }
138}