Add all back-end functionality for interacting with variants on the front-end
This commit is contained in:
parent
437f5beb71
commit
4eaf6fae2b
20 changed files with 590 additions and 27 deletions
16
backend/Cargo.lock
generated
16
backend/Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
121
backend/community_settings_srv/src/api/get_game.rs
Normal file
121
backend/community_settings_srv/src/api/get_game.rs
Normal 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))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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>,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))?
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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"))]
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
142
backend/src/api/web.rs
Normal 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
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,3 +14,5 @@ pub use general::{MinMaxJson, SettingsJson};
|
|||
pub use gpu::GpuJson;
|
||||
|
||||
pub use error::{SerdeError, RonError};
|
||||
|
||||
pub const LATEST_VERSION: u64 = 0;
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue