1use std::path::PathBuf;
2
3use actix_web::{App, HttpResponse, HttpServer, Responder, get, http::StatusCode, web};
4
5mod cli;
6
7mod config;
8pub(crate) use config::Config;
9
10mod env;
11
12mod error;
13pub(crate) use error::Error;
14
15mod pool;
16pub(crate) use pool::Pool;
17
18pub(crate) mod prelude;
19use prelude::*;
20
21mod state;
22pub(crate) use state::State;
23
24mod wallpaper;
25pub(crate) use wallpaper::Wallpaper;
26
27async fn not_found() -> impl Responder {
28 HttpResponse::build(StatusCode::NOT_FOUND).body("Not Found")
29}
30
31#[get("/api/interval")]
32async fn interval(state: web::Data<State>) -> impl Responder {
33 HttpResponse::build(StatusCode::OK).body(state.interval().to_string())
34}
35
36#[get("/api/pool/{pool_name}/digest")]
37async fn current_digest(path: web::Path<String>, state: web::Data<State>) -> impl Responder {
38 let pool_name = path.into_inner();
39 let wallpaper = match state.current_wallpaper(&pool_name) {
40 Ok(wallpaper) => wallpaper,
41 Err(Error::PoolNotFound(pool_name)) => {
42 return HttpResponse::build(StatusCode::NOT_FOUND)
43 .body(format!("there is no pool named '{}'", pool_name));
44 }
45 Err(Error::PoolEmpty(pool_name)) => {
46 error!("the current wallpaper of a pool was requested, but it is empty");
47 return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).body(format!(
48 "the pool '{}' does not contain any wallpapers",
49 pool_name
50 ));
51 }
52 Err(_) => todo!(
53 "current_wallpaper returned an error that was not expected; you should handle the new error here"
54 ),
55 };
56 let digest = wallpaper.digest().clone();
57 HttpResponse::build(StatusCode::OK).body(digest)
58}
59
60#[get("/api/pool/{pool_name}/wallpaper")]
61async fn current_wallpaper(path: web::Path<String>, state: web::Data<State>) -> impl Responder {
62 let pool_name = path.into_inner();
63 let wallpaper = match state.current_wallpaper(&pool_name) {
64 Ok(wallpaper) => wallpaper,
65 Err(Error::PoolNotFound(pool_name)) => {
66 return HttpResponse::build(StatusCode::NOT_FOUND)
67 .body(format!("there is no pool named '{}'", pool_name));
68 }
69 Err(Error::PoolEmpty(pool_name)) => {
70 error!("the current wallpaper of a pool was requested, but it is empty");
71 return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR).body(format!(
72 "the pool '{}' does not contain any wallpapers",
73 pool_name
74 ));
75 }
76 Err(_) => todo!(
77 "current_wallpaper returned an error that was not expected; you should handle the new error here"
78 ),
79 };
80 let file_path = wallpaper.file_path().clone();
81 let extension = file_path
82 .extension()
83 .expect("files with no extension should be filtered out from the pool")
84 .to_string_lossy()
85 .to_string();
86 let image_content = match web::block(move || std::fs::read(file_path)).await {
87 Ok(Ok(image_content)) => image_content,
88 Ok(Err(e)) => {
89 error!("failed to read the wallpaper file: {}", e);
90 return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
91 .body("failed to read the wallpaper file");
92 }
93 Err(e) => {
94 error!("blocking error: {}", e);
95 return HttpResponse::build(StatusCode::INTERNAL_SERVER_ERROR)
96 .body("internal server error");
97 }
98 };
99 HttpResponse::build(StatusCode::OK)
100 .content_type(format!("image/{}", extension))
101 .body(image_content)
102}
103
104pub async fn run() -> Result<()> {
105 env::load_dotenv()?;
106 let matches = cli::get_command().get_matches();
107 let custom_config_dir = matches.get_one::<PathBuf>("config-dir");
108 let config = Config::load(custom_config_dir)?;
109 let state = State::new(&config)?;
110
111 println!("pools:");
112 for (pool_name, pool) in state.pools() {
113 println!(" {}:", pool_name);
114 for wallpaper in pool.wallpapers() {
115 println!(
116 " -> {}",
117 wallpaper
118 .file_path()
119 .file_stem()
120 .unwrap()
121 .to_string_lossy()
122 .to_string()
123 );
124 }
125 }
126
127 HttpServer::new(move || {
128 App::new()
129 .app_data(web::Data::new(state.clone()))
130 .service(interval)
131 .service(current_digest)
132 .service(current_wallpaper)
133 .default_service(web::route().to(not_found))
134 })
135 .bind(("0.0.0.0", config.port()))
136 .map_err(Error::BindServer)?
137 .run()
138 .await
139 .map_err(|_| Error::RunServer)?;
140
141 Ok(())
142}