meta: renamed config to state and wrapped in a server object
This commit is contained in:
@@ -4,7 +4,7 @@ use salvo::prelude::*;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::{common::AppResult, config::ConfigReader};
|
use crate::{common::AppResult, state::StateReader};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AppListRespApp {
|
struct AppListRespApp {
|
||||||
@@ -37,7 +37,7 @@ struct GetAppsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[craft]
|
#[craft]
|
||||||
impl crate::config::ConfigFile {
|
impl crate::server::Server {
|
||||||
#[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(crate::common::AppError {
|
||||||
@@ -45,7 +45,7 @@ impl crate::config::ConfigFile {
|
|||||||
description: "failed to get available apps".to_string(),
|
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() {
|
let unique_id = match reader.unique_id() {
|
||||||
Ok(u) => u,
|
Ok(u) => u,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|||||||
@@ -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)?)
|
|
||||||
}
|
|
||||||
@@ -10,8 +10,9 @@ use moonlight_common_c_sys::{
|
|||||||
mod apps;
|
mod apps;
|
||||||
mod certs;
|
mod certs;
|
||||||
mod common;
|
mod common;
|
||||||
mod config;
|
|
||||||
mod pair;
|
mod pair;
|
||||||
|
mod server;
|
||||||
|
mod state;
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn get_server_info() -> _SERVER_INFORMATION {
|
fn get_server_info() -> _SERVER_INFORMATION {
|
||||||
@@ -113,12 +114,12 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.with_max_level(tracing::Level::DEBUG)
|
.with_max_level(tracing::Level::DEBUG)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let config = config::ConfigFile::new()?;
|
let server = server::Server::new()?;
|
||||||
let config_arc = std::sync::Arc::new(config);
|
let server_arc = std::sync::Arc::new(server);
|
||||||
|
|
||||||
let router = Router::new()
|
let router = Router::new()
|
||||||
.push(Router::with_path("pair").post(config_arc.post_pair()))
|
.push(Router::with_path("pair").post(server_arc.post_pair()))
|
||||||
.push(Router::with_path("apps").get(config_arc.get_apps()));
|
.push(Router::with_path("apps").get(server_arc.get_apps()));
|
||||||
let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
|
let doc = OpenApi::new("test api", "0.0.1").merge_router(&router);
|
||||||
let router = router
|
let router = router
|
||||||
.unshift(doc.into_router("/api-doc/openapi.json"))
|
.unshift(doc.into_router("/api-doc/openapi.json"))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
use crate::common::{AppError, AppResult, get_url};
|
use crate::common::{AppError, AppResult, get_url};
|
||||||
use crate::config::{ConfigReader, ConfigWriter};
|
use crate::state::{StateReader, StateWriter};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, ToSchema)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
struct PostPairParams {
|
struct PostPairParams {
|
||||||
@@ -291,7 +291,7 @@ async fn send_client_pairing_secret(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[craft]
|
#[craft]
|
||||||
impl crate::config::ConfigFile {
|
impl crate::server::Server {
|
||||||
#[craft(endpoint(status_codes(
|
#[craft(endpoint(status_codes(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
StatusCode::BAD_REQUEST,
|
StatusCode::BAD_REQUEST,
|
||||||
@@ -303,13 +303,13 @@ impl crate::config::ConfigFile {
|
|||||||
) -> AppResult<()> {
|
) -> AppResult<()> {
|
||||||
let params = body.into_inner();
|
let params = body.into_inner();
|
||||||
|
|
||||||
let server = crate::config::Server {
|
let server = crate::state::GamestreamServer {
|
||||||
host: params.host,
|
host: params.host,
|
||||||
base_port: params.base_port,
|
base_port: params.base_port,
|
||||||
name: params.name,
|
name: params.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
let reader = self.read().await;
|
let reader = self.state.read().await;
|
||||||
|
|
||||||
let servers = match reader.servers() {
|
let servers = match reader.servers() {
|
||||||
Ok(s) => s,
|
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,
|
Ok(u) => u,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not get unique id: {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(_) => (),
|
Ok(_) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Could not write to config file: {e}");
|
error!("Could not write to state file: {e}");
|
||||||
return Err(AppError {
|
return Err(AppError {
|
||||||
status_code: StatusCode::INTERNAL_SERVER_ERROR,
|
status_code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
description: "Pairing failed".to_string(),
|
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()?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)?)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user