Merge branch 'dev'

This commit is contained in:
NGnius (Graham) 2024-03-31 16:31:42 -04:00
commit f9e384d50c
119 changed files with 12276 additions and 2303 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

854
backend/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,22 @@
[package]
name = "powertools"
version = "1.4.0"
version = "2.0.0"
edition = "2021"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
description = "Backend (superuser) functionality for PowerTools"
license = "GPL-3.0-only"
repository = "https://github.com/NGnius/PowerTools"
repository = "https://git.ngni.us/NG-SD-Plugins/PowerTools"
keywords = ["utility", "power-management", "root", "decky"]
readme = "../README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
usdpl-back = { version = "0.10.1", features = ["blocking"] }#, path = "../../usdpl-rs/usdpl-back"}
usdpl-back = { version = "0.10.1", features = ["blocking", "decky"] }#, path = "../../usdpl-rs/usdpl-back"}
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sysfuss = { version = "0.2", features = ["derive"] }#, path = "../../sysfs-nav" }
ron = "0.8"
sysfuss = { version = "0.2", features = ["derive"] }#,path = "../../sysfs-nav"}
# async
tokio = { version = "*", features = ["time"] }
@ -26,11 +27,25 @@ log = "0.4"
simplelog = "0.12"
# limits & driver functionality
limits_core = { version = "2", path = "./limits_core" }
limits_core = { version = "3", path = "./limits_core" }
regex = "1"
libryzenadj = { version = "0.12" }
# steam deck libs
smokepatio = { version = "0.1", features = [ "std" ], path = "../../smokepatio" }
libc = "0.2"
# online settings
community_settings_core = { version = "0.1", path = "./community_settings_core" }
chrono = { version = "0.4", features = [ "serde" ] }
# hardware enablement
#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 }
ureq = { version = "2", features = ["json", "gzip", "brotli", "charset", "tls"], default-features = false, optional = true }
clap = { version = "4.4", features = [ "derive" ] }
[features]
default = ["online", "decky"]
@ -38,6 +53,7 @@ decky = ["usdpl-back/decky"]
crankshaft = ["usdpl-back/crankshaft"]
encrypt = ["usdpl-back/encrypt"]
online = ["ureq"]
experimental = []
dev_stuff = []
[profile.release]
@ -45,13 +61,13 @@ debug = false
strip = true
lto = true
codegen-units = 1
opt-level = 3
[profile.docker]
inherits = "release"
debug = false
strip = true
lto = "thin"
codegen-units = 16
opt-level = 2
codegen-units = 8
debug-assertions = false
overflow-checks = false

View File

@ -3,10 +3,11 @@
#cargo build --release --target x86_64-unknown-linux-musl
#cargo build --target x86_64-unknown-linux-musl
#cross build
cargo build
cargo build --release
#cargo build
mkdir -p ../bin
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/release/powertools ../bin/backend
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/debug/powertools ../bin/backend
cp --preserve=mode ./target/release/powertools ../bin/backend
#cp --preserve=mode ./target/debug/powertools ../bin/backend
cp --preserve=mode ./target/debug/powertools ../bin/backend

View File

@ -0,0 +1,65 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "community_settings_core"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]]
name = "proc-macro2"
version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "serde"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "2.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

View File

@ -0,0 +1,9 @@
[package]
name = "community_settings_core"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }

View File

@ -0,0 +1 @@
pub mod v1;

View File

@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone)]
pub struct Metadata {
pub name: String,
pub steam_app_id: u32,
pub steam_user_id: u64,
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,
}
impl Metadata {
pub fn set_id(&mut self, id: u128) {
self.id = id.to_string()
}
pub fn get_id(&self) -> u128 {
self.id.parse().expect("metadata id must be u128")
}
}

View File

@ -0,0 +1,5 @@
mod metadata;
mod setting;
pub use metadata::Metadata;
pub use setting::*;

View File

