Add all back-end functionality for interacting with variants on the front-end

This commit is contained in:
NGnius (Graham) 2024-01-06 13:26:35 -05:00
parent 437f5beb71
commit 4eaf6fae2b
20 changed files with 590 additions and 27 deletions

16
backend/Cargo.lock generated
View file

@ -289,6 +289,13 @@ dependencies = [
"cc",
]
[[package]]
name = "community_settings_core"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
@ -778,7 +785,9 @@ dependencies = [
[[package]]
name = "libryzenadj"
version = "0.14.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5bccdf07c3234c06c435648a53d8cb369f76d20e03bb8d2f8c24fb2330efc32"
dependencies = [
"errno",
"libryzenadj-sys",
@ -788,7 +797,9 @@ dependencies = [
[[package]]
name = "libryzenadj-sys"
version = "0.14.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1de3621be974e892e12d4a07a6a2e32b6a05950759b062d94f5b54f78fabc3a"
dependencies = [
"bindgen",
"cmake",
@ -1055,6 +1066,7 @@ name = "powertools"
version = "1.5.0-ng1"
dependencies = [
"async-trait",
"community_settings_core",
"libryzenadj",
"limits_core",
"log",

View file

@ -28,9 +28,11 @@ simplelog = "0.12"
# limits & driver functionality
limits_core = { version = "3", path = "./limits_core" }
community_settings_core = { version = "0.1", path = "./community_settings_core" }
regex = "1"
smokepatio = { version = "*", path = "../../smokepatio" }
libryzenadj = { version = "0.14", path = "../../libryzenadj-rs-14" }
#libryzenadj = { version = "0.14", path = "../../libryzenadj-rs-14" }
libryzenadj = { version = "0.13" }
# ureq's tls feature does not like musl targets
ureq = { version = "2", features = ["json", "gzip", "brotli", "charset"], default-features = false, optional = true }

View file

@ -5,7 +5,8 @@ pub struct Metadata {
pub name: String,
pub steam_app_id: u32,
pub steam_user_id: u64,
pub stream_username: String,
pub steam_username: String,
pub tags: Vec<String>,
/// Should always be a valid u128, but some parsers do not support that
pub id: String,
pub config: super::Config,

View file

@ -0,0 +1,121 @@
use actix_web::{get, web, Responder, http::header};
use crate::cli::Cli;
use crate::file_util;
const MAX_RESULTS: usize = 50;
fn special_settings() -> Vec<community_settings_core::v1::Metadata> {
vec![
community_settings_core::v1::Metadata {
name: "Zeroth the Least".to_owned(),
steam_app_id: 0,
steam_user_id: 76561198116690523,
steam_username: "NGnius".to_owned(),
tags: vec!["0".to_owned(), "gr8".to_owned()],
id: 0.to_string(),
config: community_settings_core::v1::Config {
cpus: vec![
community_settings_core::v1::Cpu {
online: true,
clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }),
governor: "Michaëlle Jean".to_owned(),
},
community_settings_core::v1::Cpu {
online: false,
clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }),
governor: "Adrienne Clarkson".to_owned(),
},
community_settings_core::v1::Cpu {
online: true,
clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }),
governor: "Michael Collins".to_owned(),
}
],
gpu: community_settings_core::v1::Gpu {
fast_ppt: Some(1),
slow_ppt: Some(1),
tdp: None,
tdp_boost: None,
clock_limits: Some(community_settings_core::v1::MinMax { max: Some(1), min: Some(0) }),
slow_memory: false,
},
battery: community_settings_core::v1::Battery {
charge_rate: Some(42),
charge_mode: Some("nuclear fusion".to_owned()),
events: vec![
community_settings_core::v1::BatteryEvent {
trigger: "anything but one on a gun".to_owned(),
charge_rate: Some(42),
charge_mode: Some("neutral".to_owned()),
}
],
}
}
}
]
}
fn get_some_settings_by_app_id(steam_app_id: u32, cli: &'static Cli) -> std::io::Result<Vec<community_settings_core::v1::Metadata>> {
let app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, steam_app_id);
let mut files: Vec<_> = app_id_folder.read_dir()?
.filter_map(|res| res.ok())
.filter(|f| f.path().extension().map(|ext| ext == file_util::RON_EXTENSION).unwrap_or(false))
.filter_map(|f| f.metadata().ok().map(|meta| (f, meta)))
.filter_map(|(f, meta)| meta.created().ok().map(|time| (f, meta, time)))
.collect();
files.sort_by(|(_, _, a_created), (_, _, b_created)| a_created.cmp(b_created));
let mut results = Vec::with_capacity(MAX_RESULTS);
for (_, (f, _, _)) in files.into_iter().enumerate().take_while(|(i, _)| *i < MAX_RESULTS) {
let reader = std::io::BufReader::new(std::fs::File::open(f.path())?);
let setting = match ron::de::from_reader(reader) {
Ok(x) => x,
Err(e) => {
let e_msg = format!("{}", e);
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
}
};
results.push(setting);
}
Ok(results)
}
#[get("/api/setting/by_app_id/{id}")]
pub async fn get_setting_by_app_id_handler(
id: web::Path<u32>,
accept: web::Header<header::Accept>,
cli: web::Data<&'static Cli>,
) -> std::io::Result<impl Responder> {
let id: u32 = *id;
println!("Accept: {}", accept.to_string());
let preferred = accept.preference();
if super::is_mime_type_ron_capable(&preferred) {
// Send RON
let ron = if id != 0 {
get_some_settings_by_app_id(id, &*cli)?
} else {
special_settings()
};
// TODO don't dump to string
let result_body = ron::ser::to_string(&ron).unwrap();
Ok(actix_web::HttpResponse::Ok()
//.insert_header(header::ContentType("application/ron".parse().unwrap()))
.insert_header(header::ContentType(mime::STAR_STAR))
.body(actix_web::body::BoxBody::new(result_body))
)
} else {
// Send JSON (fallback)
let json = if id != 0 {
get_some_settings_by_app_id(id, &*cli)?
} else {
special_settings()
};
// TODO don't dump to string
let result_body = serde_json::to_string(&json).unwrap();
Ok(actix_web::HttpResponse::Ok()
.insert_header(header::ContentType::json())
.body(actix_web::body::BoxBody::new(result_body))
)
}
}

