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