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#[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
106pub 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}