@ -0,0 +1,47 @@
use serde::{Deserialize, Serialize};
/// Base setting file containing all information for all components
#[derive(Serialize, Deserialize, Clone)]
pub struct Config {
pub cpus: Vec<Cpu>,
pub gpu: Gpu,
pub battery: Battery,
}
#[derive(Serialize, Deserialize, Clone, Copy)]
pub struct MinMax<T> {
pub max: Option<T>,
pub min: Option<T>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Battery {
pub charge_rate: Option<u64>,
pub charge_mode: Option<String>,
#[serde(default)]
pub events: Vec<BatteryEvent>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct BatteryEvent {
pub trigger: String,
pub charge_rate: Option<u64>,
pub charge_mode: Option<String>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Cpu {
pub online: bool,
pub clock_limits: Option<MinMax<u64>>,
pub governor: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Gpu {
pub fast_ppt: Option<u64>,
pub slow_ppt: Option<u64>,
pub tdp: Option<u64>,
pub tdp_boost: Option<u64>,
pub clock_limits: Option<MinMax<u64>>,
pub memory_clock: Option<u64>,
}

1538
backend/community_settings_srv/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
[package]
name = "community_settings_srv"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
community_settings_core = { version = "0.1", path = "../community_settings_core" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
ron = "0.8"
clap = { version = "4", features = ["derive", "std", "color"], default-features = false }
tokio = { version = "1", features = ["full"] }
actix-web = { version = "4.4" }
mime = { version = "0.3.17" }
# logging
log = "0.4"
simplelog = "0.12"

View File

@ -0,0 +1,7 @@
# community_settings_srv
Back-end for browsing community-contributed settings files for games.
### Technical
This does not use a database because I'm trying to speedrun the destruction of any semblance of performance for my filesystem. Everything is stored as a file, with symlinks to make it possible to find files multiple ways.

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) }),
memory_clock: Some(404),
},
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

@ -0,0 +1,121 @@
use actix_web::{get, web, Responder, http::header};
use crate::cli::Cli;
use crate::file_util;
fn special_settings() -> community_settings_core::v1::Metadata {
community_settings_core::v1::Metadata {
name: "Zeroth the Least".to_owned(),
steam_app_id: 1675200,
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) }),
memory_clock: Some(404),
},
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()),
}
],
}
}
}
}
#[get("/api/setting/by_id/{id}")]
pub async fn get_setting_handler(
id: web::Path<String>,
accept: web::Header<header::Accept>,
cli: web::Data<&'static Cli>,
) -> std::io::Result<impl Responder> {
println!("Accept: {}", accept.to_string());
let id: u128 = match id.parse() {
Ok(x) => x,
Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("invalid setting id `{}` (should be u128): {}", id, e))),
};
let preferred = accept.preference();
if super::is_mime_type_ron_capable(&preferred) {
// Send RON
let ron = if id != 0 {
let path = file_util::setting_path_by_id(&cli.folder, id, file_util::RON_EXTENSION);
if !path.exists() {
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("setting id {} does not exist", id)));
}
// TODO? cache this instead of always loading it from file
let reader = std::io::BufReader::new(std::fs::File::open(path)?);
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));
}
}
} 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 {
let path = file_util::setting_path_by_id(&cli.folder, id, file_util::JSON_EXTENSION);
// TODO? cache this instead of always loading it from file
let reader = std::io::BufReader::new(std::fs::File::open(path)?);
match serde_json::from_reader(reader) {
Ok(x) => x,
Err(e) => {
let e_msg = format!("{}", e);
if let Some(io_e) = e.io_error_kind() {
return Err(std::io::Error::new(io_e, e_msg));
} else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
}
}
}
} 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

@ -0,0 +1,12 @@
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;
pub(self) fn is_mime_type_ron_capable(mimetype: &mime::Mime) -> bool {
(mimetype.type_() == "application" || mimetype.type_() == mime::STAR)
&& (mimetype.subtype() == "ron" || mimetype.subtype() == "cc.replicated.ron" || mimetype.subtype() == "w-ron")
}

View File

@ -0,0 +1,120 @@
use actix_web::{post, web, Responder, http::header};
use crate::cli::Cli;
use crate::file_util;
const PAYLOAD_LIMIT: usize = 10_000_000; // 10 Megabyte
#[post("/api/setting")]
pub async fn save_setting_handler(
data: web::Payload,
content_type: web::Header<header::ContentType>,
cli: web::Data<&'static Cli>,
) -> std::io::Result<impl Responder> {
//println!("Content-Type: {}", content_type.to_string());
let bytes = match data.to_bytes_limited(PAYLOAD_LIMIT).await {
Ok(Ok(x)) => x,
Ok(Err(e)) => return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, format!("wut: {}", e))),
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: 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,
Err(e) => {
let e_msg = format!("{}", e);
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
}
}
} else {
// Parse JSON (fallback)
match serde_json::from_reader(bytes.as_ref()) {
Ok(x) => x,
Err(e) => {
let e_msg = format!("{}", e);
if let Some(io_e) = e.io_error_kind() {
return Err(std::io::Error::new(io_e, e_msg));
} else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
}
}
}
};
// TODO validate user and app id
// Reject blocked users and apps
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_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() {
return Err(std::io::Error::new(io_e, e_msg));
} else {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
}
}
// 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

