backend: handle app already running logic + refactor response

This commit is contained in:
2025-08-08 00:17:56 -06:00
parent a11a12828c
commit 2b5b6f190d
6 changed files with 104 additions and 33 deletions
Generated
+1
View File
@@ -671,6 +671,7 @@ dependencies = [
"getrandom 0.3.3", "getrandom 0.3.3",
"hex", "hex",
"hmac-sha256", "hmac-sha256",
"http",
"libc", "libc",
"moonlight-common-c-sys", "moonlight-common-c-sys",
"openssl", "openssl",
+1
View File
@@ -9,6 +9,7 @@ directories = "6.0.0"
getrandom = { version = "0.3.3", features = ["std"] } getrandom = { version = "0.3.3", features = ["std"] }
hex = "0.4.3" hex = "0.4.3"
hmac-sha256 = "1.1.12" hmac-sha256 = "1.1.12"
http = "1.3.1"
libc = "0.2.174" libc = "0.2.174"
moonlight-common-c-sys = { path = "../moonlight-common-c-sys" } moonlight-common-c-sys = { path = "../moonlight-common-c-sys" }
openssl = "0.10.73" openssl = "0.10.73"
+33 -4
View File
@@ -4,7 +4,12 @@ use salvo::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::{debug, error}; use tracing::{debug, error};
use crate::{common::AppResult, state::StateReader}; use crate::{
common,
common::{AppError, AppResult},
responses,
state::StateReader,
};
#[derive(Deserialize)] #[derive(Deserialize)]
struct AppListRespApp { struct AppListRespApp {
@@ -29,6 +34,7 @@ struct App {
title: String, title: String,
id: u64, id: u64,
hdr_supported: bool, hdr_supported: bool,
active: bool,
} }
#[derive(Debug, Serialize, ToSchema)] #[derive(Debug, Serialize, ToSchema)]
@@ -40,7 +46,7 @@ struct GetAppsResponse {
impl crate::backend::Backend { impl crate::backend::Backend {
#[craft(endpoint(status_codes(StatusCode::OK, StatusCode::INTERNAL_SERVER_ERROR)))] #[craft(endpoint(status_codes(StatusCode::OK, StatusCode::INTERNAL_SERVER_ERROR)))]
pub async fn get_apps(self: ::std::sync::Arc<Self>) -> AppResult<Json<GetAppsResponse>> { pub async fn get_apps(self: ::std::sync::Arc<Self>) -> AppResult<Json<GetAppsResponse>> {
let standard_error = Err(crate::common::AppError { let standard_error = Err(AppError {
status_code: StatusCode::INTERNAL_SERVER_ERROR, status_code: StatusCode::INTERNAL_SERVER_ERROR,
description: "failed to get available apps".to_string(), description: "failed to get available apps".to_string(),
}); });
@@ -67,7 +73,29 @@ impl crate::backend::Backend {
}; };
for (_, server) in servers.into_iter() { for (_, server) in servers.into_iter() {
let mut base_url = crate::common::base_url( let mut server_info_base_url = common::base_url(
"https",
&server.host,
server.https_port(),
&unique_id,
"serverinfo",
None,
);
let server_info = common::get_url(&mut server_info_base_url, true)
.await
.unwrap();
debug!("server_info: {server_info}");
let server_info: responses::ServerInfoResponse =
match serde_xml_rs::from_str(&server_info) {
Ok(s) => s,
Err(e) => {
error!("Could not parse serverinfo response: {e}");
return standard_error;
}
};
let mut base_url = common::base_url(
"https", "https",
&server.host, &server.host,
server.https_port(), server.https_port(),
@@ -76,7 +104,7 @@ impl crate::backend::Backend {
None, None,
); );
let resp = match crate::common::get_url(&mut base_url, true).await { let resp = match common::get_url(&mut base_url, true).await {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
error!("could not get applist from server {}: {}", server.name, e); error!("could not get applist from server {}: {}", server.name, e);
@@ -103,6 +131,7 @@ impl crate::backend::Backend {
title: a.app_title, title: a.app_title,
hdr_supported: a.is_hdr_supported, hdr_supported: a.is_hdr_supported,
id: a.id, id: a.id,
active: a.id == server_info.currentgame,
}) })
.rev() .rev()
.collect(); .collect();
@@ -9,6 +9,7 @@ mod common;
mod gamestream; mod gamestream;
mod pair; mod pair;
mod proxy; mod proxy;
mod responses;
mod state; mod state;
mod stream; mod stream;
@@ -0,0 +1,23 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct ServerInfoResponse {
pub hostname: String,
pub appversion: String,
pub GfeVersion: String,
pub uniqueid: uuid::Uuid,
pub HttpsPort: u16,
pub ExternalPort: u16,
pub MaxLumaPixelsHEVC: u64,
pub mac: String,
//pub ServerCommand: String,
//pub Permission: u64,
//pub VirtualDisplayCapable: bool,
//pub VirtualDisplayDriverReady: bool,
pub LocalIP: String,
pub ServerCodecModeSupport: i32,
pub PairStatus: u64,
pub currentgame: u64,
pub state: String,
}
+45 -29
View File
@@ -1,3 +1,5 @@
use std::str::FromStr;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use moonlight_common_c_sys::SCM_H264; use moonlight_common_c_sys::SCM_H264;
use salvo::prelude::*; use salvo::prelude::*;
@@ -6,7 +8,7 @@ use tracing::{debug, error, info};
use crate::{ use crate::{
common::{AppError, AppResult, get_url}, common::{AppError, AppResult, get_url},
proxy, proxy, responses,
state::{GamestreamServer, StateReadAccess, StateReader}, state::{GamestreamServer, StateReadAccess, StateReader},
}; };
@@ -25,33 +27,16 @@ struct PostStreamStartResponse {
//cert_hash: String, //cert_hash: String,
} }
#[derive(Deserialize)]
struct ServerInfoResponse {
hostname: String,
appversion: String,
GfeVersion: String,
uniqueid: uuid::Uuid,
HttpsPort: u16,
ExternalPort: u16,
MaxLumaPixelsHEVC: u64,
mac: String,
//ServerCommand: String,
//Permission: u64,
//VirtualDisplayCapable: bool,
//VirtualDisplayDriverReady: bool,
LocalIP: String,
ServerCodecModeSupport: i32,
PairStatus: u64,
currentgame: u64,
state: String,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct LaunchResponse { struct LaunchResponse {
#[serde(rename = "@status_code")]
status_code: String,
#[serde(rename = "@status_message")]
status_message: Option<String>,
#[serde(rename = "sessionUrl0")] #[serde(rename = "sessionUrl0")]
session_url_0: url::Url, session_url_0: Option<url::Url>,
#[serde(rename = "gamesession")] #[serde(rename = "gamesession")]
game_session: u64, game_session: Option<u64>,
} }
fn get_server(reader: &StateReadAccess, server: &String) -> Result<Option<GamestreamServer>> { fn get_server(reader: &StateReadAccess, server: &String) -> Result<Option<GamestreamServer>> {
@@ -108,7 +93,7 @@ impl crate::backend::Backend {
Ok(s) => match s { Ok(s) => match s {
Some(s) => s, Some(s) => s,
None => { None => {
info!("No server with name: {}", body.server); error!("No server with name: {}", body.server);
return Err(AppError { return Err(AppError {
status_code: StatusCode::BAD_REQUEST, status_code: StatusCode::BAD_REQUEST,
description: "No server configured with that name.".to_string(), description: "No server configured with that name.".to_string(),
@@ -139,8 +124,9 @@ impl crate::backend::Backend {
); );
let server_info = crate::common::get_url(&mut base_url, true).await.unwrap(); let server_info = crate::common::get_url(&mut base_url, true).await.unwrap();
debug!("app_info: {server_info}"); debug!("server_info: {server_info}");
let server_info: ServerInfoResponse = match serde_xml_rs::from_str(&server_info) { let server_info: responses::ServerInfoResponse = match serde_xml_rs::from_str(&server_info)
{
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
error!("Could not parse serverinfo response: {e}"); error!("Could not parse serverinfo response: {e}");
@@ -227,6 +213,36 @@ impl crate::backend::Backend {
} }
}; };
let Ok(launch_resp_status) = http::StatusCode::from_str(&launch_response.status_code)
else {
error!(
"Server returned invalid status code: {}",
launch_response.status_code
);
return standard_error;
};
if !launch_resp_status.is_success() {
let status_message = launch_response
.status_message
.unwrap_or("Server did not provide a status message".to_string());
info!("Could not launch game: {}", status_message,);
return Err(AppError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
description: format!("Could not launch game: {}", status_message),
});
}
let Some(url) = launch_response.session_url_0 else {
error!("Server response was missing session_url_0");
return standard_error;
};
let Some(game_session) = launch_response.game_session else {
error!("Server response was missing game_session");
return standard_error;
};
let stream_id = uuid::Uuid::new_v4(); let stream_id = uuid::Uuid::new_v4();
let server_codec_mode_support = if server_info.ServerCodecModeSupport == 0 { let server_codec_mode_support = if server_info.ServerCodecModeSupport == 0 {
@@ -237,8 +253,8 @@ impl crate::backend::Backend {
let stream = crate::backend::Stream { let stream = crate::backend::Stream {
id: stream_id, id: stream_id,
url: launch_response.session_url_0, url,
game_session: launch_response.game_session, game_session,
server_address: server.host.clone(), server_address: server.host.clone(),
stream_config: body.stream_config.clone(), stream_config: body.stream_config.clone(),
app_version: server_info.appversion, app_version: server_info.appversion,