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",
"hex",
"hmac-sha256",
"http",
"libc",
"moonlight-common-c-sys",
"openssl",
+1
View File
@@ -9,6 +9,7 @@ directories = "6.0.0"
getrandom = { version = "0.3.3", features = ["std"] }
hex = "0.4.3"
hmac-sha256 = "1.1.12"
http = "1.3.1"
libc = "0.2.174"
moonlight-common-c-sys = { path = "../moonlight-common-c-sys" }
openssl = "0.10.73"
+33 -4
View File
@@ -4,7 +4,12 @@ use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use tracing::{debug, error};
use crate::{common::AppResult, state::StateReader};
use crate::{
common,
common::{AppError, AppResult},
responses,
state::StateReader,
};
#[derive(Deserialize)]
struct AppListRespApp {
@@ -29,6 +34,7 @@ struct App {
title: String,
id: u64,
hdr_supported: bool,
active: bool,
}
#[derive(Debug, Serialize, ToSchema)]
@@ -40,7 +46,7 @@ struct GetAppsResponse {
impl crate::backend::Backend {
#[craft(endpoint(status_codes(StatusCode::OK, StatusCode::INTERNAL_SERVER_ERROR)))]
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,
description: "failed to get available apps".to_string(),
});
@@ -67,7 +73,29 @@ impl crate::backend::Backend {
};
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",
&server.host,
server.https_port(),
@@ -76,7 +104,7 @@ impl crate::backend::Backend {
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,
Err(e) => {
error!("could not get applist from server {}: {}", server.name, e);
@@ -103,6 +131,7 @@ impl crate::backend::Backend {
title: a.app_title,
hdr_supported: a.is_hdr_supported,
id: a.id,
active: a.id == server_info.currentgame,
})
.rev()
.collect();
@@ -9,6 +9,7 @@ mod common;
mod gamestream;
mod pair;
mod proxy;
mod responses;
mod state;
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 moonlight_common_c_sys::SCM_H264;
use salvo::prelude::*;
@@ -6,7 +8,7 @@ use tracing::{debug, error, info};
use crate::{
common::{AppError, AppResult, get_url},
proxy,
proxy, responses,
state::{GamestreamServer, StateReadAccess, StateReader},
};
@@ -25,33 +27,16 @@ struct PostStreamStartResponse {
//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)]
struct LaunchResponse {
#[serde(rename = "@status_code")]
status_code: String,
#[serde(rename = "@status_message")]
status_message: Option<String>,
#[serde(rename = "sessionUrl0")]
session_url_0: url::Url,
session_url_0: Option<url::Url>,
#[serde(rename = "gamesession")]
game_session: u64,
game_session: Option<u64>,
}
fn get_server(reader: &StateReadAccess, server: &String) -> Result<Option<GamestreamServer>> {
@@ -108,7 +93,7 @@ impl crate::backend::Backend {
Ok(s) => match s {
Some(s) => s,
None => {
info!("No server with name: {}", body.server);
error!("No server with name: {}", body.server);
return Err(AppError {
status_code: StatusCode::BAD_REQUEST,
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();
debug!("app_info: {server_info}");
let server_info: ServerInfoResponse = match serde_xml_rs::from_str(&server_info) {
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}");
@@ -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 server_codec_mode_support = if server_info.ServerCodecModeSupport == 0 {
@@ -237,8 +253,8 @@ impl crate::backend::Backend {
let stream = crate::backend::Stream {
id: stream_id,
url: launch_response.session_url_0,
game_session: launch_response.game_session,
url,
game_session,
server_address: server.host.clone(),
stream_config: body.stream_config.clone(),
app_version: server_info.appversion,