@ -0,0 +1,23 @@
use clap::Parser;
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Cli {
/// Root folder to store contributed setting files
#[arg(short, long, default_value = "./community_settings")]
pub folder: std::path::PathBuf,
/// Server port
#[arg(short, long, default_value_t = 8080)]
pub port: u16,
/// Log file location
#[arg(short, long, default_value = "/tmp/powertools_community_settings_srv.log")]
pub log: std::path::PathBuf,
}
impl Cli {
pub fn get() -> Self {
Self::parse()
}
}

View File

@ -0,0 +1,86 @@
use std::path::{Path, PathBuf};
use std::sync::Mutex;
pub const RON_EXTENSION: &'static str = "ron";
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(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 {
let mut lock = LAST_SETTING_ID.lock().unwrap();
let mut last_id = *lock;
if last_id == 0 {
// needs init
let mut path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION);
while path.exists() {
last_id += 1;
path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION);
}
*lock = last_id;
println!("setting id initialized to {}", last_id);
}
*lock += 1;
*lock
}

View File

@ -0,0 +1,45 @@
mod api;
mod cli;
mod file_util;
use actix_web::{web, App, HttpServer};
#[tokio::main]
async fn main() -> std::io::Result<()> {
let args = cli::Cli::get();
println!("cli: {:?}", args);
simplelog::WriteLogger::init(
#[cfg(debug_assertions)]
{
log::LevelFilter::Debug
},
#[cfg(not(debug_assertions))]
{
log::LevelFilter::Info
},
Default::default(),
std::fs::File::create(&args.log).expect("Failed to create log file"),
//std::fs::File::create("/home/deck/powertools-rs.log").unwrap(),
)
.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()
.app_data(web::Data::new(leaked_args))
//.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))?
.run()
.await
}

View File

@ -2,18 +2,11 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "itoa"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b02a5381cc465bd3041d84623d0fa3b66738b52b8e2fc3bab8ad63ab032f4a"
[[package]]
name = "limits_core"
version = "2.0.1"
version = "3.0.0"
dependencies = [
"serde",
"serde_json",
]
[[package]]
@ -34,12 +27,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe232bdf6be8c8de797b22184ee71118d63780ea42ac85b61d1baa6d3b782ae9"
[[package]]
name = "serde"
version = "1.0.166"
@ -60,17 +47,6 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "2.0.23"

View File

