A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 147 lines 5.0 kB view raw
1use std::{ 2 path::PathBuf, 3 sync::{mpsc::Sender, Arc, Mutex}, 4}; 5 6use actix_cors::Cors; 7use actix_files::{self as fs, NamedFile}; 8use actix_web::{ 9 error::ErrorNotFound, 10 guard, 11 http::header::{ContentDisposition, DispositionType, HOST}, 12 web::{self, Data}, 13 App, HttpRequest, HttpResponse, HttpServer, Result, 14}; 15use anyhow::Error; 16use async_graphql::{http::GraphiQLSource, Schema}; 17use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse, GraphQLSubscription}; 18use rockbox_library::{create_connection_pool, repo}; 19use rockbox_search::create_indexes; 20use rockbox_sys::events::RockboxCommand; 21use rockbox_webui::{dist, index, index_spa}; 22use sqlx::{Pool, Sqlite}; 23 24use crate::{ 25 schema::{Mutation, Query, Subscription}, 26 RockboxSchema, 27}; 28 29async fn index_ws( 30 schema: web::Data<RockboxSchema>, 31 req: HttpRequest, 32 payload: web::Payload, 33) -> Result<HttpResponse> { 34 GraphQLSubscription::new(Schema::clone(&*schema)).start(&req, payload) 35} 36 37#[actix_web::post("/graphql")] 38async fn index_graphql(schema: web::Data<RockboxSchema>, req: GraphQLRequest) -> GraphQLResponse { 39 schema.execute(req.into_inner()).await.into() 40} 41 42#[actix_web::get("/graphiql")] 43async fn index_graphiql(req: HttpRequest) -> Result<HttpResponse> { 44 let host = req 45 .headers() 46 .get(HOST) 47 .unwrap() 48 .to_str() 49 .unwrap() 50 .split(":") 51 .next() 52 .unwrap(); 53 54 let http_port = std::env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string()); 55 let graphql_endpoint = format!("http://{}:{}/graphql", host, http_port); 56 let ws_endpoint = format!("ws://{}:{}/graphql", host, http_port); 57 Ok(HttpResponse::Ok() 58 .content_type("text/html; charset=utf-8") 59 .body( 60 GraphiQLSource::build() 61 .endpoint(&graphql_endpoint) 62 .subscription_endpoint(&ws_endpoint) 63 .finish(), 64 )) 65} 66 67async fn index_file(req: HttpRequest) -> Result<NamedFile, actix_web::Error> { 68 let id = req.match_info().get("id").unwrap(); 69 let id = id.split('.').next().unwrap(); 70 let mut path = PathBuf::new(); 71 72 println!("id: {}", id); 73 74 let pool = req.app_data::<Pool<Sqlite>>().unwrap(); 75 match repo::track::find(pool.clone(), id).await { 76 Ok(Some(track)) => { 77 path.push(track.path); 78 println!("Serving file: {}", path.display()); 79 let file = NamedFile::open(path)?; 80 Ok(file.set_content_disposition(ContentDisposition { 81 disposition: DispositionType::Attachment, 82 parameters: vec![], 83 })) 84 } 85 Ok(None) => Err(ErrorNotFound("Track not found").into()), 86 Err(_) => Err(ErrorNotFound("Track not found").into()), 87 } 88} 89 90pub async fn start(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> Result<(), Error> { 91 let client = reqwest::Client::new(); 92 let pool = create_connection_pool().await?; 93 let indexes = create_indexes()?; 94 let schema = Schema::build( 95 Query::default(), 96 Mutation::default(), 97 Subscription::default(), 98 ) 99 .data(cmd_tx) 100 .data(client) 101 .data(pool.clone()) 102 .data(indexes) 103 .finish(); 104 105 let graphql_port = std::env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string()); 106 let addr = format!("{}:{}", "0.0.0.0", graphql_port); 107 108 HttpServer::new(move || { 109 let home = std::env::var("HOME").unwrap(); 110 let rockbox_data_dir = format!("{}/.config/rockbox.org", home); 111 let covers_path = format!("{}/covers", rockbox_data_dir); 112 std::fs::create_dir_all(&covers_path).unwrap(); 113 114 let cors = Cors::permissive(); 115 App::new() 116 .app_data(pool.clone()) 117 .app_data(Data::new(schema.clone())) 118 .wrap(cors) 119 .service(index_graphql) 120 .service(index_graphiql) 121 .service( 122 web::resource("/graphql") 123 .guard(guard::Get()) 124 .guard(guard::Header("upgrade", "websocket")) 125 .to(index_ws), 126 ) 127 .service(fs::Files::new("/covers", covers_path).show_files_listing()) 128 .service(index) 129 .route("/tracks", web::get().to(index_spa)) 130 .route("/artists", web::get().to(index_spa)) 131 .route("/albums", web::get().to(index_spa)) 132 .route("/files", web::get().to(index_spa)) 133 .route("/likes", web::get().to(index_spa)) 134 .route("/settings", web::get().to(index_spa)) 135 .route("/artists/{_:.*}", web::get().to(index_spa)) 136 .route("/albums/{_:.*}", web::get().to(index_spa)) 137 .route("/playlists/{_:.*}", web::get().to(index_spa)) 138 .route("/files/{_:.*}", web::get().to(index_spa)) 139 .route("/tracks/{id}", web::get().to(index_file)) 140 .route("/tracks/{id}", web::head().to(index_file)) 141 .service(dist) 142 }) 143 .bind(addr)? 144 .run() 145 .await 146 .map_err(Error::new) 147}