Compare commits
No commits in common. "main" and "v2.0.0" have entirely different histories.
6
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
@ -20,7 +20,7 @@ body:
|
||||||
id: reproduction
|
id: reproduction
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps To Reproduce
|
label: Steps To Reproduce
|
||||||
description: Steps to reproduce the behaviour.
|
description: Steps to reproduce the behavior
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
|
@ -33,12 +33,12 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Anything else?
|
label: Anything else?
|
||||||
description: |
|
description: |
|
||||||
Screenshots? Logs? limits_overrides.ron? limits_cache.ron? Anything that will give more context about the problem!
|
Screenshots? Logs? pt_oc.json? limits_overrides.json? Anything that will give more context about the problem!
|
||||||
If applicable, add screenshots to help explain your problem.
|
If applicable, add screenshots to help explain your problem.
|
||||||
Please include the log (located at `/tmp/powertools.log`) if possible.
|
Please include the log (located at `/tmp/powertools.log`) if possible.
|
||||||
Note: the log is deleted when the device is restarted.
|
Note: the log is deleted when the device is restarted.
|
||||||
|
|
||||||
Tip: You can attach files by clicking this area to highlight it and then dragging them in.
|
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: input
|
||||||
|
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -1,5 +1,5 @@
|
||||||
blank_issues_enabled: false
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: NGnius
|
- name: NGnius
|
||||||
url: https://git.ngni.us/sys/website/wiki#contact-information
|
url: https://github.com/NGnius
|
||||||
about: Repository owner
|
about: Repository owner
|
||||||
|
|
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
8
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
|
@ -3,7 +3,7 @@ description: Suggest functionality to add
|
||||||
labels: "enhancement"
|
labels: "enhancement"
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: feature-description
|
id: described
|
||||||
attributes:
|
attributes:
|
||||||
label: Describe what you'd like to be able to do
|
label: Describe what you'd like to be able to do
|
||||||
description: A clear and concise description of what you want.
|
description: A clear and concise description of what you want.
|
||||||
|
@ -22,11 +22,7 @@ body:
|
||||||
id: extras
|
id: extras
|
||||||
attributes:
|
attributes:
|
||||||
label: Anything else?
|
label: Anything else?
|
||||||
description: |
|
description: Description of how this can be achieved, or other additional context
|
||||||
Description of how this can be achieved, or other additional context.
|
|
||||||
If this is related to the UI, consider adding a picture.
|
|
||||||
|
|
||||||
Tip: You can attach files by clicking this area to highlight it and then dragging them in.
|
|
||||||
placeholder: This can be accomplished by doing ...
|
placeholder: This can be accomplished by doing ...
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
23
.github/ISSUE_TEMPLATE/question.yml
vendored
23
.github/ISSUE_TEMPLATE/question.yml
vendored
|
@ -1,23 +0,0 @@
|
||||||
name: "Question"
|
|
||||||
description: "Ask for more information about PowerTools"
|
|
||||||
labels: "question"
|
|
||||||
body:
|
|
||||||
- type: textarea
|
|
||||||
id: question-elaboration
|
|
||||||
attributes:
|
|
||||||
label: Question
|
|
||||||
description: |
|
|
||||||
A clear and concise description of what you'd like to know.
|
|
||||||
Please check the wiki and closed issues to avoid waiting for an answer when you didn't need to.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: extras
|
|
||||||
attributes:
|
|
||||||
label: Extra Info
|
|
||||||
description: |
|
|
||||||
Additional context or information which may be helpful when answering your question.
|
|
||||||
|
|
||||||
Tip: You can attach files by clicking this area to highlight it and then dragging them in.
|
|
||||||
validations:
|
|
||||||
required: false
|
|
13
.github/pull_request_template.md
vendored
13
.github/pull_request_template.md
vendored
|
@ -1,13 +0,0 @@
|
||||||
## Description
|
|
||||||
|
|
||||||
A short description of what the PR changes.
|
|
||||||
If this is a minor change and the commit message(s) explains it well, feel free to delete this section.
|
|
||||||
|
|
||||||
## Motivation
|
|
||||||
|
|
||||||
Why did you write this PR and/or why should it be accepted.
|
|
||||||
|
|
||||||
## Fixes
|
|
||||||
|
|
||||||
Please indicate issues that this fixes or addresses here.
|
|
||||||
If this is a bugfix with no pre-existing issue, please describe the bug here instead.
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -49,4 +49,3 @@ yalc.lock
|
||||||
# packaging
|
# packaging
|
||||||
/PowerTools
|
/PowerTools
|
||||||
**.zip
|
**.zip
|
||||||
deck.json
|
|
||||||
|
|
28
README.md
28
README.md
|
@ -1,12 +1,12 @@
|
||||||
# PowerTools
|
# PowerTools
|
||||||
<!-- TODO Update badges for new git repo location -->
|
<!-- TODO Update badges for new git repo location -->
|
||||||
[![Decky store](https://img.shields.io/badge/dynamic/json?color=blue&label=release&query=%24%5B%3F%28%40.name%3D%3D%27PowerTools%27%29%5D.versions%5B0%5D.name&url=https%3A%2F%2Fplugins.deckbrew.xyz%2Fplugins&style=flat-square)](https://plugins.deckbrew.xyz/)
|
[![Decky store](https://img.shields.io/badge/dynamic/json?color=blue&label=release&query=%24%5B%3F%28%40.name%3D%3D%27PowerTools%27%29%5D.versions%5B0%5D.name&url=https%3A%2F%2Fplugins.deckbrew.xyz%2Fplugins&style=flat-square)](https://plugins.deckbrew.xyz/)
|
||||||
[![Custom store](https://img.shields.io/badge/dynamic/json?color=blue&label=preview&query=%24%5B%3F%28%40.name%3D%3D%27PowerTools%27%29%5D.versions%5B0%5D.name&url=https%3A%2F%2Fnot-decky-alpha.ngni.us%2Fplugins&style=flat-square)](https://git.ngni.us/NG-SD-Plugins/PowerTools/wiki)
|
[![Custom store](https://img.shields.io/badge/dynamic/json?color=blue&label=preview&query=%24%5B%3F%28%40.name%3D%3D%27PowerTools%27%29%5D.versions%5B0%5D.name&url=https%3A%2F%2Fnot-decky-alpha.ngni.us%2Fplugins&style=flat-square)](https://github.com/NGnius/PowerTools/wiki)
|
||||||
[![GitHub package.json version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgit.ngni.us%2FNG-SD-Plugins%2FPowerTools%2Fraw%2Fbranch%2Fmain%2Fpackage.json&query=%24.version&style=flat-square&label=local&cacheSeconds=600)](https://git.ngni.us/NG-SD-Plugins/PowerTools/src/branch/main/package.json)
|
[![GitHub package.json version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgit.ngni.us%2FNG-SD-Plugins%2FPowerTools%2Fraw%2Fbranch%2Fmain%2Fpackage.json&query=%24.version&style=flat-square&label=local&cacheSeconds=600)](https://git.ngni.us/NG-SD-Plugins/PowerTools/src/branch/main/package.json)
|
||||||
|
|
||||||
[![Liberapay](https://img.shields.io/liberapay/patrons/NGnius?style=flat-square)](https://liberapay.com/NGnius)
|
[![Liberapay](https://img.shields.io/liberapay/patrons/NGnius?style=flat-square)](https://liberapay.com/NGnius)
|
||||||
[![GitHub](https://img.shields.io/badge/GPL--3.0-orange?style=flat-square&label=license&cacheSeconds=600)](https://git.ngni.us/NG-SD-Plugins/PowerTools/blob/main/LICENSE)
|
[![GitHub](https://img.shields.io/badge/GPL--3.0-orange?style=flat-square&label=license&cacheSeconds=600)](https://github.com/NGnius/PowerTools/blob/main/LICENSE)
|
||||||
[![GitHub package.json dependency version (local)](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgit.ngni.us%2FNG-SD-Plugins%2FPowerTools%2Fraw%2Fbranch%2Fmain%2Fpackage.json&query=%24..%5B'decky-frontend-lib'%5D&style=flat-square&label=decky-frontend-lib&cacheSeconds=600)](https://git.ngni.us/NG-SD-Plugins/PowerTools/blob/main/pnpm-lock.yaml)
|
[![GitHub package.json dependency version (local)](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgit.ngni.us%2FNG-SD-Plugins%2FPowerTools%2Fraw%2Fbranch%2Fmain%2Fpackage.json&query=%24..%5B'decky-frontend-lib'%5D&style=flat-square&label=decky-frontend-lib&cacheSeconds=600)](https://github.com/NGnius/PowerTools/blob/main/pnpm-lock.yaml)
|
||||||
|
|
||||||
![plugin_demo](./assets/ui.png)
|
![plugin_demo](./assets/ui.png)
|
||||||
|
|
||||||
|
@ -20,38 +20,24 @@ You will need that installed for this plugin to work.
|
||||||
- Enable & disable CPU threads & SMT
|
- Enable & disable CPU threads & SMT
|
||||||
- Set CPU frequencies
|
- Set CPU frequencies
|
||||||
- Set GPU frequencies and power (fastPPT & slowPPT)
|
- Set GPU frequencies and power (fastPPT & slowPPT)
|
||||||
- Cap battery charge level
|
- Cap battery charge rate (when awake)
|
||||||
- Display supplementary battery info
|
- Display supplementary battery info
|
||||||
- Keep settings between restarts (stored in `~/homebrew/settings/PowerTools/<appId>.ron`)
|
- Keep settings between restarts (stored in `~/.config/powertools/<gameId>.json`)
|
||||||
|
|
||||||
This plugin is tested on Steam Deck LCD/OLED, but is designed to work on other Linux devices as well. Unfortunately I am currently unable to test on most other devices.
|
This plugin is tested on Steam Deck, but is designed to work on other Linux devices as well. Unfortunately I am currently unable to test on other devices.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Please use Decky's [built-in store](https://plugins.deckbrew.xyz/) to install official releases.
|
Please use Decky's [built-in store](https://plugins.deckbrew.xyz/) to install official releases.
|
||||||
If you want to test unstable versions, use [my custom store](https://not-decky-alpha.ngni.us/plugins). If you would like to use an in-development version, feel free to build PowerTools yourself.
|
If you want to test unstable versions, use [my custom store](https://not-decky-alpha.ngni.us/plugins). If you would like to use an in-development version, feel free to build PowerTools yourself.
|
||||||
|
|
||||||
## Build/Deploy
|
## Build
|
||||||
|
|
||||||
0. Requirements: a functioning Rust toolchain for x86_64-unknown-linux-gnu (or -musl), pnpm, and some tech literacy
|
0. Requirements: a functioning Rust toolchain for x86_64-unknown-linux-gnu (or -musl), pnpm, and some tech literacy
|
||||||
1. In a terminal, navigate to the backend directory of this project and run `./build.sh`
|
1. In a terminal, navigate to the backend directory of this project and run `./build.sh`
|
||||||
2. In the root of this project, run `pnpm run build`
|
2. In the root of this project, run `pnpm run build`
|
||||||
3. Transfer the project (especially dist/ and bin/) to a folder in your Steam Deck's `~/homebrew/plugins` directory
|
3. Transfer the project (especially dist/ and bin/) to a folder in your Steam Deck's `~/homebrew/plugins` directory
|
||||||
4. Restart Decky with `sudo systemctl restart plugin_loader.service`
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This is licensed under GNU GPLv3.
|
This is licensed under GNU GPLv3.
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
All contributions are welcome!
|
|
||||||
Anything from a comment on an issue to a new feature pull request will be appreciated by PowerTools's crack team of one (NGnius).
|
|
||||||
|
|
||||||
### Translations
|
|
||||||
|
|
||||||
Adding new languages and keeping existing language files up to date makes PowerTools more accessible to the majority of the world which doesn't speak English. Take a look at [this comment](https://git.ngni.us/NG-SD-Plugins/PowerTools/issues/9#issuecomment-345) (and the rest of that issue) to get started.
|
|
||||||
|
|
||||||
### Code
|
|
||||||
|
|
||||||
To prevent spam, this server does not allow regular users to create/fork repositories. Please open an issue [here](https://git.ngni.us/sys/website) to request permission. There's no pressure to actually do anything with that permission, though it may be revoked when the server is running low on space.
|
|
||||||
|
|
10
backend/Cargo.lock
generated
10
backend/Cargo.lock
generated
|
@ -1170,7 +1170,7 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powertools"
|
name = "powertools"
|
||||||
version = "2.0.3"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1481,9 +1481,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smokepatio"
|
name = "smokepatio"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3416e8c907d171c4334df3933873c32bff97ca5ad7ae0ee93e6268e04e2041ef"
|
checksum = "626ef8beee78bebc397d841469fa47bf7e370ddb8b8f3e628e69b03bf968d089"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-io",
|
"embedded-io",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1540,9 +1540,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysfuss"
|
name = "sysfuss"
|
||||||
version = "0.3.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f33bae529511a671b5f2ed4cc46ae0b2ccdf8c03ccf7eebe95a5a886ff7914dc"
|
checksum = "4fa4dd5879b3fd41aff63991a59970cdfeced6f0d5920c5da0937279904d9f45"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "powertools"
|
name = "powertools"
|
||||||
version = "2.0.3"
|
version = "2.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
|
||||||
description = "Backend (superuser) functionality for PowerTools"
|
description = "Backend (superuser) functionality for PowerTools"
|
||||||
|
@ -16,7 +16,7 @@ usdpl-back = { version = "0.10.1", features = ["blocking", "decky"] }#, path = "
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
sysfuss = { version = "0.3", features = ["derive"] }#,path = "../../sysfs-nav"}
|
sysfuss = { version = "0.2", features = ["derive"] }#,path = "../../sysfs-nav"}
|
||||||
|
|
||||||
# async
|
# async
|
||||||
tokio = { version = "*", features = ["time"] }
|
tokio = { version = "*", features = ["time"] }
|
||||||
|
@ -31,7 +31,7 @@ limits_core = { version = "3", path = "./limits_core" }
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
# steam deck libs
|
# steam deck libs
|
||||||
smokepatio = { version = "0.2", default-features = false }
|
smokepatio = { version = "0.1", features = [ "std" ] }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
# online settings
|
# online settings
|
||||||
|
|
|
@ -1,34 +1,27 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd /backend
|
echo "--- Rust version info ---"
|
||||||
|
|
||||||
echo "--- Debug info ---"
|
|
||||||
rustup --version
|
rustup --version
|
||||||
rustc --version
|
rustc --version
|
||||||
cargo --version
|
cargo --version
|
||||||
|
|
||||||
id $USER
|
mkdir -p out
|
||||||
stat /backend || exit 1
|
|
||||||
stat /backend/out || exit 1
|
|
||||||
|
|
||||||
echo $(uname -a) > /backend/out/version.txt
|
|
||||||
|
|
||||||
echo "--- Building ryzenadj lib ---"
|
echo "--- Building ryzenadj lib ---"
|
||||||
git clone https://github.com/FlyGoat/RyzenAdj /tmp/ryzenadj
|
git clone https://github.com/FlyGoat/RyzenAdj ryzenadj
|
||||||
cd /tmp/ryzenadj
|
cd ryzenadj
|
||||||
git checkout -q v0.14.0
|
git checkout -q 160502771054d31d2f4c2fa46ad42c96336f3a74
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release .. || exit 1
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
make || exit 1
|
make
|
||||||
mv libryzenadj.so /backend/out/libryzenadj.so || exit 1
|
mv libryzenadj.so ../../out/libryzenadj.so
|
||||||
mv ryzenadj /backend/out/ryzenadj || exit 1
|
cd ../..
|
||||||
cd /backend
|
|
||||||
|
|
||||||
echo "--- Building plugin backend ---"
|
echo "--- Building plugin backend ---"
|
||||||
cargo build --profile docker || exit 1
|
cargo build --profile docker
|
||||||
mkdir -p out || exit 1
|
mkdir -p out
|
||||||
|
|
||||||
mv target/docker/powertools out/backend || exit 1
|
mv target/docker/powertools out/backend
|
||||||
|
|
||||||
echo " --- Cleaning up ---"
|
echo " --- Cleaning up ---"
|
||||||
# remove root-owned target folder
|
# remove root-owned target folder
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
#cargo build --release --target x86_64-unknown-linux-musl
|
#cargo build --release --target x86_64-unknown-linux-musl
|
||||||
#cargo build --target x86_64-unknown-linux-musl
|
#cargo build --target x86_64-unknown-linux-musl
|
||||||
#cross build
|
#cross build
|
||||||
#cargo build --release
|
cargo build --release
|
||||||
cargo build
|
#cargo build
|
||||||
|
|
||||||
mkdir -p ../bin
|
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/release/powertools ../bin/backend
|
||||||
#cp --preserve=mode ./target/x86_64-unknown-linux-musl/debug/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/release/powertools ../bin/backend
|
||||||
cp --preserve=mode ./target/debug/powertools ../bin/backend
|
#cp --preserve=mode ./target/debug/powertools ../bin/backend
|
||||||
|
|
22
backend/community_settings_srv/Cargo.lock
generated
22
backend/community_settings_srv/Cargo.lock
generated
|
@ -438,7 +438,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "community_settings_srv"
|
name = "community_settings_srv"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -450,7 +450,6 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"simplelog",
|
"simplelog",
|
||||||
"tokio",
|
"tokio",
|
||||||
"walkdir",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1018,15 +1017,6 @@ version = "1.0.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "same-file"
|
|
||||||
version = "1.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
|
||||||
dependencies = [
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -1329,16 +1319,6 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "walkdir"
|
|
||||||
version = "2.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
|
||||||
dependencies = [
|
|
||||||
"same-file",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "community_settings_srv"
|
name = "community_settings_srv"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -16,8 +16,6 @@ tokio = { version = "1", features = ["full"] }
|
||||||
actix-web = { version = "4.4" }
|
actix-web = { version = "4.4" }
|
||||||
mime = { version = "0.3.17" }
|
mime = { version = "0.3.17" }
|
||||||
|
|
||||||
walkdir = "2"
|
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
simplelog = "0.12"
|
simplelog = "0.12"
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
cargo build --release --target aarch64-unknown-linux-musl
|
|
|
@ -60,12 +60,11 @@ fn get_some_settings_by_app_id(steam_app_id: u32, cli: &'static Cli) -> std::io:
|
||||||
let app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, steam_app_id);
|
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()?
|
let mut files: Vec<_> = app_id_folder.read_dir()?
|
||||||
.filter_map(|res| res.ok())
|
.filter_map(|res| res.ok())
|
||||||
.filter(|f| f.path().extension().map(|ext| ext == crate::consts::RON_EXTENSION).unwrap_or(false))
|
.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| f.metadata().ok().map(|meta| (f, meta)))
|
||||||
.filter_map(|(f, meta)| meta.modified().ok().map(|time| (f, meta, time)))
|
.filter_map(|(f, meta)| meta.created().ok().map(|time| (f, meta, time)))
|
||||||
.collect();
|
.collect();
|
||||||
files.sort_by(|(_, _, a_created), (_, _, b_created)| a_created.cmp(b_created));
|
files.sort_by(|(_, _, a_created), (_, _, b_created)| a_created.cmp(b_created));
|
||||||
let files_len = files.len();
|
|
||||||
|
|
||||||
let mut results = Vec::with_capacity(MAX_RESULTS);
|
let mut results = Vec::with_capacity(MAX_RESULTS);
|
||||||
for (_, (f, _, _)) in files.into_iter().enumerate().take_while(|(i, _)| *i < MAX_RESULTS) {
|
for (_, (f, _, _)) in files.into_iter().enumerate().take_while(|(i, _)| *i < MAX_RESULTS) {
|
||||||
|
@ -73,14 +72,12 @@ fn get_some_settings_by_app_id(steam_app_id: u32, cli: &'static Cli) -> std::io:
|
||||||
let setting = match ron::de::from_reader(reader) {
|
let setting = match ron::de::from_reader(reader) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::debug!("Error while reading {}: {}", f.path().display(), e);
|
|
||||||
let e_msg = format!("{}", e);
|
let e_msg = format!("{}", e);
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
|
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
results.push(setting);
|
results.push(setting);
|
||||||
}
|
}
|
||||||
log::debug!("Got {} results (from {} files) for {}", results.len(), files_len, app_id_folder.display());
|
|
||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,8 +88,7 @@ pub async fn get_setting_by_app_id_handler(
|
||||||
cli: web::Data<&'static Cli>,
|
cli: web::Data<&'static Cli>,
|
||||||
) -> std::io::Result<impl Responder> {
|
) -> std::io::Result<impl Responder> {
|
||||||
let id: u32 = *id;
|
let id: u32 = *id;
|
||||||
#[cfg(debug_assertions)]
|
println!("Accept: {}", accept.to_string());
|
||||||
log::debug!("Accept: {}", accept.to_string());
|
|
||||||
let preferred = accept.preference();
|
let preferred = accept.preference();
|
||||||
if super::is_mime_type_ron_capable(&preferred) {
|
if super::is_mime_type_ron_capable(&preferred) {
|
||||||
// Send RON
|
// Send RON
|
||||||
|
|
|
@ -67,7 +67,7 @@ pub async fn get_setting_handler(
|
||||||
if super::is_mime_type_ron_capable(&preferred) {
|
if super::is_mime_type_ron_capable(&preferred) {
|
||||||
// Send RON
|
// Send RON
|
||||||
let ron = if id != 0 {
|
let ron = if id != 0 {
|
||||||
let path = file_util::setting_path_by_id(&cli.folder, id, crate::consts::RON_EXTENSION);
|
let path = file_util::setting_path_by_id(&cli.folder, id, file_util::RON_EXTENSION);
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("setting id {} does not exist", id)));
|
return Err(std::io::Error::new(std::io::ErrorKind::NotFound, format!("setting id {} does not exist", id)));
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ pub async fn get_setting_handler(
|
||||||
} else {
|
} else {
|
||||||
// Send JSON (fallback)
|
// Send JSON (fallback)
|
||||||
let json = if id != 0 {
|
let json = if id != 0 {
|
||||||
let path = file_util::setting_path_by_id(&cli.folder, id, crate::consts::JSON_EXTENSION);
|
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
|
// TODO? cache this instead of always loading it from file
|
||||||
let reader = std::io::BufReader::new(std::fs::File::open(path)?);
|
let reader = std::io::BufReader::new(std::fs::File::open(path)?);
|
||||||
match serde_json::from_reader(reader) {
|
match serde_json::from_reader(reader) {
|
||||||
|
|
|
@ -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")),
|
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 next_id = file_util::next_setting_id(&cli.folder);
|
||||||
let mut parsed_data: community_settings_core::v1::Metadata = 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
|
// Parse as RON
|
||||||
match ron::de::from_reader(bytes.as_ref()) {
|
match ron::de::from_reader(bytes.as_ref()) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
|
@ -42,18 +42,16 @@ pub async fn save_setting_handler(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Override the ID with the one used by this server
|
|
||||||
parsed_data.id = next_id.to_string();
|
|
||||||
// TODO validate user and app id
|
// TODO validate user and app id
|
||||||
// Reject blocked users and apps
|
// Reject blocked users and apps
|
||||||
let path_ron = file_util::setting_path_by_id(&cli.folder, next_id, crate::consts::RON_EXTENSION);
|
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)?);
|
let writer = std::io::BufWriter::new(std::fs::File::create(&path_ron)?);
|
||||||
if let Err(e) = ron::ser::to_writer(writer, &parsed_data) {
|
if let Err(e) = ron::ser::to_writer(writer, &parsed_data) {
|
||||||
let e_msg = format!("{}", e);
|
let e_msg = format!("{}", e);
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
|
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, crate::consts::JSON_EXTENSION);
|
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)?);
|
let writer = std::io::BufWriter::new(std::fs::File::create(&path_json)?);
|
||||||
if let Err(e) = serde_json::to_writer(writer, &parsed_data) {
|
if let Err(e) = serde_json::to_writer(writer, &parsed_data) {
|
||||||
let e_msg = format!("{}", e);
|
let e_msg = format!("{}", e);
|
||||||
|
@ -64,32 +62,57 @@ pub async fn save_setting_handler(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log::debug!("Saved to {}, building symlinks", path_ron.display());
|
// 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);
|
||||||
|
|
||||||
let to_symlink = file_util::symlinks(&cli.folder, &parsed_data)?;
|
// create symlinks to app id folder
|
||||||
|
let app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, parsed_data.steam_app_id);
|
||||||
let path_ron_canon = path_ron.canonicalize()?;
|
if !app_id_folder.exists() {
|
||||||
let path_json_canon = path_json.canonicalize()?;
|
std::fs::create_dir(&app_id_folder)?;
|
||||||
for ron_link in to_symlink.ron {
|
|
||||||
log::debug!("Symlinking {} -> {}", ron_link.display(), path_ron_canon.display());
|
|
||||||
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
|
||||||
{
|
|
||||||
std::os::windows::fs::symlink_file(&path_ron_canon, &ron_link)?;
|
|
||||||
}
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
std::os::unix::fs::symlink(&path_ron_canon, &ron_link)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for json_link in to_symlink.json {
|
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
||||||
//log::debug!("Symlinking {} -> {}", json_link.display(), path_json_canon.display());
|
{
|
||||||
|
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
|
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
||||||
{
|
{
|
||||||
std::os::windows::fs::symlink_file(&path_json_canon, json_link)?;
|
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")]
|
#[cfg(target_family = "unix")]
|
||||||
{
|
{
|
||||||
std::os::unix::fs::symlink(&path_json_canon, json_link)?;
|
std::os::unix::fs::symlink(&path_ron, tag_folder.join(&filename_ron))?;
|
||||||
|
std::os::unix::fs::symlink(&path_json, tag_folder.join(&filename_json))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,6 @@ pub struct Cli {
|
||||||
/// Log file location
|
/// Log file location
|
||||||
#[arg(short, long, default_value = "/tmp/powertools_community_settings_srv.log")]
|
#[arg(short, long, default_value = "/tmp/powertools_community_settings_srv.log")]
|
||||||
pub log: std::path::PathBuf,
|
pub log: std::path::PathBuf,
|
||||||
|
|
||||||
/// Perform maintenance tasks
|
|
||||||
#[arg(long)]
|
|
||||||
pub fix: bool,
|
|
||||||
|
|
||||||
/// Keep up to this many duplicate settings (0 will delete everything!!!)
|
|
||||||
#[arg(long, default_value_t = 2)]
|
|
||||||
pub duplicates: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
pub const RON_EXTENSION: &'static str = "ron";
|
|
||||||
pub const JSON_EXTENSION: &'static str = "json";
|
|
||||||
|
|
||||||
pub const SETTING_FOLDER: &'static str = "settings";
|
|
||||||
pub const ID_FOLDER: &'static str = "by_id";
|
|
||||||
pub const APP_ID_FOLDER: &'static str = "by_app_id";
|
|
||||||
pub const USER_ID_FOLDER: &'static str = "by_user_id";
|
|
||||||
pub const TAG_FOLDER: &'static str = "by_tag";
|
|
|
@ -1,10 +1,41 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use crate::consts::*;
|
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);
|
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 {
|
pub fn filename(id: u128, ext: &str) -> String {
|
||||||
format!("{}.{}", id, ext)
|
format!("{}.{}", id, ext)
|
||||||
}
|
}
|
||||||
|
@ -23,23 +54,6 @@ pub fn setting_folder_by_app_id(root: impl AsRef<Path>, steam_app_id: u32) -> Pa
|
||||||
.join(steam_app_id.to_string())
|
.join(steam_app_id.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setting_tag_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())
|
|
||||||
.join(TAG_FOLDER)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setting_folder_by_app_id_tag(root: impl AsRef<Path>, steam_app_id: u32, tag: &str) -> PathBuf {
|
|
||||||
root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(APP_ID_FOLDER)
|
|
||||||
.join(steam_app_id.to_string())
|
|
||||||
.join(TAG_FOLDER)
|
|
||||||
.join(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setting_folder_by_user_id(root: impl AsRef<Path>, steam_user_id: u64) -> PathBuf {
|
pub fn setting_folder_by_user_id(root: impl AsRef<Path>, steam_user_id: u64) -> PathBuf {
|
||||||
root.as_ref()
|
root.as_ref()
|
||||||
.join(SETTING_FOLDER)
|
.join(SETTING_FOLDER)
|
||||||
|
@ -47,23 +61,6 @@ pub fn setting_folder_by_user_id(root: impl AsRef<Path>, steam_user_id: u64) ->
|
||||||
.join(steam_user_id.to_string())
|
.join(steam_user_id.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setting_tag_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())
|
|
||||||
.join(TAG_FOLDER)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setting_folder_by_user_id_tag(root: impl AsRef<Path>, steam_user_id: u64, tag: &str) -> PathBuf {
|
|
||||||
root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(USER_ID_FOLDER)
|
|
||||||
.join(steam_user_id.to_string())
|
|
||||||
.join(TAG_FOLDER)
|
|
||||||
.join(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setting_folder_by_tag(root: impl AsRef<Path>, tag: &str) -> PathBuf {
|
pub fn setting_folder_by_tag(root: impl AsRef<Path>, tag: &str) -> PathBuf {
|
||||||
root.as_ref()
|
root.as_ref()
|
||||||
.join(SETTING_FOLDER)
|
.join(SETTING_FOLDER)
|
||||||
|
@ -76,83 +73,14 @@ pub fn next_setting_id(root: impl AsRef<Path>) -> u128 {
|
||||||
let mut last_id = *lock;
|
let mut last_id = *lock;
|
||||||
if last_id == 0 {
|
if last_id == 0 {
|
||||||
// needs init
|
// needs init
|
||||||
last_id = 1;
|
|
||||||
let mut path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION);
|
let mut path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION);
|
||||||
while path.exists() {
|
while path.exists() {
|
||||||
last_id += 1;
|
last_id += 1;
|
||||||
path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION);
|
path = setting_path_by_id(root.as_ref(), last_id, RON_EXTENSION);
|
||||||
}
|
}
|
||||||
*lock = last_id - 1;
|
*lock = last_id;
|
||||||
log::info!("setting id initialized to {}", last_id);
|
println!("setting id initialized to {}", last_id);
|
||||||
}
|
}
|
||||||
*lock += 1;
|
*lock += 1;
|
||||||
*lock
|
*lock
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToSymlink {
|
|
||||||
pub ron: Vec<PathBuf>,
|
|
||||||
pub json: Vec<PathBuf>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn symlinks(root: impl AsRef<Path>, meta: &community_settings_core::v1::Metadata) -> std::io::Result<ToSymlink> {
|
|
||||||
let mut symlink_locations = ToSymlink { ron: Vec::new(), json: Vec::new() };
|
|
||||||
let filename_ron = filename(meta.get_id(), crate::consts::RON_EXTENSION);
|
|
||||||
let filename_json = filename(meta.get_id(), crate::consts::JSON_EXTENSION);
|
|
||||||
// build symlinks to app id folder
|
|
||||||
let app_id_folder = setting_folder_by_app_id(&root, meta.steam_app_id);
|
|
||||||
log::debug!("App id folder {}", app_id_folder.display());
|
|
||||||
if !app_id_folder.exists() {
|
|
||||||
std::fs::create_dir(&app_id_folder)?;
|
|
||||||
std::fs::create_dir(setting_tag_folder_by_app_id(&root, meta.steam_app_id))?;
|
|
||||||
}
|
|
||||||
symlink_locations.ron.push(app_id_folder.join(&filename_ron));
|
|
||||||
symlink_locations.json.push(app_id_folder.join(&filename_json));
|
|
||||||
|
|
||||||
// create symlinks for user id folder
|
|
||||||
let user_id_folder = setting_folder_by_user_id(&root, meta.steam_user_id);
|
|
||||||
if !user_id_folder.exists() {
|
|
||||||
std::fs::create_dir(&user_id_folder)?;
|
|
||||||
std::fs::create_dir(setting_tag_folder_by_user_id(&root, meta.steam_user_id))?;
|
|
||||||
}
|
|
||||||
symlink_locations.ron.push(user_id_folder.join(&filename_ron));
|
|
||||||
symlink_locations.json.push(user_id_folder.join(&filename_json));
|
|
||||||
|
|
||||||
// create symlinks for each tag
|
|
||||||
for tag in meta.tags.iter() {
|
|
||||||
if !str_is_alphanumeric_or_space(&tag){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// create symlinks for general tag folder
|
|
||||||
let tag_folder = setting_folder_by_tag(&root, tag);
|
|
||||||
if !tag_folder.exists() {
|
|
||||||
std::fs::create_dir(&tag_folder)?;
|
|
||||||
}
|
|
||||||
symlink_locations.ron.push(tag_folder.join(&filename_ron));
|
|
||||||
symlink_locations.json.push(tag_folder.join(&filename_json));
|
|
||||||
|
|
||||||
// create symlinks for app id tag folder
|
|
||||||
let app_tag_folder = setting_folder_by_app_id_tag(&root, meta.steam_app_id, tag);
|
|
||||||
if !app_tag_folder.exists() {
|
|
||||||
std::fs::create_dir(&app_tag_folder)?;
|
|
||||||
}
|
|
||||||
symlink_locations.ron.push(app_tag_folder.join(&filename_ron));
|
|
||||||
symlink_locations.json.push(app_tag_folder.join(&filename_json));
|
|
||||||
|
|
||||||
// create symlinks for user id tag folder
|
|
||||||
let user_tag_folder = setting_folder_by_user_id_tag(&root, meta.steam_user_id, tag);
|
|
||||||
if !user_tag_folder.exists() {
|
|
||||||
std::fs::create_dir(&user_tag_folder)?;
|
|
||||||
}
|
|
||||||
symlink_locations.ron.push(user_tag_folder.join(&filename_ron));
|
|
||||||
symlink_locations.json.push(user_tag_folder.join(&filename_json));
|
|
||||||
}
|
|
||||||
Ok(symlink_locations)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn str_is_alphanumeric_or_space(s: &str) -> bool {
|
|
||||||
let mut result = true;
|
|
||||||
for ch in s.chars() {
|
|
||||||
result &= ch.is_ascii_alphanumeric() || ch == ' ';
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod cli;
|
mod cli;
|
||||||
mod consts;
|
|
||||||
mod file_util;
|
mod file_util;
|
||||||
mod tasks;
|
|
||||||
mod upgrade;
|
|
||||||
|
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
|
|
||||||
|
@ -30,22 +27,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
|
|
||||||
// setup
|
// setup
|
||||||
log::debug!("Building folder layout (if not exists) at: {}", &args.folder.display());
|
log::debug!("Building folder layout (if not exists) at: {}", &args.folder.display());
|
||||||
upgrade::build_folder_layout(&args.folder)?;
|
file_util::build_folder_layout(&args.folder)?;
|
||||||
|
|
||||||
// fix things
|
|
||||||
if args.fix {
|
|
||||||
log::info!("Fixing old symlinks");
|
|
||||||
upgrade::fix_symlinks(&args.folder)?;
|
|
||||||
log::info!("Creating missing by_tag folders");
|
|
||||||
upgrade::make_tag_subfolders(&args.folder)?;
|
|
||||||
log::info!("Resynchronizing file IDs with file name IDs");
|
|
||||||
upgrade::sync_ids(&args.folder)?;
|
|
||||||
log::info!("Rebuilding missing symlinks");
|
|
||||||
upgrade::rebuild_symlinks(&args.folder)?;
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks::start_tasks(args.clone());
|
|
||||||
|
|
||||||
let leaked_args: &'static cli::Cli = Box::leak::<'static>(Box::new(args));
|
let leaked_args: &'static cli::Cli = Box::leak::<'static>(Box::new(args));
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
mod task_runner;
|
|
||||||
mod symlink_cleanup;
|
|
||||||
mod user_antispam;
|
|
||||||
|
|
||||||
pub fn start_tasks(args: crate::cli::Cli) {
|
|
||||||
task_runner::TaskRunner::new(
|
|
||||||
args.clone(),
|
|
||||||
symlink_cleanup::remove_broken_symlinks,
|
|
||||||
std::time::Duration::from_secs(1 * 60 /* 1 minute */),
|
|
||||||
).run();
|
|
||||||
task_runner::TaskRunner::new(
|
|
||||||
args.clone(),
|
|
||||||
user_antispam::remove_similar_user_uploads,
|
|
||||||
std::time::Duration::from_secs(5 * 60 /* 5 minutes */),
|
|
||||||
).run();
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
//! Realistically this shouldn't be occur unless there's corruption or a settings files is manually deleted
|
|
||||||
|
|
||||||
pub fn remove_broken_symlinks(args: &mut crate::cli::Cli) {
|
|
||||||
log::info!("Starting broken symlink removal task");
|
|
||||||
if let Err(e) = enforce_working_symlinks(&args.folder) {
|
|
||||||
log::error!("Error in broken symlink task: {}", e);
|
|
||||||
}
|
|
||||||
log::info!("Completed broken symlink removal task");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_working_symlinks(root: impl AsRef<std::path::Path>) -> std::io::Result<()> {
|
|
||||||
for dir_entry in walkdir::WalkDir::new(root.as_ref().join(crate::consts::SETTING_FOLDER)) {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
match check_symlink(dir_entry.path()) {
|
|
||||||
Ok(true) => {},
|
|
||||||
Ok(false) => {
|
|
||||||
log::info!("Symlink {} seems broken, removing...", dir_entry.path().display());
|
|
||||||
if let Err(e) = std::fs::remove_file(dir_entry.path()) {
|
|
||||||
log::warn!("Failed to delete broken symlink {}: {}", dir_entry.path().display(), e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(symlink_e) => {
|
|
||||||
log::info!("Thing {} seems broken, removing... err: {}", dir_entry.path().display(), symlink_e);
|
|
||||||
if let Err(e) = std::fs::remove_file(dir_entry.path()) {
|
|
||||||
log::warn!("Failed to delete broken thing {}: {}", dir_entry.path().display(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_symlink(path: impl AsRef<std::path::Path>) -> std::io::Result<bool> {
|
|
||||||
if path.as_ref().is_symlink() {
|
|
||||||
Ok(path.as_ref().read_link().is_ok_and(|link| link.exists()))
|
|
||||||
} else {
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
use std::thread::{spawn, JoinHandle, sleep};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
pub struct TaskRunner<C: Send + 'static, F: FnMut(&mut C) + Send + 'static> {
|
|
||||||
task: F,
|
|
||||||
context: C,
|
|
||||||
period: Option<Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <C: Send + 'static, F: FnMut(&mut C) + Send + 'static> TaskRunner<C, F> {
|
|
||||||
pub fn new(c: C, f: F, period: Duration) -> Self {
|
|
||||||
Self {
|
|
||||||
task: f,
|
|
||||||
context: c,
|
|
||||||
period: Some(period),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub fn new_oneshot(c: C, f: F) -> Self {
|
|
||||||
Self {
|
|
||||||
task: f,
|
|
||||||
context: c,
|
|
||||||
period: None,
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
pub fn run(mut self) -> JoinHandle<()> {
|
|
||||||
if let Some(period) = self.period {
|
|
||||||
spawn(move || {
|
|
||||||
let mut pre_task;
|
|
||||||
let mut after_task;
|
|
||||||
loop {
|
|
||||||
pre_task = Instant::now();
|
|
||||||
(self.task)(&mut self.context);
|
|
||||||
after_task = Instant::now();
|
|
||||||
sleep(period - (after_task.duration_since(pre_task)));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
spawn(move || {
|
|
||||||
(self.task)(&mut self.context);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
use crate::file_util;
|
|
||||||
|
|
||||||
pub fn remove_similar_user_uploads(args: &mut crate::cli::Cli) {
|
|
||||||
log::info!("Starting user antispam task");
|
|
||||||
if let Err(e) = enforce_user_dirs(&args.folder, args.duplicates) {
|
|
||||||
log::error!("Error in user antispam task: {}", e);
|
|
||||||
}
|
|
||||||
log::info!("Completed user antispam task");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enforce_user_dirs(root: impl AsRef<std::path::Path>, keep_duplicates: usize) -> std::io::Result<()> {
|
|
||||||
let mut to_remove: Vec<community_settings_core::v1::Metadata> = Vec::new();
|
|
||||||
let mut seen_names = std::collections::HashMap::<String, Vec<community_settings_core::v1::Metadata>>::new();
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(crate::consts::SETTING_FOLDER)
|
|
||||||
.join(crate::consts::USER_ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
match dir_entry {
|
|
||||||
Ok(dir_entry) => {
|
|
||||||
log::info!("Scanning {} for user antispam", dir_entry.path().display());
|
|
||||||
if dir_entry.file_type()?.is_dir() {
|
|
||||||
seen_names.clear();
|
|
||||||
to_remove.clear();
|
|
||||||
for user_entry in dir_entry.path().read_dir()? {
|
|
||||||
match user_entry {
|
|
||||||
Ok(user_entry) => {
|
|
||||||
let f_path = user_entry.path();
|
|
||||||
if let Some(ext) = f_path.extension() {
|
|
||||||
if ext == crate::consts::RON_EXTENSION {
|
|
||||||
let reader = std::io::BufReader::new(std::fs::File::open(&f_path)?);
|
|
||||||
match ron::de::from_reader::<_, community_settings_core::v1::Metadata>(reader) {
|
|
||||||
Ok(x) => {
|
|
||||||
let sani_name = sanitise_name(&x.name);
|
|
||||||
if let Some(seen_in_ids) = seen_names.get_mut(&sani_name) {
|
|
||||||
seen_in_ids.push(x);
|
|
||||||
} else {
|
|
||||||
seen_names.insert(sani_name, vec![x]);
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
Err(e) => log::debug!("Error while reading {}: {}", f_path.display(), e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => log::warn!("Skipping file in {} for user antispam task due to error: {}", dir_entry.path().display(), e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for seen in seen_names.values_mut() {
|
|
||||||
seen.sort_by_key(|meta| meta.get_id()); // sort lowest to highest
|
|
||||||
// keep the highest id (i.e. latest uploaded) settings
|
|
||||||
for _ in 0..keep_duplicates {
|
|
||||||
seen.pop();
|
|
||||||
}
|
|
||||||
to_remove.append(seen);
|
|
||||||
}
|
|
||||||
log::info!("Found {} spammy entries in {}", to_remove.len(), dir_entry.path().display());
|
|
||||||
// remove settings (and related symlinks) from the filesystem
|
|
||||||
for meta in to_remove.iter() {
|
|
||||||
let filename_ron = file_util::filename(meta.get_id(), crate::consts::RON_EXTENSION);
|
|
||||||
let filename_json = file_util::filename(meta.get_id(), crate::consts::JSON_EXTENSION);
|
|
||||||
// delete tag symlinks
|
|
||||||
for tag in meta.tags.iter() {
|
|
||||||
let app_id_tag_folder = file_util::setting_folder_by_app_id_tag(root.as_ref(), meta.steam_app_id, tag);
|
|
||||||
let user_id_tag_folder = file_util::setting_folder_by_user_id_tag(root.as_ref(), meta.steam_user_id, tag);
|
|
||||||
let tag_folder = file_util::setting_folder_by_tag(root.as_ref(), tag);
|
|
||||||
let paths = [
|
|
||||||
app_id_tag_folder.join(&filename_json),
|
|
||||||
app_id_tag_folder.join(&filename_ron),
|
|
||||||
user_id_tag_folder.join(&filename_json),
|
|
||||||
user_id_tag_folder.join(&filename_ron),
|
|
||||||
tag_folder.join(&filename_json),
|
|
||||||
tag_folder.join(&filename_ron),
|
|
||||||
];
|
|
||||||
for path in paths {
|
|
||||||
if path.exists() {
|
|
||||||
if let Err(e) = std::fs::remove_file(&path) {
|
|
||||||
log::warn!("Failed to delete {}: {}", path.display(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// delete first-order symlinks and finally the actual files
|
|
||||||
let app_id_folder = file_util::setting_folder_by_app_id(root.as_ref(), meta.steam_app_id);
|
|
||||||
let user_id_folder = file_util::setting_folder_by_user_id(root.as_ref(), meta.steam_user_id);
|
|
||||||
let paths = [
|
|
||||||
app_id_folder.join(&filename_json),
|
|
||||||
app_id_folder.join(&filename_ron),
|
|
||||||
user_id_folder.join(&filename_json),
|
|
||||||
user_id_folder.join(&filename_ron),
|
|
||||||
file_util::setting_path_by_id(root.as_ref(), meta.get_id(), crate::consts::JSON_EXTENSION),
|
|
||||||
file_util::setting_path_by_id(root.as_ref(), meta.get_id(), crate::consts::RON_EXTENSION),
|
|
||||||
];
|
|
||||||
for path in paths {
|
|
||||||
if path.exists() {
|
|
||||||
if let Err(e) = std::fs::remove_file(&path) {
|
|
||||||
log::warn!("Failed to delete {}: {}", path.display(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::info!("Encountered non-folder path in user dir: {}", dir_entry.path().display());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => log::warn!("Skipping file for user antispam task due to error: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sanitise_name(name: &str) -> String {
|
|
||||||
name.trim().to_lowercase()
|
|
||||||
}
|
|
|
@ -1,217 +0,0 @@
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::consts::*;
|
|
||||||
|
|
||||||
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 rebuild_symlinks(root: impl AsRef<Path>) -> std::io::Result<()> {
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.file_type()?.is_file() {
|
|
||||||
let f_path = dir_entry.path();
|
|
||||||
if let Some(ext) = f_path.extension() {
|
|
||||||
if ext == RON_EXTENSION {
|
|
||||||
let reader = std::io::BufReader::new(std::fs::File::open(&f_path)?);
|
|
||||||
let setting: community_settings_core::v1::Metadata = match ron::de::from_reader(reader) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => {
|
|
||||||
log::debug!("Error while reading {}: {}", f_path.display(), e);
|
|
||||||
let e_msg = format!("{}", e);
|
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let to_symlink = crate::file_util::symlinks(&root, &setting)?;
|
|
||||||
let path_ron_canon = f_path.canonicalize()?;
|
|
||||||
let path_json_canon = crate::file_util::setting_path_by_id(&root, setting.get_id(), JSON_EXTENSION).canonicalize()?;
|
|
||||||
for ron_link in to_symlink.ron {
|
|
||||||
if ron_link.exists() { continue; }
|
|
||||||
log::info!("Rebuilding symlink {} -> {}", ron_link.display(), path_ron_canon.display());
|
|
||||||
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
|
||||||
{
|
|
||||||
std::os::windows::fs::symlink_file(&path_ron_canon, &ron_link)?;
|
|
||||||
}
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
std::os::unix::fs::symlink(&path_ron_canon, &ron_link)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for json_link in to_symlink.json {
|
|
||||||
if json_link.exists() { continue; }
|
|
||||||
log::info!("Rebuilding symlink {} -> {}", json_link.display(), path_json_canon.display());
|
|
||||||
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
|
||||||
{
|
|
||||||
std::os::windows::fs::symlink_file(&path_json_canon, json_link)?;
|
|
||||||
}
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
std::os::unix::fs::symlink(&path_json_canon, json_link)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fix_symlinks(root: impl AsRef<Path>) -> std::io::Result<()> {
|
|
||||||
log::info!("root setttings folder: {} aka {} (absolute)", root.as_ref().display(), root.as_ref().canonicalize()?.display());
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(APP_ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.file_type()?.is_dir() {
|
|
||||||
make_symlinks_absolute_in_dir(root.as_ref(), dir_entry.path())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(USER_ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.file_type()?.is_dir() {
|
|
||||||
make_symlinks_absolute_in_dir(root.as_ref(), dir_entry.path())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(TAG_FOLDER).read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.file_type()?.is_dir() {
|
|
||||||
make_symlinks_absolute_in_dir(root.as_ref(), dir_entry.path())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_symlinks_absolute_in_dir(root: impl AsRef<Path>, dir: impl AsRef<Path>) -> std::io::Result<()> {
|
|
||||||
let abs_root = root.as_ref().canonicalize()?;
|
|
||||||
assert!(abs_root.is_absolute());
|
|
||||||
for dir_entry in dir.as_ref()
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.file_type()?.is_symlink() {
|
|
||||||
let path = dir_entry.path();
|
|
||||||
let link_path = path.read_link()?;
|
|
||||||
if !link_path.is_absolute() {
|
|
||||||
let new_link = abs_root.join(
|
|
||||||
link_path.strip_prefix(&root).expect("Symlinked path does not begin with root settings folder")
|
|
||||||
);
|
|
||||||
log::info!("Fixing {} -> {} to -> {}", path.display(), link_path.display(), new_link.display());
|
|
||||||
std::fs::remove_file(&path)?;
|
|
||||||
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
|
||||||
{
|
|
||||||
std::os::windows::fs::symlink_file(new_link, &path)?;
|
|
||||||
}
|
|
||||||
#[cfg(target_family = "unix")]
|
|
||||||
{
|
|
||||||
std::os::unix::fs::symlink(new_link, &path)?;
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
log::info!("Found already-absolute symlink {} -> {}", path.display(), link_path.display());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::info!("Found non-symlink {}: {:?}", dir_entry.path().display(), dir_entry.file_type()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sync_ids(root: impl AsRef<Path>) -> std::io::Result<()> {
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
let f_path = dir_entry.path();
|
|
||||||
if let Some(ext) = f_path.extension() {
|
|
||||||
let id = f_path.file_stem().map(|os| os.to_string_lossy().to_string()).unwrap();
|
|
||||||
if ext == RON_EXTENSION {
|
|
||||||
let reader = std::io::BufReader::new(std::fs::File::open(&f_path)?);
|
|
||||||
let mut setting: community_settings_core::v1::Metadata = match ron::de::from_reader(reader) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => {
|
|
||||||
log::debug!("Error while reading {}: {}", f_path.display(), e);
|
|
||||||
let e_msg = format!("{}", e);
|
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if setting.id != id {
|
|
||||||
setting.id = id;
|
|
||||||
ron::ser::to_writer(std::fs::File::create(&f_path)?, &setting).unwrap();
|
|
||||||
}
|
|
||||||
} else if ext == JSON_EXTENSION {
|
|
||||||
let reader = std::io::BufReader::new(std::fs::File::open(&f_path)?);
|
|
||||||
let mut setting: community_settings_core::v1::Metadata = match serde_json::from_reader(reader) {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(e) => {
|
|
||||||
log::debug!("Error while reading {}: {}", f_path.display(), e);
|
|
||||||
let e_msg = format!("{}", e);
|
|
||||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e_msg));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if setting.id != id {
|
|
||||||
setting.id = id;
|
|
||||||
serde_json::to_writer(std::fs::File::create(&f_path)?, &setting).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_tag_subfolders(root: impl AsRef<Path>) -> std::io::Result<()> {
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(USER_ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.metadata()?.is_dir() {
|
|
||||||
let tag_folder = dir_entry.path().join(TAG_FOLDER);
|
|
||||||
if !tag_folder.exists() {
|
|
||||||
std::fs::create_dir(&tag_folder)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for dir_entry in root.as_ref()
|
|
||||||
.join(SETTING_FOLDER)
|
|
||||||
.join(APP_ID_FOLDER)
|
|
||||||
.read_dir()? {
|
|
||||||
let dir_entry = dir_entry?;
|
|
||||||
if dir_entry.metadata()?.is_dir() {
|
|
||||||
let tag_folder = dir_entry.path().join(TAG_FOLDER);
|
|
||||||
if !tag_folder.exists() {
|
|
||||||
std::fs::create_dir(&tag_folder)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO populate folders
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -5,4 +5,4 @@ echo "Container's IP address: `awk 'END{print $1}' /etc/hosts`"
|
||||||
|
|
||||||
cd /backend
|
cd /backend
|
||||||
|
|
||||||
bash build-docker.sh
|
sudo bash build-docker.sh
|
|
@ -34,8 +34,8 @@ impl GenericBatteryLimit {
|
||||||
fn default_steam_deck() -> Self {
|
fn default_steam_deck() -> Self {
|
||||||
Self {
|
Self {
|
||||||
charge_rate: Some(RangeLimit {
|
charge_rate: Some(RangeLimit {
|
||||||
min: Some(0),
|
min: Some(250),
|
||||||
max: Some(100),
|
max: Some(2500),
|
||||||
}),
|
}),
|
||||||
charge_modes: vec![
|
charge_modes: vec![
|
||||||
"normal".to_owned(),
|
"normal".to_owned(),
|
||||||
|
|
|
@ -612,8 +612,8 @@
|
||||||
"provider": "GabeBoy",
|
"provider": "GabeBoy",
|
||||||
"limits": {
|
"limits": {
|
||||||
"charge_rate": {
|
"charge_rate": {
|
||||||
"min": 0,
|
"min": 250,
|
||||||
"max": 100
|
"max": 2500
|
||||||
},
|
},
|
||||||
"charge_modes": [
|
"charge_modes": [
|
||||||
"normal",
|
"normal",
|
||||||
|
@ -878,8 +878,8 @@
|
||||||
"provider": "GabeBoySP",
|
"provider": "GabeBoySP",
|
||||||
"limits": {
|
"limits": {
|
||||||
"charge_rate": {
|
"charge_rate": {
|
||||||
"min": 0,
|
"min": 250,
|
||||||
"max": 100
|
"max": 2500
|
||||||
},
|
},
|
||||||
"charge_modes": [
|
"charge_modes": [
|
||||||
"normal",
|
"normal",
|
||||||
|
|
|
@ -32,30 +32,6 @@ pub enum ApiMessage {
|
||||||
UploadCurrentVariant(String, String), // SteamID, Steam username
|
UploadCurrentVariant(String, String), // SteamID, Steam username
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for ApiMessage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Battery(x) => write!(f, "Battery;{}", x),
|
|
||||||
Self::Cpu(x) => write!(f, "Cpu;{}", x),
|
|
||||||
Self::Gpu(x) => write!(f, "Gpu;{}", x),
|
|
||||||
Self::General(x) => write!(f, "General;{}", x),
|
|
||||||
Self::OnResume => write!(f, "OnResume"),
|
|
||||||
Self::OnPluggedIn => write!(f, "OnPluggedIn"),
|
|
||||||
Self::OnUnplugged => write!(f, "OnUnplugged"),
|
|
||||||
Self::OnChargeChange(x) => write!(f, "OnChargeChange({:?})", x),
|
|
||||||
Self::PowerVibeCheck => write!(f, "PowerVibeCheck"),
|
|
||||||
Self::WaitForEmptyQueue(_) => write!(f, "WaitForEmptyQueue"),
|
|
||||||
Self::LoadSettings(path, name, variant, variant_name) => write!(f, "LoadSettings({}, {}, {}, {})", path, name, variant, variant_name),
|
|
||||||
Self::LoadVariant(variant, variant_name) => write!(f, "LoadVariant({}, {})", variant, variant_name),
|
|
||||||
Self::LoadMainSettings => write!(f, "LoadMainSettings"),
|
|
||||||
Self::LoadSystemSettings => write!(f, "LoadSystemSettings"),
|
|
||||||
Self::GetLimits(_) => write!(f, "GetLimits"),
|
|
||||||
Self::GetProvider(s, _) => write!(f, "GetProvider({})", s),
|
|
||||||
Self::UploadCurrentVariant(id, user) => write!(f, "UploadCurrentVariant(id: {}, user: {})", id, user),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum BatteryMessage {
|
pub enum BatteryMessage {
|
||||||
SetChargeRate(Option<u64>),
|
SetChargeRate(Option<u64>),
|
||||||
GetChargeRate(Callback<Option<u64>>),
|
GetChargeRate(Callback<Option<u64>>),
|
||||||
|
@ -70,24 +46,6 @@ pub enum BatteryMessage {
|
||||||
GetChargeLimit(Callback<Option<f64>>),
|
GetChargeLimit(Callback<Option<f64>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for BatteryMessage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SetChargeRate(x) => write!(f, "SetChargeRate({:?})", x),
|
|
||||||
Self::GetChargeRate(_) => write!(f, "GetChargeRate"),
|
|
||||||
Self::SetChargeMode(x) => write!(f, "SetChargeMode({:?})", x),
|
|
||||||
Self::GetChargeMode(_) => write!(f, "GetChargeMode"),
|
|
||||||
Self::ReadChargeFull(_) => write!(f, "ReadChargeFull"),
|
|
||||||
Self::ReadChargeNow(_) => write!(f, "ReadChargeNow"),
|
|
||||||
Self::ReadChargeDesign(_) => write!(f, "ReadChargeDesign"),
|
|
||||||
Self::ReadCurrentNow(_) => write!(f, "ReadCurrentNow"),
|
|
||||||
Self::ReadChargePower(_) => write!(f, "ReadChargePower"),
|
|
||||||
Self::SetChargeLimit(x) => write!(f, "SetChargeLimit({:?})", x),
|
|
||||||
Self::GetChargeLimit(_) => write!(f, "GetChargeLimit"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BatteryMessage {
|
impl BatteryMessage {
|
||||||
fn process(self, settings: &mut dyn TBattery) -> bool {
|
fn process(self, settings: &mut dyn TBattery) -> bool {
|
||||||
let dirty = self.is_modify();
|
let dirty = self.is_modify();
|
||||||
|
@ -129,23 +87,6 @@ pub enum CpuMessage {
|
||||||
GetCpusGovernor(Callback<Vec<String>>),
|
GetCpusGovernor(Callback<Vec<String>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for CpuMessage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SetCpuOnline(i, x) => write!(f, "SetCpuOnline({}, {})", i, x),
|
|
||||||
Self::SetCpusOnline(x) => write!(f, "SetCpusOnline({:?})", x),
|
|
||||||
Self::SetSmt(x, _) => write!(f, "SetChargeMode({})", x),
|
|
||||||
Self::GetSmt(_) => write!(f, "GetSmt"),
|
|
||||||
Self::GetCpusOnline(_) => write!(f, "GetCpusOnline"),
|
|
||||||
Self::SetClockLimits(x, y) => write!(f, "SetClockLimits({}, {:?})", x, y),
|
|
||||||
Self::GetClockLimits(x, _) => write!(f, "GetClockLimits({})", x),
|
|
||||||
Self::SetCpuGovernor(i, x) => write!(f, "SetCpuGovernor({}, {})", i, x),
|
|
||||||
Self::SetCpusGovernor(x) => write!(f, "SetCpusGovernor({:?})", x),
|
|
||||||
Self::GetCpusGovernor(_) => write!(f, "GetCpusGovernor"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CpuMessage {
|
impl CpuMessage {
|
||||||
fn process(self, settings: &mut dyn TCpus) -> bool {
|
fn process(self, settings: &mut dyn TCpus) -> bool {
|
||||||
let dirty = self.is_modify();
|
let dirty = self.is_modify();
|
||||||
|
@ -265,19 +206,6 @@ pub enum GpuMessage {
|
||||||
GetMemoryClock(Callback<Option<u64>>),
|
GetMemoryClock(Callback<Option<u64>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for GpuMessage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SetPpt(x, y) => write!(f, "SetPpt(fast {:?}, slow {:?})", x, y),
|
|
||||||
Self::GetPpt(_) => write!(f, "GetPpt"),
|
|
||||||
Self::SetClockLimits(x) => write!(f, "SetClockLimits({:?})", x),
|
|
||||||
Self::GetClockLimits(_) => write!(f, "GetClockLimits"),
|
|
||||||
Self::SetMemoryClock(x) => write!(f, "SetMemoryClock({:?})", x),
|
|
||||||
Self::GetMemoryClock(_) => write!(f, "GetMemoryClock"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GpuMessage {
|
impl GpuMessage {
|
||||||
fn process(self, settings: &mut dyn TGpu) -> bool {
|
fn process(self, settings: &mut dyn TGpu) -> bool {
|
||||||
let dirty = self.is_modify();
|
let dirty = self.is_modify();
|
||||||
|
@ -314,21 +242,6 @@ pub enum GeneralMessage {
|
||||||
ApplyNow,
|
ApplyNow,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Display for GeneralMessage {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::SetPersistent(x) => write!(f, "SetPersistent({})", x),
|
|
||||||
Self::GetPersistent(_) => write!(f, "GetPersistent"),
|
|
||||||
Self::GetCurrentProfileName(_) => write!(f, "GetCurrentProfileName"),
|
|
||||||
Self::GetPath(_) => write!(f, "GetPath"),
|
|
||||||
Self::GetCurrentVariant(_) => write!(f, "GetCurrentVariant"),
|
|
||||||
Self::GetAllVariants(_) => write!(f, "GetAllVariants"),
|
|
||||||
Self::AddVariant(variant, _) => write!(f, "AddVariant(name: `{}` [...])", variant.name),
|
|
||||||
Self::ApplyNow => write!(f, "ApplyNow"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GeneralMessage {
|
impl GeneralMessage {
|
||||||
fn process(self, settings: &mut dyn TGeneral) -> bool {
|
fn process(self, settings: &mut dyn TGeneral) -> bool {
|
||||||
let dirty = self.is_modify();
|
let dirty = self.is_modify();
|
||||||
|
@ -372,31 +285,20 @@ fn print_errors(call_name: &str, errors: Vec<crate::settings::SettingError>) {
|
||||||
log::error!("Settings {}() err:\n{}", call_name, err_list);
|
log::error!("Settings {}() err:\n{}", call_name, err_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_messages(msgs: &Vec<String>) {
|
|
||||||
let mut log_msg = String::new();
|
|
||||||
for msg in msgs.iter() {
|
|
||||||
//use core::fmt::Write;
|
|
||||||
write!(log_msg, "{}, ", msg).unwrap();
|
|
||||||
}
|
|
||||||
log::info!("Processed messages: [{}]", log_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApiMessageHandler {
|
impl ApiMessageHandler {
|
||||||
pub fn process_forever(&mut self, settings: &mut Settings) {
|
pub fn process_forever(&mut self, settings: &mut Settings) {
|
||||||
crate::utility::ioperm_power_ec();
|
crate::utility::ioperm_power_ec();
|
||||||
let mut dirty_echo = true; // set everything twice, to make sure PowerTools wins on race conditions
|
//let mut dirty_echo = true; // set everything twice, to make sure PowerTools wins on race conditions
|
||||||
while let Ok(msg) = self.intake.recv() {
|
while let Ok(msg) = self.intake.recv() {
|
||||||
let mut messages = vec![msg.to_string()]; // keep messages for logging
|
|
||||||
let mut dirty = self.process(settings, msg);
|
let mut dirty = self.process(settings, msg);
|
||||||
while let Ok(msg) = self.intake.try_recv() {
|
while let Ok(msg) = self.intake.try_recv() {
|
||||||
messages.push(msg.to_string());
|
|
||||||
dirty |= self.process(settings, msg);
|
dirty |= self.process(settings, msg);
|
||||||
}
|
}
|
||||||
if dirty
|
if dirty
|
||||||
|| dirty_echo
|
/*|| dirty_echo */
|
||||||
{
|
{
|
||||||
dirty_echo = dirty; // echo only once
|
//dirty_echo = dirty; // echo only once
|
||||||
print_messages(&messages);
|
|
||||||
// run on_set
|
// run on_set
|
||||||
if let Err(e) = settings.on_set() {
|
if let Err(e) = settings.on_set() {
|
||||||
print_errors("on_set", e);
|
print_errors("on_set", e);
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
|
||||||
use sysfuss::capability::attributes;
|
use sysfuss::capability::attributes;
|
||||||
use sysfuss::{
|
use sysfuss::{
|
||||||
HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, PowerSupplyAttribute,
|
HwMonAttribute, HwMonAttributeItem, HwMonAttributeType, HwMonPath, PowerSupplyAttribute,
|
||||||
PowerSupplyPath, SysEntity, SysEntityAttributesExt, SysAttributeExt,
|
PowerSupplyPath, SysAttribute, SysEntity, SysEntityAttributesExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use limits_core::json_v2::GenericBatteryLimit;
|
use limits_core::json_v2::GenericBatteryLimit;
|
||||||
|
@ -230,12 +230,12 @@ const HWMON_NEEDS: &[HwMonAttribute] = &[
|
||||||
];
|
];
|
||||||
|
|
||||||
const MAX_BATTERY_CHARGE_RATE_ATTR: HwMonAttribute =
|
const MAX_BATTERY_CHARGE_RATE_ATTR: HwMonAttribute =
|
||||||
HwMonAttribute::custom("max_battery_charge_rate");
|
HwMonAttribute::custom("maximum_battery_charge_rate");
|
||||||
const MAX_BATTERY_CHARGE_LEVEL_ATTR: HwMonAttribute =
|
const MAX_BATTERY_CHARGE_LEVEL_ATTR: HwMonAttribute =
|
||||||
HwMonAttribute::custom("max_battery_charge_level");
|
HwMonAttribute::custom("max_battery_charge_level");
|
||||||
|
|
||||||
const MAX_CHARGE_RATE: u64 = 100;
|
const MAX_CHARGE_RATE: u64 = 2500;
|
||||||
const MIN_CHARGE_RATE: u64 = 0;
|
const MIN_CHARGE_RATE: u64 = 250;
|
||||||
|
|
||||||
impl Battery {
|
impl Battery {
|
||||||
fn find_battery_sysfs(root: Option<impl AsRef<std::path::Path>>) -> PowerSupplyPath {
|
fn find_battery_sysfs(root: Option<impl AsRef<std::path::Path>>) -> PowerSupplyPath {
|
||||||
|
@ -325,7 +325,7 @@ impl Battery {
|
||||||
self.state.charge_rate_set = true;
|
self.state.charge_rate_set = true;
|
||||||
let path = MAX_BATTERY_CHARGE_RATE_ATTR.path(&*self.sysfs_hwmon);
|
let path = MAX_BATTERY_CHARGE_RATE_ATTR.path(&*self.sysfs_hwmon);
|
||||||
self.sysfs_hwmon
|
self.sysfs_hwmon
|
||||||
.set(MAX_BATTERY_CHARGE_RATE_ATTR, format!("{}\n", charge_rate))
|
.set(MAX_BATTERY_CHARGE_RATE_ATTR, charge_rate)
|
||||||
.map_err(|e| SettingError {
|
.map_err(|e| SettingError {
|
||||||
msg: format!("Failed to write to `{}`: {}", path.display(), e),
|
msg: format!("Failed to write to `{}`: {}", path.display(), e),
|
||||||
setting: crate::settings::SettingVariant::Battery,
|
setting: crate::settings::SettingVariant::Battery,
|
||||||
|
@ -336,10 +336,10 @@ impl Battery {
|
||||||
self.sysfs_hwmon
|
self.sysfs_hwmon
|
||||||
.set(
|
.set(
|
||||||
MAX_BATTERY_CHARGE_RATE_ATTR,
|
MAX_BATTERY_CHARGE_RATE_ATTR,
|
||||||
format!("{}\n", self.limits
|
self.limits
|
||||||
.charge_rate
|
.charge_rate
|
||||||
.and_then(|lim| lim.max)
|
.and_then(|lim| lim.max)
|
||||||
.unwrap_or(100)),
|
.unwrap_or(2500),
|
||||||
)
|
)
|
||||||
.map_err(|e| SettingError {
|
.map_err(|e| SettingError {
|
||||||
msg: format!("Failed to write to `{}`: {}", path.display(), e),
|
msg: format!("Failed to write to `{}`: {}", path.display(), e),
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
|
|
||||||
use sysfuss::{
|
use sysfuss::{
|
||||||
capability::attributes, BasicEntityPath, HwMonPath, SysEntity, SysEntityAttributesExt, SysAttributeExt,
|
capability::attributes, BasicEntityPath, HwMonPath, SysAttribute, SysEntity,
|
||||||
|
SysEntityAttributes, SysEntityAttributesExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
use limits_core::json_v2::GenericGpuLimit;
|
use limits_core::json_v2::GenericGpuLimit;
|
||||||
|
@ -150,7 +151,7 @@ impl Gpu {
|
||||||
if let super::Model::OLED = self.variant {
|
if let super::Model::OLED = self.variant {
|
||||||
if let Ok(f) = self
|
if let Ok(f) = self
|
||||||
.sysfs_card
|
.sysfs_card
|
||||||
.read_value(&GPU_CLOCK_READOUT_ATTRIBUTE.to_owned())
|
.read_value(GPU_CLOCK_READOUT_ATTRIBUTE.to_owned())
|
||||||
{
|
{
|
||||||
let options = parse_pp_dpm_sclk(&String::from_utf8_lossy(&f));
|
let options = parse_pp_dpm_sclk(&String::from_utf8_lossy(&f));
|
||||||
return options
|
return options
|
||||||
|
@ -191,7 +192,7 @@ impl Gpu {
|
||||||
fn quantize_memory_clock(&self, clock: u64) -> u64 {
|
fn quantize_memory_clock(&self, clock: u64) -> u64 {
|
||||||
if let Ok(f) = self
|
if let Ok(f) = self
|
||||||
.sysfs_card
|
.sysfs_card
|
||||||
.read_value(&GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned())
|
.read_value(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned())
|
||||||
{
|
{
|
||||||
let options = parse_pp_dpm_fclk(&String::from_utf8_lossy(&f));
|
let options = parse_pp_dpm_fclk(&String::from_utf8_lossy(&f));
|
||||||
// round (and find) nearest valid clock step
|
// round (and find) nearest valid clock step
|
||||||
|
@ -237,7 +238,7 @@ impl Gpu {
|
||||||
{
|
{
|
||||||
let options_count = self
|
let options_count = self
|
||||||
.sysfs_card
|
.sysfs_card
|
||||||
.read_value(&GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned())
|
.read_value(GPU_MEMORY_DOWNCLOCK_ATTRIBUTE.to_owned())
|
||||||
.map(|b| parse_pp_dpm_fclk(&String::from_utf8_lossy(&b)).len())
|
.map(|b| parse_pp_dpm_fclk(&String::from_utf8_lossy(&b)).len())
|
||||||
.unwrap_or_else(|_| if is_oled { 4 } else { 2 });
|
.unwrap_or_else(|_| if is_oled { 4 } else { 2 });
|
||||||
let modifier = (options_count - 1) as u64;
|
let modifier = (options_count - 1) as u64;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
use sysfuss::{BasicEntityPath, SysEntityAttributesExt, SysAttributeExt};
|
use sysfuss::{BasicEntityPath, SysAttribute, SysEntityAttributesExt};
|
||||||
|
|
||||||
use crate::settings::SettingError;
|
use crate::settings::SettingError;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::sync::Mutex;
|
|
||||||
|
|
||||||
pub const JUPITER_HWMON_NAME: &'static str = "jupiter";
|
pub const JUPITER_HWMON_NAME: &'static str = "jupiter";
|
||||||
pub const STEAMDECK_HWMON_NAME: &'static str = "steamdeck_hwmon";
|
pub const STEAMDECK_HWMON_NAME: &'static str = "steamdeck_hwmon";
|
||||||
pub const GPU_HWMON_NAME: &'static str = "amdgpu";
|
pub const GPU_HWMON_NAME: &'static str = "amdgpu";
|
||||||
|
|
||||||
pub static THING_EC: Mutex<smokepatio::ec::unnamed_power::UnnamedPowerEC> = Mutex::new(smokepatio::ec::unnamed_power::UnnamedPowerEC::new());
|
|
||||||
|
|
||||||
pub fn range_min_or_fallback<I: Copy>(
|
pub fn range_min_or_fallback<I: Copy>(
|
||||||
range: &Option<limits_core::json_v2::RangeLimit<I>>,
|
range: &Option<limits_core::json_v2::RangeLimit<I>>,
|
||||||
fallback: I,
|
fallback: I,
|
||||||
|
@ -29,25 +25,23 @@ pub fn card_also_has(card: &dyn sysfuss::SysEntity, extensions: &'static [&'stat
|
||||||
}
|
}
|
||||||
|
|
||||||
const THINGS: &[u8] = &[
|
const THINGS: &[u8] = &[
|
||||||
0, 0, 0,
|
|
||||||
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
|
1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0,
|
||||||
0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1,
|
0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1,
|
||||||
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
|
1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0,
|
||||||
0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
const TIME_UNIT: std::time::Duration = std::time::Duration::from_millis(250);
|
const TIME_UNIT: std::time::Duration = std::time::Duration::from_millis(200);
|
||||||
|
|
||||||
pub fn flash_led() {
|
pub fn flash_led() {
|
||||||
use smokepatio::ec::ControllerSet;
|
use smokepatio::ec::ControllerSet;
|
||||||
|
let mut ec = smokepatio::ec::unnamed_power::UnnamedPowerEC::new();
|
||||||
let mut ec = THING_EC.lock().unwrap();
|
|
||||||
for &code in THINGS {
|
for &code in THINGS {
|
||||||
let on = code != 0;
|
let on = code != 0;
|
||||||
let colour = if on {
|
let colour = if on {
|
||||||
smokepatio::ec::unnamed_power::StaticColour::Red
|
smokepatio::ec::unnamed_power::StaticColour::Red
|
||||||
} else {
|
} else {
|
||||||
smokepatio::ec::unnamed_power::StaticColour::Disabled
|
smokepatio::ec::unnamed_power::StaticColour::Off
|
||||||
};
|
};
|
||||||
if let Err(e) = ec.set(colour) {
|
if let Err(e) = ec.set(colour) {
|
||||||
log::error!("Thing err: {}", e);
|
log::error!("Thing err: {}", e);
|
||||||
|
|
|
@ -69,8 +69,7 @@ pub fn chown_settings_dir() -> std::io::Result<()> {
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or(1000);
|
.unwrap_or(1000);
|
||||||
log::info!(
|
log::info!(
|
||||||
"chmod/chown {} for user `{}` ({})",
|
"chmod/chown ~/.config/powertools for user `{}` ({})",
|
||||||
dir.display(),
|
|
||||||
deck_user,
|
deck_user,
|
||||||
uid
|
uid
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "PowerTools",
|
"name": "PowerTools",
|
||||||
"version": "2.0.3",
|
"version": "2.0.0",
|
||||||
"description": "Power tweaks for power users",
|
"description": "Power tweaks for power users",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "shx rm -rf dist && rollup -c",
|
"build": "shx rm -rf dist && rollup -c",
|
||||||
|
|
|
@ -85,7 +85,7 @@ export class Battery extends Component<backend.IdcProps> {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{ get_value(CHARGE_RATE_BATT) != null && <SliderField
|
{ get_value(CHARGE_RATE_BATT) != null && <SliderField
|
||||||
label={tr("Maximum (%)")}
|
label={tr("Maximum (mA)")}
|
||||||
value={get_value(CHARGE_RATE_BATT)}
|
value={get_value(CHARGE_RATE_BATT)}
|
||||||
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.max}
|
max={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.max}
|
||||||
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.min}
|
min={(get_value(LIMITS_INFO) as backend.SettingsLimits).battery.charge_current!.min}
|
||||||
|
|
|
@ -364,7 +364,6 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
|
||||||
(ok: boolean) => {
|
(ok: boolean) => {
|
||||||
backend.log(backend.LogLevel.Debug, "New settings variant ok? " + ok);
|
backend.log(backend.LogLevel.Debug, "New settings variant ok? " + ok);
|
||||||
reload();
|
reload();
|
||||||
isVariantLoading = false;
|
|
||||||
backend.resolve(backend.waitForComplete(), (_) => {
|
backend.resolve(backend.waitForComplete(), (_) => {
|
||||||
backend.log(backend.LogLevel.Debug, "Trying to tell UI to re-render due to new settings variant");
|
backend.log(backend.LogLevel.Debug, "Trying to tell UI to re-render due to new settings variant");
|
||||||
tryNotifyProfileChange();
|
tryNotifyProfileChange();
|
||||||
|
|
|
@ -40,22 +40,18 @@ export class StoreResultsPage extends Component<{onNewVariant: () => void}> {
|
||||||
{ tr("No results") /* TODO translate */ }
|
{ tr("No results") /* TODO translate */ }
|
||||||
</Focusable>);
|
</Focusable>);
|
||||||
} else {
|
} else {
|
||||||
return (
|
// TODO
|
||||||
<div style={{
|
return (<Focusable
|
||||||
marginTop: "40px",
|
style={{
|
||||||
marginBottom: "40px",
|
display: "flex",
|
||||||
overflowY: "scroll",
|
flexWrap: "wrap",
|
||||||
}}>
|
justifyContent: "center",
|
||||||
<Focusable
|
rowGap: "5px",
|
||||||
style={{
|
columnGap: "5px",
|
||||||
display: "flex",
|
maxWidth: "100%",
|
||||||
flexWrap: "wrap",
|
margin: "2em 0.5em",
|
||||||
justifyContent: "center",
|
}}
|
||||||
rowGap: "0.5em",
|
>
|
||||||
columnGap: "0.5em",
|
|
||||||
maxWidth: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
storeItems.map((meta: backend.StoreMetadata) => (<PanelSectionRow>
|
storeItems.map((meta: backend.StoreMetadata) => (<PanelSectionRow>
|
||||||
<Focusable style={{
|
<Focusable style={{
|
||||||
|
@ -153,8 +149,7 @@ export class StoreResultsPage extends Component<{onNewVariant: () => void}> {
|
||||||
</Focusable>
|
</Focusable>
|
||||||
</PanelSectionRow>))
|
</PanelSectionRow>))
|
||||||
}
|
}
|
||||||
</Focusable>
|
</Focusable>);
|
||||||
</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
Binary file not shown.
|
@ -1,16 +0,0 @@
|
||||||
# TEMPLATE TITLE.
|
|
||||||
# Copyright (C) 2024 NGnius
|
|
||||||
# This file is distributed under the same license as the PowerTools package.
|
|
||||||
# NGnius (Graham) <ngniusness@gmail.com>, 2024.
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: v1.1\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://git.ngni.us/NG-SD-Plugins/PowerTools/PowerTools/issues\n"
|
|
||||||
"POT-Creation-Date: 2023-01-09 19:52-0500\n"
|
|
||||||
"PO-Revision-Date: 2024-05-07 18:42-0500\n"
|
|
||||||
"Last-Translator: \n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"Language: en-US\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
|
@ -5,7 +5,7 @@
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: v1.1\n"
|
"Project-Id-Version: v1.1\n"
|
||||||
"Report-Msgid-Bugs-To: https://git.ngni.us/NG-SD-Plugins/PowerTools/issues\n"
|
"Report-Msgid-Bugs-To: https://github.com/NGnius/PowerTools/issues\n"
|
||||||
"POT-Creation-Date: 2023-01-09 19:52-0500\n"
|
"POT-Creation-Date: 2023-01-09 19:52-0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
|
Loading…
Reference in a new issue