mural_server/
lib.rs

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}