View file

@ -8,7 +8,8 @@ fn special_settings() -> community_settings_core::v1::Metadata {
name: "Zeroth the Least".to_owned(),
steam_app_id: 1675200,
steam_user_id: 76561198116690523,
stream_username: "NGnius".to_owned(),
steam_username: "NGnius".to_owned(),
tags: vec!["0".to_owned(), "gr8".to_owned()],
id: 0.to_string(),
config: community_settings_core::v1::Config {
cpus: vec![
@ -51,7 +52,7 @@ fn special_settings() -> community_settings_core::v1::Metadata {
}
}
#[get("/api/setting/{id}")]
#[get("/api/setting/by_id/{id}")]
pub async fn get_setting_handler(
id: web::Path<String>,
accept: web::Header<header::Accept>,

View file

@ -1,6 +1,8 @@
mod get_game;
mod get_setting;
mod save_setting;
pub use get_game::get_setting_by_app_id_handler as get_setting_by_steam_app_id;
pub use get_setting::get_setting_handler as get_setting_by_id;
pub use save_setting::save_setting_handler as save_setting_with_new_id;

View file

@ -18,7 +18,7 @@ pub async fn save_setting_handler(
Err(_e) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "too many bytes in payload")),
};
let next_id = file_util::next_setting_id(&cli.folder);
let parsed_data = if super::is_mime_type_ron_capable(&content_type) {
let parsed_data: community_settings_core::v1::Metadata = if super::is_mime_type_ron_capable(&content_type) {
// Parse as RON
match ron::de::from_reader(bytes.as_ref()) {
Ok(x) => x,
@ -44,15 +44,15 @@ pub async fn save_setting_handler(
};
// TODO validate user and app id
// Reject blocked users and apps
let path = file_util::setting_path_by_id(&cli.folder, next_id, file_util::RON_EXTENSION);
let writer = std::io::BufWriter::new(std::fs::File::create(path)?);
let path_ron = file_util::setting_path_by_id(&cli.folder, next_id, file_util::RON_EXTENSION);
let writer = std::io::BufWriter::new(std::fs::File::create(&path_ron)?);
if let Err(e) = ron::ser::to_writer(writer, &parsed_data) {
let e_msg = format!("{}", e);
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
}
let path = file_util::setting_path_by_id(&cli.folder, next_id, file_util::JSON_EXTENSION);
let writer = std::io::BufWriter::new(std::fs::File::create(path)?);
let path_json = file_util::setting_path_by_id(&cli.folder, next_id, file_util::JSON_EXTENSION);
let writer = std::io::BufWriter::new(std::fs::File::create(&path_json)?);
if let Err(e) = serde_json::to_writer(writer, &parsed_data) {
let e_msg = format!("{}", e);
if let Some(io_e) = e.io_error_kind() {
@ -62,7 +62,59 @@ pub async fn save_setting_handler(
}
}
// TODO create symlinks for other ways of looking up these settings files
// create symlinks for other ways of looking up these settings files
let filename_ron = file_util::filename(next_id, file_util::RON_EXTENSION);
let filename_json = file_util::filename(next_id, file_util::JSON_EXTENSION);
// create symlinks to app id folder
let app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, parsed_data.steam_app_id);
if !app_id_folder.exists() {
std::fs::create_dir(&app_id_folder)?;
}
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
{
std::os::windows::fs::symlink_file(&path_ron, app_id_folder.join(&filename_ron))?;
std::os::windows::fs::symlink_file(&path_json, app_id_folder.join(&filename_json))?;
}
#[cfg(target_family = "unix")]
{
std::os::unix::fs::symlink(&path_ron, app_id_folder.join(&filename_ron))?;
std::os::unix::fs::symlink(&path_json, app_id_folder.join(&filename_json))?;
}
// create symlinks for user id folder
let user_id_folder = file_util::setting_folder_by_user_id(&cli.folder, parsed_data.steam_user_id);
if !user_id_folder.exists() {
std::fs::create_dir(&user_id_folder)?;
}
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
{
std::os::windows::fs::symlink_file(&path_ron, user_id_folder.join(&filename_ron))?;
std::os::windows::fs::symlink_file(&path_json, user_id_folder.join(&filename_json))?;
}
#[cfg(target_family = "unix")]
{
std::os::unix::fs::symlink(&path_ron, user_id_folder.join(&filename_ron))?;
std::os::unix::fs::symlink(&path_json, user_id_folder.join(&filename_json))?;
}
// create symlinks for each tag
for tag in parsed_data.tags.iter() {
let tag_folder = file_util::setting_folder_by_tag(&cli.folder, tag);
if !tag_folder.exists() {
std::fs::create_dir(&tag_folder)?;
}
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
{
std::os::windows::fs::symlink_file(&path_ron, tag_folder.join(&filename_ron))?;
std::os::windows::fs::symlink_file(&path_json, tag_folder.join(&filename_json))?;
}
#[cfg(target_family = "unix")]
{
std::os::unix::fs::symlink(&path_ron, tag_folder.join(&filename_ron))?;
std::os::unix::fs::symlink(&path_json, tag_folder.join(&filename_json))?;
}
}
Ok(actix_web::HttpResponse::NoContent())
}

View file

@ -6,14 +6,66 @@ pub const JSON_EXTENSION: &'static str = "json";
const SETTING_FOLDER: &'static str = "settings";
const ID_FOLDER: &'static str = "by_id";
const APP_ID_FOLDER: &'static str = "by_app_id";
const USER_ID_FOLDER: &'static str = "by_user_id";
const TAG_FOLDER: &'static str = "by_tag";
static LAST_SETTING_ID: Mutex<u128> = Mutex::new(0);
pub fn build_folder_layout(root: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(
root.as_ref()
.join(SETTING_FOLDER)
.join(ID_FOLDER)
)?;
std::fs::create_dir_all(
root.as_ref()
.join(SETTING_FOLDER)
.join(APP_ID_FOLDER)
)?;
std::fs::create_dir_all(
root.as_ref()
.join(SETTING_FOLDER)
.join(USER_ID_FOLDER)
)?;
std::fs::create_dir_all(
root.as_ref()
.join(SETTING_FOLDER)
.join(TAG_FOLDER)
)?;
Ok(())
}
pub fn filename(id: u128, ext: &str) -> String {
format!("{}.{}", id, ext)
}
pub fn setting_path_by_id(root: impl AsRef<Path>, id: u128, ext: &str) -> PathBuf {
root.as_ref()
.join(SETTING_FOLDER)
.join(ID_FOLDER)
.join(format!("{}.{}", id, ext))
.join(filename(id, ext))
}
pub fn setting_folder_by_app_id(root: impl AsRef<Path>, steam_app_id: u32) -> PathBuf {
root.as_ref()
.join(SETTING_FOLDER)
.join(APP_ID_FOLDER)
.join(steam_app_id.to_string())
}
pub fn setting_folder_by_user_id(root: impl AsRef<Path>, steam_user_id: u64) -> PathBuf {
root.as_ref()
.join(SETTING_FOLDER)
.join(USER_ID_FOLDER)
.join(steam_user_id.to_string())
}
pub fn setting_folder_by_tag(root: impl AsRef<Path>, tag: &str) -> PathBuf {
root.as_ref()
.join(SETTING_FOLDER)
.join(TAG_FOLDER)
.join(tag)
}
pub fn next_setting_id(root: impl AsRef<Path>) -> u128 {

View file

@ -25,6 +25,10 @@ async fn main() -> std::io::Result<()> {
.unwrap();
log::debug!("Logging to: {}", args.log.display());
// setup
log::debug!("Building folder layout (if not exists) at: {}", &args.folder.display());
file_util::build_folder_layout(&args.folder)?;
let leaked_args: &'static cli::Cli = Box::leak::<'static>(Box::new(args));
HttpServer::new(move || {
App::new()
@ -32,6 +36,7 @@ async fn main() -> std::io::Result<()> {
//.app_data(web::Data::new(IndexPage::load("dist/index.html").unwrap()))
//.app_data(basic::Config::default().realm("Restricted area"))
.service(api::get_setting_by_id)
.service(api::get_setting_by_steam_app_id)
.service(api::save_setting_with_new_id)
})
.bind(("0.0.0.0", leaked_args.port))?

View file

@ -61,3 +61,9 @@ pub struct GpuLimits {
pub clock_step: u64,
pub memory_control_capable: bool,
}
#[derive(Serialize, Deserialize)]
pub struct VariantInfo {
pub id: String,
pub name: String,
}

View file

@ -391,3 +391,61 @@ fn wait_for_response<T>(sender: &Sender<ApiMessage>, rx: mpsc::Receiver<T>, api_
sender.send(api_msg).expect(&format!("{} send failed", op));
rx.recv().expect(&format!("{} callback recv failed", op))
}
/// Generate get variants
pub fn get_all_variants(sender: Sender<ApiMessage>) -> impl AsyncCallable {
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
let getter = move || {
let sender2 = sender.clone();
move || {
let (tx, rx) = mpsc::channel();
let callback =
move |variants: Vec<super::VariantInfo>| tx.send(variants).expect("get_all_variants callback send failed");
sender2
.lock()
.unwrap()
.send(ApiMessage::General(GeneralMessage::GetAllVariants(
Box::new(callback),
)))
.expect("get_all_variants send failed");
rx.recv().expect("get_all_variants callback recv failed")
}
};
super::async_utils::AsyncIshGetter {
set_get: getter,
trans_getter: |result| {
let mut output = Vec::with_capacity(result.len());
for status in result.iter() {
output.push(Primitive::Json(serde_json::to_string(status).expect("Failed to serialize variant info to JSON")));
}
output
},
}
}
/// Generate get current variant
pub fn get_current_variant(sender: Sender<ApiMessage>) -> impl AsyncCallable {
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
let getter = move || {
let sender2 = sender.clone();
move || {
let (tx, rx) = mpsc::channel();
let callback =
move |variant: super::VariantInfo| tx.send(variant).expect("get_all_variants callback send failed");
sender2
.lock()
.unwrap()
.send(ApiMessage::General(GeneralMessage::GetCurrentVariant(
Box::new(callback),
)))
.expect("get_current_variant send failed");
rx.recv().expect("get_current_variant callback recv failed")
}
};
super::async_utils::AsyncIshGetter {
set_get: getter,
trans_getter: |result| {
vec![Primitive::Json(serde_json::to_string(&result).expect("Failed to serialize variant info to JSON"))]
},
}
}

View file

@ -227,6 +227,9 @@ pub enum GeneralMessage {
GetPersistent(Callback<bool>),
GetCurrentProfileName(Callback<String>),
GetPath(Callback<std::path::PathBuf>),
GetCurrentVariant(Callback<super::VariantInfo>),
GetAllVariants(Callback<Vec<super::VariantInfo>>),
AddVariant(crate::persist::SettingsJson, Callback<Vec<super::VariantInfo>>),
ApplyNow,
}
@ -238,6 +241,15 @@ impl GeneralMessage {
Self::GetPersistent(cb) => cb(*settings.persistent()),
Self::GetCurrentProfileName(cb) => cb(settings.get_name().to_owned()),
Self::GetPath(cb) => cb(settings.get_path().to_owned()),
Self::GetCurrentVariant(cb) => cb(settings.get_variant_info()),
Self::GetAllVariants(cb) => cb(settings.get_variants()),
Self::AddVariant(variant, cb) => match settings.add_variant(variant) {
Ok(variants) => cb(variants),
Err(e) => {
print_errors("GeneralMessage::AddVariant => TGeneral::add_variant", vec![e]);
cb(Vec::with_capacity(0))
},
},
Self::ApplyNow => {}
}
dirty
@ -389,7 +401,7 @@ impl ApiMessageHandler {
true
}
ApiMessage::LoadSystemSettings => {
settings.load_system_default(settings.general.get_name().to_owned(), settings.general.get_variant_id(), settings.general.get_variant_name().to_owned());
settings.load_system_default(settings.general.get_name().to_owned(), settings.general.get_variant_id(), settings.general.get_variant_info().name);
true
}
ApiMessage::GetLimits(cb) => {

View file

@ -7,6 +7,7 @@ pub mod gpu;
pub mod handler;
pub mod message;
mod utility;
pub mod web;
pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>;

142
backend/src/api/web.rs Normal file
View file

@ -0,0 +1,142 @@
use std::sync::mpsc::{self, Sender};
use std::sync::{Arc, Mutex};
use usdpl_back::core::serdes::Primitive;
use usdpl_back::AsyncCallable;
use super::handler::{ApiMessage, GeneralMessage};
const BASE_URL: &'static str = "http://powertools.ngni.us";
/// Get search results web method
pub fn search_by_app_id() -> impl AsyncCallable {
let getter = move || {
move |steam_app_id: u32| {
let req_url = format!("{}/api/setting/by_app_id/{}", BASE_URL, steam_app_id);
match ureq::get(&req_url).call() {
Ok(response) => {
let json_res: std::io::Result<Vec<community_settings_core::v1::Metadata>> = response.into_json();
match json_res {
Ok(search_results) => {
// search results may be quite large, so let's do the JSON string conversion in the background (blocking) thread
match serde_json::to_string(&search_results) {
Err(e) => log::error!("Cannot convert search results from `{}` to JSON: {}", req_url, e),
Ok(s) => return s,
}
}
Err(e) => {
log::error!("Cannot parse response from `{}`: {}", req_url, e)
}
}
}
Err(e) => log::warn!("Cannot get search results from `{}`: {}", req_url, e),
}
"[]".to_owned()
}
};
super::async_utils::AsyncIsh {
trans_setter: |params| {
if let Some(Primitive::F64(app_id)) = params.get(0) {
Ok(*app_id as u32)
} else {
Err("search_by_app_id missing/invalid parameter 0".to_owned())
}
},
set_get: getter,
trans_getter: |result| vec![Primitive::Json(result)],
}
}
fn web_config_to_settings_json(meta: community_settings_core::v1::Metadata) -> crate::persist::SettingsJson {
crate::persist::SettingsJson {
version: crate::persist::LATEST_VERSION,
name: meta.name,
variant: u64::MAX, // TODO maybe change this to use the 64 low bits of id (u64::MAX will cause it to generate a new id when added to file variant map
persistent: true,
cpus: meta.config.cpus.into_iter().map(|cpu| crate::persist::CpuJson {
online: cpu.online,
clock_limits: cpu.clock_limits.map(|lim| crate::persist::MinMaxJson {
min: lim.min,
max: lim.max,
}),
governor: cpu.governor,
root: None,
}).collect(),
gpu: crate::persist::GpuJson {
fast_ppt: meta.config.gpu.fast_ppt,
slow_ppt: meta.config.gpu.slow_ppt,
tdp: meta.config.gpu.tdp,
tdp_boost: meta.config.gpu.tdp_boost,
clock_limits: meta.config.gpu.clock_limits.map(|lim| crate::persist::MinMaxJson {
min: lim.min,
max: lim.max,
}),
slow_memory: meta.config.gpu.slow_memory,
root: None,
},
battery: crate::persist::BatteryJson {
charge_rate: meta.config.battery.charge_rate,
charge_mode: meta.config.battery.charge_mode,
events: meta.config.battery.events.into_iter().map(|be| crate::persist::BatteryEventJson {
charge_rate: be.charge_rate,
charge_mode: be.charge_mode,
trigger: be.trigger,
}).collect(),
root: None,
},
provider: Some(crate::persist::DriverJson::AutoDetect),
}
}
/// Download config web method
pub fn download_new_config(sender: Sender<ApiMessage>) -> impl AsyncCallable {
let sender = Arc::new(Mutex::new(sender)); // Sender is not Sync; this is required for safety
let getter = move || {
let sender2 = sender.clone();
move |id: u128| {
let req_url = format!("{}/api/setting/by_id/{}", BASE_URL, id);
match ureq::get(&req_url).call() {
Ok(response) => {
let json_res: std::io::Result<community_settings_core::v1::Metadata> = response.into_json();
match json_res {
Ok(meta) => {
let (tx, rx) = mpsc::channel();
let callback =
move |values: Vec<super::VariantInfo>| tx.send(values).expect("download_new_config callback send failed");
sender2
.lock()
.unwrap()
.send(ApiMessage::General(GeneralMessage::AddVariant(web_config_to_settings_json(meta), Box::new(callback))))
.expect("download_new_config send failed");
return rx.recv().expect("download_new_config callback recv failed");
}
Err(e) => {
log::error!("Cannot parse response from `{}`: {}", req_url, e)
}
}
}
Err(e) => log::warn!("Cannot get setting result from `{}`: {}", req_url, e),
}
vec![]
}
};
super::async_utils::AsyncIsh {
trans_setter: |params| {
if let Some(Primitive::String(id)) = params.get(0) {
match id.parse::<u128>() {
Ok(id) => Ok(id),
Err(e) => Err(format!("download_new_config non-u128 string parameter 0: {} (got `{}`)", e, id))
}
} else {
Err("download_new_config missing/invalid parameter 0".to_owned())
}
},
set_get: getter,
trans_getter: |result| {
let mut output = Vec::with_capacity(result.len());
for status in result.iter() {
output.push(Primitive::Json(serde_json::to_string(status).expect("Failed to serialize variant info to JSON")));
}
output
},
}
}

View file

@ -299,8 +299,24 @@ fn main() -> Result<(), ()> {
"GENERAL_get_periodicals",
api::general::get_periodicals(api_sender.clone())
)
.register_async(
"GENERAL_get_all_variants",
api::general::get_all_variants(api_sender.clone())
)
.register_async(
"GENERAL_get_current_variant",
api::general::get_current_variant(api_sender.clone())
)
.register_async("MESSAGE_get", message_getter)
.register_async("MESSAGE_dismiss", message_dismisser);
.register_async("MESSAGE_dismiss", message_dismisser)
.register_async(
"WEB_search_by_app",
api::web::search_by_app_id()
)
.register_async(
"WEB_download_new",
api::web::download_new_config(api_sender.clone())
);
if let Err(e) = loaded_settings.on_set() {
e.iter()

View file

@ -37,14 +37,24 @@ impl FileJson {
ron::de::from_reader(&mut file).map_err(|e| SerdeError::Serde(e.into()))
}
pub fn update_variant_or_create<P: AsRef<std::path::Path>>(path: P, setting: SettingsJson, given_name: String) -> Result<(), SerdeError> {
fn next_available_id(&self) -> u64 {
self.variants.keys()
.max()
.map(|k| k+1)
.unwrap_or(0)
}
pub fn update_variant_or_create<P: AsRef<std::path::Path>>(path: P, mut setting: SettingsJson, given_name: String) -> Result<Self, SerdeError> {
if !setting.persistent {
return Ok(())
return Self::open(path)
}
let path = path.as_ref();
let file = if path.exists() {
let mut file = Self::open(path)?;
if setting.variant == u64::MAX {
setting.variant = file.next_available_id();
}
file.variants.insert(setting.variant, setting);
file
} else {
@ -57,6 +67,7 @@ impl FileJson {
}
};
file.save(path)
file.save(path)?;
Ok(file)
}
}

View file

@ -14,3 +14,5 @@ pub use general::{MinMaxJson, SettingsJson};
pub use gpu::GpuJson;
pub use error::{SerdeError, RonError};
pub const LATEST_VERSION: u64 = 0;

View file

@ -89,14 +89,45 @@ impl TGeneral for General {
self.variant_id = id;
}
fn get_variant_name(&self) -> &'_ str {
&self.variant_name
}
fn variant_name(&mut self, name: String) {
self.variant_name = name;
}
fn get_variants(&self) -> Vec<crate::api::VariantInfo> {
if let Ok(file) = crate::persist::FileJson::open(self.get_path()) {
file.variants.into_iter()
.map(|(id, conf)| crate::api::VariantInfo {
id: id.to_string(),
name: conf.name,
})
.collect()
} else {
vec![self.get_variant_info()]
}
}
fn add_variant(&self, variant: crate::persist::SettingsJson) -> Result<Vec<crate::api::VariantInfo>, SettingError> {
let variant_name = variant.name.clone();
crate::persist::FileJson::update_variant_or_create(self.get_path(), variant, variant_name)
.map_err(|e| SettingError {
msg: format!("failed to add variant: {}", e),
setting: SettingVariant::General,
})
.map(|file| file.variants.into_iter()
.map(|(id, conf)| crate::api::VariantInfo {
id: id.to_string(),
name: conf.name,
})
.collect())
}
fn get_variant_info(&self) -> crate::api::VariantInfo {
crate::api::VariantInfo {
id: self.variant_id.to_string(),
name: self.variant_name.clone(),
}
}
fn provider(&self) -> crate::persist::DriverJson {
self.driver.clone()
}
@ -265,9 +296,10 @@ impl Settings {
}*/
pub fn json(&self) -> SettingsJson {
let variant_info = self.general.get_variant_info();
SettingsJson {
version: LATEST_VERSION,
name: self.general.get_variant_name().to_owned(),
name: variant_info.name,
variant: self.general.get_variant_id(),
persistent: self.general.get_persistent(),
cpus: self.cpus.json(),

View file

@ -109,14 +109,18 @@ pub trait TGeneral: OnSet + OnResume + OnPowerEvent + Debug + Send {
fn name(&mut self, name: String);
fn get_variant_id(&self) -> u64;
fn variant_id(&mut self, id: u64);
fn get_variant_name(&self) -> &'_ str;
fn variant_name(&mut self, name: String);
fn get_variant_id(&self) -> u64;
fn get_variants(&self) -> Vec<crate::api::VariantInfo>;
fn get_variant_info(&self) -> crate::api::VariantInfo;
fn add_variant(&self, variant: crate::persist::SettingsJson) -> Result<Vec<crate::api::VariantInfo>, SettingError>;
fn provider(&self) -> crate::persist::DriverJson;
}

View file

@ -349,3 +349,34 @@ export async function getPeriodicals(): Promise<Periodicals> {
settings_path: result[4],
};
}
export type StoreMetadata = {
name: string,
steam_app_id: number,
steam_user_id: number,
steam_username: string,
tags: string[],
id: string,
//config: any,
}
export async function searchStoreByAppId(id: number): Promise<StoreMetadata[]> {
return (await call_backend("WEB_search_by_app", [id]))[0];
}
export type VariantInfo = {
id: string,
name: string,
}
export async function storeDownloadById(id: string): Promise<VariantInfo[]> {
return (await call_backend("WEB_download_new", [id]));
}
export async function getAllSettingVariants(): Promise<VariantInfo[]> {
return (await call_backend("GENERAL_get_all_variants", []));
}
export async function getCurrentSettingVariant(): Promise<VariantInfo> {
return (await call_backend("GENERAL_get_current_variant", []))[0];
}