meta: renamed config to state and wrapped in a server object

This commit is contained in:
2025-07-18 18:07:33 -06:00
parent 6ae459ddf5
commit 053ed8c054
6 changed files with 190 additions and 173 deletions
+3 -3
View File
@@ -4,7 +4,7 @@ use salvo::prelude::*;
use serde::{Deserialize, Serialize};
use tracing::{debug, error};
use crate::{common::AppResult, config::ConfigReader};
use crate::{common::AppResult, state::StateReader};
#[derive(Deserialize)]
struct AppListRespApp {
@@ -37,7 +37,7 @@ struct GetAppsResponse {
}
#[craft]
impl crate::config::ConfigFile {
impl crate::server::Server {
#[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 {
@@ -45,7 +45,7 @@ impl crate::config::ConfigFile {
description: "failed to get available apps".to_string(),
});
let reader = self.read().await;
let reader = self.state.read().await;
let unique_id = match reader.unique_id() {
Ok(u) => u,
Err(e) => {
-158
View File
@@ -1,158 +0,0 @@
use std::{
collections::HashMap,
fs::{self, File},
path::PathBuf,
};
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use tokio::sync::{RwLockReadGuard, RwLockWriteGuard};
#[derive(Serialize, Deserialize)]
pub struct Server {
pub name: String,
pub host: String,
pub base_port: u16,
}
impl Server {
pub fn http_port(&self) -> u16 {
self.base_port + 189
}
pub fn https_port(&self) -> u16 {
self.base_port + 184
}
}
#[derive(Serialize, Deserialize)]
struct Config {
servers: HashMap<String, Server>,
unique_id: String,
}
pub struct ConfigFile {
lock: tokio::sync::RwLock<()>,
path: PathBuf,
}
pub trait ConfigReader {
fn servers(&self) -> Result<HashMap<String, Server>>;
fn unique_id(&self) -> Result<String>;
}
pub trait ConfigWriter {
fn add_server(&self, server: Server) -> Result<()>;
}
pub struct ConfigReadAccess<'a> {
_guard: RwLockReadGuard<'a, ()>,
config: &'a ConfigFile,
}
pub struct ConfigWriteAccess<'a> {
_guard: RwLockWriteGuard<'a, ()>,
config: &'a ConfigFile,
}
impl ConfigFile {
fn load_config(&self) -> Result<Config> {
tracing::debug!("parsing config file");
let config_file = File::open(&self.path)?;
Ok(serde_json::from_reader(config_file)?)
}
fn write_config(&self, config: Config) -> Result<()> {
tracing::debug!("serializing config file");
let config_file = File::create(&self.path)?;
Ok(serde_json::to_writer_pretty(config_file, &config)?)
}
pub fn new() -> Result<Self> {
let project_dirs =
directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy")
.ok_or(anyhow::anyhow!("Could not get project dirs"))?;
let data_dir = project_dirs.data_dir();
fs::create_dir_all(data_dir)?;
let state_path = data_dir.join("state.json");
if let Err(e) = File::open(&state_path) {
if e.kind() == std::io::ErrorKind::NotFound {
write_default_config_to_path(&state_path)?;
} else {
return Err(anyhow!(e));
}
}
Ok(ConfigFile {
lock: tokio::sync::RwLock::new(()),
path: state_path,
})
}
}
impl ConfigFile {
pub async fn read(&self) -> ConfigReadAccess {
ConfigReadAccess {
_guard: self.lock.read().await,
config: self,
}
}
pub async fn write(&self) -> ConfigWriteAccess {
ConfigWriteAccess {
_guard: self.lock.write().await,
config: self,
}
}
}
impl<'a> ConfigReader for ConfigReadAccess<'a> {
fn servers(&self) -> Result<HashMap<String, Server>> {
let config = self.config.load_config()?;
Ok(config.servers)
}
fn unique_id(&self) -> Result<String> {
let config = self.config.load_config()?;
Ok(config.unique_id)
}
}
impl<'a> ConfigWriter for ConfigWriteAccess<'a> {
fn add_server(&self, server: Server) -> Result<()> {
let mut config = self.config.load_config()?;
if config.servers.contains_key(&server.name) {
return Err(anyhow!(
"cannot add duplicate server with name: {}",
server.name
));
}
config.servers.insert(server.name.clone(), server);
self.config.write_config(config)?;
Ok(())
}
}
pub fn get_unique_id() -> Result<String> {
let mut bytes = [0u8; 8];
openssl::rand::rand_bytes(&mut bytes)?;
Ok(hex::encode(bytes))
}
fn write_default_config_to_path(path: &PathBuf) -> Result<()> {
let default_config = Config {
servers: HashMap::new(),
unique_id: get_unique_id()?,
};
let config_file = File::create(path)?;
Ok(serde_json::to_writer_pretty(config_file, &default_config)?)
}
+6 -5
View File
@@ -10,8 +10,9 @@ use moonlight_common_c_sys::{
mod apps;
mod certs;
mod common;
mod config;
mod pair;
mod server;
mod state;
#[allow(unused)]
fn get_server_info() -> _SERVER_INFORMATION {
@@ -113,12 +114,12 @@ async fn main() -> anyhow::Result<()> {
.with_max_level(tracing::Level::DEBUG)
.init();
let config = config::ConfigFile::new()?;
let config_arc = std::sync::Arc::new(config);
let server = server::Server::new()?;
let server_arc = std::sync::Arc::new(server);
let router = Router::new()
.push(Router::with_path("pair").post(config_arc.post_pair()))
.push(Router::with_path("apps").get(config_arc.get_apps()));
.push(Router::with_path("pair").post(server_arc.post_pair()))
.push(Router::with_path("apps").get(server_arc.get_apps()));
let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
+7 -7
View File
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use tracing::{debug, error, info};
use crate::common::{AppError, AppResult, get_url};
use crate::config::{ConfigReader, ConfigWriter};
use crate::state::{StateReader, StateWriter};
#[derive(Debug, Deserialize, ToSchema)]
struct PostPairParams {
@@ -291,7 +291,7 @@ async fn send_client_pairing_secret(
}
#[craft]
impl crate::config::ConfigFile {
impl crate::server::Server {
#[craft(endpoint(status_codes(
StatusCode::OK,
StatusCode::BAD_REQUEST,
@@ -303,13 +303,13 @@ impl crate::config::ConfigFile {
) -> AppResult<()> {
let params = body.into_inner();
let server = crate::config::Server {
let server = crate::state::GamestreamServer {
host: params.host,
base_port: params.base_port,
name: params.name,
};
let reader = self.read().await;
let reader = self.state.read().await;
let servers = match reader.servers() {
Ok(s) => s,
@@ -333,7 +333,7 @@ impl crate::config::ConfigFile {
});
}
let unique_id = match self.read().await.unique_id() {
let unique_id = match self.state.read().await.unique_id() {
Ok(u) => u,
Err(e) => {
error!("Could not get unique id: {e}");
@@ -457,10 +457,10 @@ impl crate::config::ConfigFile {
);
}
match self.write().await.add_server(server) {
match self.state.write().await.add_server(server) {
Ok(_) => (),
Err(e) => {
error!("Could not write to config file: {e}");
error!("Could not write to state file: {e}");
return Err(AppError {
status_code: StatusCode::INTERNAL_SERVER_ERROR,
description: "Pairing failed".to_string(),
@@ -0,0 +1,16 @@
use anyhow::Result;
use crate::state::StateFile;
pub struct Server {
pub state: StateFile,
}
impl Server {
pub fn new() -> Result<Self> {
Ok(Server {
state: StateFile::new()?
})
}
}
+158
View File
@@ -0,0 +1,158 @@
use std::{
collections::HashMap,
fs::{self, File},
path::PathBuf,
};
use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};
use tokio::sync::{RwLockReadGuard, RwLockWriteGuard};
#[derive(Serialize, Deserialize)]
pub struct GamestreamServer {
pub name: String,
pub host: String,
pub base_port: u16,
}
impl GamestreamServer {
pub fn http_port(&self) -> u16 {
self.base_port + 189
}
pub fn https_port(&self) -> u16 {
self.base_port + 184
}
}
#[derive(Serialize, Deserialize)]
struct State {
servers: HashMap<String, GamestreamServer>,
unique_id: String,
}
pub struct StateFile {
lock: tokio::sync::RwLock<()>,
path: PathBuf,
}
pub trait StateReader {
fn servers(&self) -> Result<HashMap<String, GamestreamServer>>;
fn unique_id(&self) -> Result<String>;
}
pub trait StateWriter {
fn add_server(&self, server: GamestreamServer) -> Result<()>;
}
pub struct StateReadAccess<'a> {
_guard: RwLockReadGuard<'a, ()>,
state: &'a StateFile,
}
pub struct StateWriteAccess<'a> {
_guard: RwLockWriteGuard<'a, ()>,
state: &'a StateFile,
}
impl StateFile {
fn load_state(&self) -> Result<State> {
tracing::debug!("parsing state file");
let state_file = File::open(&self.path)?;
Ok(serde_json::from_reader(state_file)?)
}
fn write_state(&self, state: State) -> Result<()> {
tracing::debug!("serializing state file");
let state_file = File::create(&self.path)?;
Ok(serde_json::to_writer_pretty(state_file, &state)?)
}
pub fn new() -> Result<Self> {
let project_dirs =
directories::ProjectDirs::from("xyz", "ohea", "gamestream-webtransport-proxy")
.ok_or(anyhow::anyhow!("Could not get project dirs"))?;
let data_dir = project_dirs.data_dir();
fs::create_dir_all(data_dir)?;
let state_path = data_dir.join("state.json");
if let Err(e) = File::open(&state_path) {
if e.kind() == std::io::ErrorKind::NotFound {
write_default_state_to_path(&state_path)?;
} else {
return Err(anyhow!(e));
}
}
Ok(StateFile {
lock: tokio::sync::RwLock::new(()),
path: state_path,
})
}
}
impl StateFile {
pub async fn read(&self) -> StateReadAccess {
StateReadAccess {
_guard: self.lock.read().await,
state: self,
}
}
pub async fn write(&self) -> StateWriteAccess {
StateWriteAccess {
_guard: self.lock.write().await,
state: self,
}
}
}
impl<'a> StateReader for StateReadAccess<'a> {
fn servers(&self) -> Result<HashMap<String, GamestreamServer>> {
let state = self.state.load_state()?;
Ok(state.servers)
}
fn unique_id(&self) -> Result<String> {
let state = self.state.load_state()?;
Ok(state.unique_id)
}
}
impl<'a> StateWriter for StateWriteAccess<'a> {
fn add_server(&self, server: GamestreamServer) -> Result<()> {
let mut state = self.state.load_state()?;
if state.servers.contains_key(&server.name) {
return Err(anyhow!(
"cannot add duplicate server with name: {}",
server.name
));
}
state.servers.insert(server.name.clone(), server);
self.state.write_state(state)?;
Ok(())
}
}
fn get_unique_id() -> Result<String> {
let mut bytes = [0u8; 8];
openssl::rand::rand_bytes(&mut bytes)?;
Ok(hex::encode(bytes))
}
fn write_default_state_to_path(path: &PathBuf) -> Result<()> {
let default_state = State {
servers: HashMap::new(),
unique_id: get_unique_id()?,
};
let state_file = File::create(path)?;
Ok(serde_json::to_writer_pretty(state_file, &default_state)?)
}