@ -1,10 +1,9 @@
[package]
name = "limits_core"
version = "2.0.1"
version = "3.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -1,5 +1,5 @@
use std::default::Default;
use serde::{Deserialize, Serialize};
use std::default::Default;
/// Base JSON limits information
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -20,7 +20,7 @@ impl Default for Base {
name: "Steam Deck Custom".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()),
cpuinfo: Some("model name\t: AMD Custom APU (0405)|(0932)\n".to_owned()),
os: None,
command: None,
file_exists: Some("./pt_oc.json".into()),
@ -35,7 +35,7 @@ impl Default for Base {
name: "Steam Deck".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()),
cpuinfo: Some("model name\t: AMD Custom APU (0405)|(0932)\n".to_owned()),
os: None,
command: None,
file_exists: None,
@ -131,7 +131,7 @@ impl Default for Base {
name: "AMD R7 6800U".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t+: AMD Ryzen 7 6800U\n".to_owned()),
cpuinfo: Some("model name\t+: AMD Ryzen 7 6800U( with Radeon Graphics)?\n".to_owned()),
os: None,
command: None,
file_exists: None,
@ -174,8 +174,8 @@ impl Default for Base {
super::DeveloperMessage {
id: 1,
title: "Welcome".to_owned(),
body: "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue on GitHub.".to_owned(),
url: Some("https://github.com/NGnius/PowerTools/wiki".to_owned()),
body: "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue.".to_owned(),
url: Some("https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki".to_owned()),
}
],
refresh: Some("http://limits.ngni.us:45000/powertools/v1".to_owned())

View File

@ -0,0 +1,341 @@
use serde::{Deserialize, Serialize};
use std::default::Default;
/// Base JSON limits information
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Base {
/// System-specific configurations
pub configs: Vec<super::Config>,
/// Server messages
pub messages: Vec<super::DeveloperMessage>,
/// Base URL for the config store
pub store: String,
/// URL from which to grab the next update
pub refresh: Option<String>,
}
impl Default for Base {
fn default() -> Self {
Base {
configs: vec![
super::Config {
name: "Devs mode best mode".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: None,
os: None,
file_exists: Some("/etc/powertools_dev_mode".into()),
},
limits: super::Limits {
cpu: super::Limit {
provider: super::CpuLimitType::DevMode,
limits: super::GenericCpusLimit::default_for(super::CpuLimitType::DevMode),
},
gpu: super::Limit {
provider: super::GpuLimitType::DevMode,
limits: super::GenericGpuLimit::default_for(super::GpuLimitType::DevMode),
},
battery: super::Limit {
provider: super::BatteryLimitType::DevMode,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::DevMode),
},
}
},
super::Config {
name: "Steam Deck".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t: AMD Custom APU 0405\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::Limit {
provider: super::CpuLimitType::SteamDeck,
limits: super::GenericCpusLimit::default_for(super::CpuLimitType::SteamDeck),
},
gpu: super::Limit {
provider: super::GpuLimitType::SteamDeck,
limits: super::GenericGpuLimit::default_for(super::GpuLimitType::SteamDeck),
},
battery: super::Limit {
provider: super::BatteryLimitType::SteamDeck,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::SteamDeck),
},
}
},
super::Config {
name: "Steam Deck OLED".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t: AMD Custom APU 0932\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::Limit {
provider: super::CpuLimitType::SteamDeckOLED,
limits: super::GenericCpusLimit::default_for(super::CpuLimitType::SteamDeckOLED),
},
gpu: super::Limit {
provider: super::GpuLimitType::SteamDeckOLED,
limits: super::GenericGpuLimit::default_for(super::GpuLimitType::SteamDeckOLED),
},
battery: super::Limit {
provider: super::BatteryLimitType::SteamDeckOLED,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::SteamDeckOLED),
},
}
},
super::Config {
name: "AMD R3 2300U".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t+: AMD Ryzen 3 2300U\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::CpuLimit {
provider: super::CpuLimitType::GenericAMD,
limits: super::GenericCpusLimit {
cpus: vec![
super::GenericCpuLimit {
clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(3700) }),
clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(3700) }),
clock_step: Some(100),
skip_resume_reclock: false,
..Default::default()
}; 4],
global_governors: true,
..Default::default()
}
},
gpu: super::GpuLimit {
provider: super::GpuLimitType::GenericAMD,
limits: super::GenericGpuLimit {
fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }),
slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }),
ppt_step: Some(1_000),
ppt_divisor: Some(1_000),
clock_min: Some(super::RangeLimit { min: Some(400), max: Some(1100) }),
clock_max: Some(super::RangeLimit { min: Some(400), max: Some(1100) }),
clock_step: Some(100),
..Default::default()
}
},
battery: super::Limit {
provider: super::BatteryLimitType::Generic,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic),
}
},
},
super::Config {
name: "AMD R5 5560U".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t+: AMD Ryzen 5 5560U\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::CpuLimit {
provider: super::CpuLimitType::GenericAMD,
limits: super::GenericCpusLimit {
cpus: vec![
super::GenericCpuLimit {
clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(4000) }),
clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(4000) }),
clock_step: Some(100),
skip_resume_reclock: false,
..Default::default()
}; 12], // 6 cores with SMTx2
global_governors: true,
..Default::default()
}
},
gpu: super::GpuLimit {
provider: super::GpuLimitType::GenericAMD,
limits: super::GenericGpuLimit {
fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }),
slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(25_000) }),
ppt_step: Some(1_000),
ppt_divisor: Some(1_000),
clock_min: Some(super::RangeLimit { min: Some(400), max: Some(1600) }),
clock_max: Some(super::RangeLimit { min: Some(400), max: Some(1600) }),
clock_step: Some(100),
..Default::default()
}
},
battery: super::Limit {
provider: super::BatteryLimitType::Generic,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic),
}
}
},
super::Config {
name: "AMD R7 5825U".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t+: AMD Ryzen 7 5825U\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::CpuLimit {
provider: super::CpuLimitType::GenericAMD,
limits: super::GenericCpusLimit {
cpus: vec![
super::GenericCpuLimit {
clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(4500) }),
clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(4500) }),
clock_step: Some(100),
skip_resume_reclock: false,
..Default::default()
}; 16], // 8 cores with SMTx2
global_governors: true,
..Default::default()
}
},
gpu: super::GpuLimit {
provider: super::GpuLimitType::GenericAMD,
limits: super::GenericGpuLimit {
fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }),
slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }),
ppt_step: Some(1_000),
ppt_divisor: Some(1_000),
clock_min: Some(super::RangeLimit { min: Some(400), max: Some(2200) }),
clock_max: Some(super::RangeLimit { min: Some(400), max: Some(2200) }),
clock_step: Some(100),
..Default::default()
}
},
battery: super::Limit {
provider: super::BatteryLimitType::Generic,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic),
}
}
},
super::Config {
name: "AMD R7 6800U".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\t+: AMD Ryzen 7 6800U( with Radeon Graphics)?\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::CpuLimit {
provider: super::CpuLimitType::GenericAMD,
limits: super::GenericCpusLimit {
cpus: vec![
super::GenericCpuLimit {
clock_min: Some(super::RangeLimit { min: Some(1000), max: Some(4700) }),
clock_max: Some(super::RangeLimit { min: Some(1000), max: Some(4700) }),
clock_step: Some(100),
skip_resume_reclock: false,
..Default::default()
}; 16], // 8 cores with SMTx2
global_governors: true,
..Default::default()
}
},
gpu: super::GpuLimit {
provider: super::GpuLimitType::GenericAMD,
limits: super::GenericGpuLimit {
fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }),
slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(28_000) }),
ppt_step: Some(1_000),
ppt_divisor: Some(1_000),
clock_min: Some(super::RangeLimit { min: Some(400), max: Some(2200) }),
clock_max: Some(super::RangeLimit { min: Some(400), max: Some(2200) }),
clock_step: Some(100),
..Default::default()
}
},
battery: super::Limit {
provider: super::BatteryLimitType::Generic,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic),
}
}
},
super::Config {
name: "AMD R7 7840U".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: Some("model name\\s+: AMD Ryzen 7 7840U( w\\/ Radeon 780M Graphics)?\n".to_owned()),
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::CpuLimit {
provider: super::CpuLimitType::GenericAMD,
limits: super::GenericCpusLimit {
cpus: vec![
super::GenericCpuLimit {
clock_min: Some(super::RangeLimit { min: Some(400), max: Some(5100) }),
clock_max: Some(super::RangeLimit { min: Some(400), max: Some(5100) }),
clock_step: Some(100),
skip_resume_reclock: false,
..Default::default()
}; 16], // 8 cores with SMTx2
global_governors: true,
..Default::default()
}
},
gpu: super::GpuLimit {
provider: super::GpuLimitType::GenericAMD,
limits: super::GenericGpuLimit {
fast_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(53_000) }),
slow_ppt: Some(super::RangeLimit { min: Some(1_000), max: Some(43_000) }),
ppt_step: Some(1_000),
ppt_divisor: Some(1_000),
clock_min: None,
clock_max: None,
clock_step: None,
..Default::default()
}
},
battery: super::Limit {
provider: super::BatteryLimitType::Generic,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Generic),
}
}
},
super::Config {
name: "Fallback".to_owned(),
conditions: super::Conditions {
dmi: None,
cpuinfo: None,
os: None,
file_exists: None,
},
limits: super::Limits {
cpu: super::Limit {
provider: super::CpuLimitType::Unknown,
limits: super::GenericCpusLimit::default_for(super::CpuLimitType::Unknown),
},
gpu: super::Limit {
provider: super::GpuLimitType::Unknown,
limits: super::GenericGpuLimit::default_for(super::GpuLimitType::Unknown),
},
battery: super::Limit {
provider: super::BatteryLimitType::Unknown,
limits: super::GenericBatteryLimit::default_for(super::BatteryLimitType::Unknown),
}
}
}
],
messages: vec![
super::DeveloperMessage {
id: 1,
title: "Welcome".to_owned(),
body: "Thanks for installing PowerTools! For more information, please check the wiki. For bugs and requests, please create an issue.".to_owned(),
url: Some("https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki".to_owned()),
}
],
store: "https://powertools.ngni.us".to_owned(),
refresh: Some("http://limits.ngni.us:45000/powertools/v2".to_owned())
}
}
}

View File

@ -0,0 +1,99 @@
use serde::{Deserialize, Serialize};
use super::RangeLimit;
#[derive(Serialize, Deserialize, Debug, Clone)]
//#[serde(tag = "target")]
pub enum BatteryLimitType {
#[serde(rename = "GabeBoy", alias = "SteamDeck")]
SteamDeck,
#[serde(rename = "GabeBoySP", alias = "SteamDeckOLED")]