wallheaven 1.1.1

Random image fetcher for wallhaven.cc
#[cfg(target_family = "unix")]
extern crate daemonize;
#[cfg(target_family = "unix")]
use std::fs::File;

use axum::{
    extract::Path,
    http::{HeaderValue, Method, StatusCode},
    response::{IntoResponse, Json},
    routing::get,
    Router,
};

#[cfg(target_family = "unix")]
use daemonize::Daemonize;

#[cfg(target_family = "windows")]
use {std::env, std::process::Stdio};

use serde_json::{json, Value};
use std::process::Command;
use tokio::{runtime::Runtime, signal};
use tower_http::cors::CorsLayer;

fn main() {
    #[cfg(target_family = "windows")]
    let args: Vec<String> = env::args().collect();
    #[cfg(target_family = "windows")]
    let is_daemon = args.contains(&"--daemon".to_string());

    let output = Command::new("wallheaven").arg("-h").output();
    if !output.is_ok() {
        panic!("Wallheaven not found. http://github.com/davenicholson-xyz/wallheaven");
    }

    let app = Router::new()
        .route("/", get(ping))
        .route("/wp/:id", get(set_wallpaper))
        .route("/current", get(get_current))
        .layer(
            CorsLayer::new()
                .allow_origin("http://localhost:2388".parse::<HeaderValue>().unwrap())
                .allow_origin("https://wallhaven.cc".parse::<HeaderValue>().unwrap())
                .allow_methods(Method::GET),
        );

    #[cfg(target_family = "unix")]
    daemonize_unix();

    #[cfg(target_family = "windows")]
    if !is_daemon {
        daemonize_windows();
        return;
    }

    let runtime = Runtime::new().unwrap();

    runtime.block_on(async {
        let listener = tokio::net::TcpListener::bind("0.0.0.0:2388").await.unwrap();
        axum::serve(listener, app)
            .with_graceful_shutdown(shutdown_signal())
            .await
            .unwrap()
    })
}

async fn ping() -> Json<Value> {
    Json(json!({ "ping" : "pong"}))
}

async fn set_wallpaper(Path(id): Path<String>) -> impl IntoResponse {
    match Command::new("wallheaven").arg("--id").arg(&id).output() {
        Ok(output) => {
            if output.status.success() {
                return (StatusCode::OK, Json(json!({"wallpaper" : id })));
            } else {
                let msg = String::from_utf8_lossy(&output.stderr);
                return (
                    StatusCode::BAD_REQUEST,
                    Json(json!({"error" : msg.to_string().trim()})),
                );
            }
        }
        Err(e) => {
            return (
                StatusCode::BAD_REQUEST,
                Json(json!({"error" : e.to_string().trim()})),
            )
        }
    }
}

async fn get_current() -> Json<Value> {
    let output = Command::new("wallheaven")
        .arg("-u")
        .output()
        .expect("problemo");

    let current = String::from_utf8(output.stdout).expect("unknown");
    Json(json!({"current" : current.trim() }))
}

async fn shutdown_signal() {
    signal::ctrl_c()
        .await
        .expect("failed to install CTRL+C signal handler");
}

#[cfg(target_family = "unix")]
fn daemonize_unix() {
    let stdout = File::create("/tmp/wallheavend.out").unwrap();
    let stderr = File::create("/tmp/wallheavend.err").unwrap();

    let daemonize = Daemonize::new()
        .pid_file("/tmp/wallheavend.pid")
        .stdout(stdout)
        .stderr(stderr);

    match daemonize.start() {
        Ok(_) => println!("Successfully daemonized"),
        Err(e) => eprintln!("Error: {}", e),
    }
}

#[cfg(target_family = "windows")]
fn daemonize_windows() {
    use std::os::windows::process::CommandExt;
    const DETACHED_PROCESS: u32 = 0x00000008;
    const CREATE_NO_WINDOW: u32 = 0x08000000;

    let result = Command::new(std::env::current_exe().unwrap())
        .arg("--daemon")
        .creation_flags(DETACHED_PROCESS | CREATE_NO_WINDOW)
        .stdout(Stdio::null())
        .stderr(Stdio::null())
        .spawn();

    match result {
        Ok(_) => println!("Wallheavend running in background"),
        Err(e) => eprintln!("Failed to daemonize application: {}", e),
    }

    std::process::exit(0);
}