Compare commits
3 commits
9d452acc28
...
6c28cef9b3
Author | SHA1 | Date | |
---|---|---|---|
6c28cef9b3 | |||
c1064fdf92 | |||
81b9107bf5 |
13 changed files with 297 additions and 168 deletions
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
|
@ -1170,7 +1170,7 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powertools"
|
name = "powertools"
|
||||||
version = "2.0.0-beta2"
|
version = "2.0.1-beta1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "powertools"
|
name = "powertools"
|
||||||
version = "2.0.0-beta2"
|
version = "2.0.1-beta1"
|
||||||
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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -60,7 +60,7 @@ 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 == file_util::RON_EXTENSION).unwrap_or(false))
|
.filter(|f| f.path().extension().map(|ext| ext == crate::consts::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.modified().ok().map(|time| (f, meta, time)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -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, file_util::RON_EXTENSION);
|
let path = file_util::setting_path_by_id(&cli.folder, id, crate::consts::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, file_util::JSON_EXTENSION);
|
let path = file_util::setting_path_by_id(&cli.folder, id, crate::consts::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) {
|
||||||
|
|
|
@ -46,14 +46,14 @@ pub async fn save_setting_handler(
|
||||||
parsed_data.id = next_id.to_string();
|
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, file_util::RON_EXTENSION);
|
let path_ron = file_util::setting_path_by_id(&cli.folder, next_id, crate::consts::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, file_util::JSON_EXTENSION);
|
let path_json = file_util::setting_path_by_id(&cli.folder, next_id, crate::consts::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);
|
||||||
|
@ -67,14 +67,15 @@ pub async fn save_setting_handler(
|
||||||
log::debug!("Saved to {}, building symlinks", path_ron.display());
|
log::debug!("Saved to {}, building symlinks", path_ron.display());
|
||||||
|
|
||||||
// create symlinks for other ways of looking up these settings files
|
// create symlinks for other ways of looking up these settings files
|
||||||
let filename_ron = file_util::filename(next_id, file_util::RON_EXTENSION);
|
let filename_ron = file_util::filename(next_id, crate::consts::RON_EXTENSION);
|
||||||
let filename_json = file_util::filename(next_id, file_util::JSON_EXTENSION);
|
let filename_json = file_util::filename(next_id, crate::consts::JSON_EXTENSION);
|
||||||
|
|
||||||
// create symlinks to app id folder
|
// 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 app_id_folder = file_util::setting_folder_by_app_id(&cli.folder, parsed_data.steam_app_id);
|
||||||
log::debug!("App id folder {}", app_id_folder.display());
|
log::debug!("App id folder {}", app_id_folder.display());
|
||||||
if !app_id_folder.exists() {
|
if !app_id_folder.exists() {
|
||||||
std::fs::create_dir(&app_id_folder)?;
|
std::fs::create_dir(&app_id_folder)?;
|
||||||
|
std::fs::create_dir(file_util::setting_tag_folder_by_app_id(&cli.folder, parsed_data.steam_app_id))?;
|
||||||
}
|
}
|
||||||
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
||||||
{
|
{
|
||||||
|
@ -93,6 +94,7 @@ pub async fn save_setting_handler(
|
||||||
let user_id_folder = file_util::setting_folder_by_user_id(&cli.folder, parsed_data.steam_user_id);
|
let user_id_folder = file_util::setting_folder_by_user_id(&cli.folder, parsed_data.steam_user_id);
|
||||||
if !user_id_folder.exists() {
|
if !user_id_folder.exists() {
|
||||||
std::fs::create_dir(&user_id_folder)?;
|
std::fs::create_dir(&user_id_folder)?;
|
||||||
|
std::fs::create_dir(file_util::setting_tag_folder_by_user_id(&cli.folder, parsed_data.steam_user_id))?;
|
||||||
}
|
}
|
||||||
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
||||||
{
|
{
|
||||||
|
@ -109,6 +111,10 @@ pub async fn save_setting_handler(
|
||||||
|
|
||||||
// create symlinks for each tag
|
// create symlinks for each tag
|
||||||
for tag in parsed_data.tags.iter() {
|
for tag in parsed_data.tags.iter() {
|
||||||
|
if !str_is_alphanumeric_or_space(&tag){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// create symlinks for general tag folder
|
||||||
let tag_folder = file_util::setting_folder_by_tag(&cli.folder, tag);
|
let tag_folder = file_util::setting_folder_by_tag(&cli.folder, tag);
|
||||||
if !tag_folder.exists() {
|
if !tag_folder.exists() {
|
||||||
std::fs::create_dir(&tag_folder)?;
|
std::fs::create_dir(&tag_folder)?;
|
||||||
|
@ -125,7 +131,51 @@ pub async fn save_setting_handler(
|
||||||
std::os::unix::fs::symlink(&path_ron.canonicalize()?, tag_folder.join(&filename_ron))?;
|
std::os::unix::fs::symlink(&path_ron.canonicalize()?, tag_folder.join(&filename_ron))?;
|
||||||
std::os::unix::fs::symlink(&path_json.canonicalize()?, tag_folder.join(&filename_json))?;
|
std::os::unix::fs::symlink(&path_json.canonicalize()?, tag_folder.join(&filename_json))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create symlinks for app id tag folder
|
||||||
|
let app_tag_folder = file_util::setting_folder_by_app_id_tag(&cli.folder, parsed_data.steam_app_id, tag);
|
||||||
|
if !app_tag_folder.exists() {
|
||||||
|
std::fs::create_dir(&app_tag_folder)?;
|
||||||
|
}
|
||||||
|
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
||||||
|
{
|
||||||
|
log::debug!("Symlinking {} -> {}", app_tag_folder.join(&filename_ron).display(), path_ron.canonicalize()?.display());
|
||||||
|
std::os::windows::fs::symlink_file(&path_ron.canonicalize()?, app_tag_folder.join(&filename_ron))?;
|
||||||
|
std::os::windows::fs::symlink_file(&path_json.canonicalize()?, app_tag_folder.join(&filename_json))?;
|
||||||
|
}
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
log::debug!("Symlinking {} -> {}", app_tag_folder.join(&filename_ron).display(), path_ron.canonicalize()?.display());
|
||||||
|
std::os::unix::fs::symlink(&path_ron.canonicalize()?, app_tag_folder.join(&filename_ron))?;
|
||||||
|
std::os::unix::fs::symlink(&path_json.canonicalize()?, app_tag_folder.join(&filename_json))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create symlinks for user id tag folder
|
||||||
|
let user_tag_folder = file_util::setting_folder_by_user_id_tag(&cli.folder, parsed_data.steam_user_id, tag);
|
||||||
|
if !user_tag_folder.exists() {
|
||||||
|
std::fs::create_dir(&user_tag_folder)?;
|
||||||
|
}
|
||||||
|
#[cfg(target_family = "windows")] // NOTE: windows support is untested and unmaintained
|
||||||
|
{
|
||||||
|
log::debug!("Symlinking {} -> {}", user_tag_folder.join(&filename_ron).display(), path_ron.canonicalize()?.display());
|
||||||
|
std::os::windows::fs::symlink_file(&path_ron.canonicalize()?, user_tag_folder.join(&filename_ron))?;
|
||||||
|
std::os::windows::fs::symlink_file(&path_json.canonicalize()?, user_tag_folder.join(&filename_json))?;
|
||||||
|
}
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
log::debug!("Symlinking {} -> {}", user_tag_folder.join(&filename_ron).display(), path_ron.canonicalize()?.display());
|
||||||
|
std::os::unix::fs::symlink(&path_ron.canonicalize()?, user_tag_folder.join(&filename_ron))?;
|
||||||
|
std::os::unix::fs::symlink(&path_json.canonicalize()?, user_tag_folder.join(&filename_json))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(actix_web::HttpResponse::NoContent())
|
Ok(actix_web::HttpResponse::NoContent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
8
backend/community_settings_srv/src/consts.rs
Normal file
8
backend/community_settings_srv/src/consts.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
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,146 +1,10 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
pub const RON_EXTENSION: &'static str = "ron";
|
use crate::consts::*;
|
||||||
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 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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setting.id = id;
|
|
||||||
serde_json::to_writer(std::fs::File::create(&f_path)?, &setting).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn filename(id: u128, ext: &str) -> String {
|
pub fn filename(id: u128, ext: &str) -> String {
|
||||||
format!("{}.{}", id, ext)
|
format!("{}.{}", id, ext)
|
||||||
}
|
}
|
||||||
|
@ -159,6 +23,23 @@ 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)
|
||||||
|
@ -166,6 +47,23 @@ 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)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
mod api;
|
mod api;
|
||||||
mod cli;
|
mod cli;
|
||||||
|
mod consts;
|
||||||
mod file_util;
|
mod file_util;
|
||||||
|
mod upgrade;
|
||||||
|
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
|
|
||||||
|
@ -27,14 +29,16 @@ 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());
|
||||||
file_util::build_folder_layout(&args.folder)?;
|
upgrade::build_folder_layout(&args.folder)?;
|
||||||
|
|
||||||
// fix things
|
// fix things
|
||||||
if args.fix {
|
if args.fix {
|
||||||
log::info!("Fixing old symlinks");
|
log::info!("Fixing old symlinks");
|
||||||
file_util::fix_symlinks(&args.folder)?;
|
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");
|
log::info!("Resynchronizing file IDs with file name IDs");
|
||||||
file_util::sync_ids(&args.folder)?;
|
upgrade::sync_ids(&args.folder)?;
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
164
backend/community_settings_srv/src/upgrade.rs
Normal file
164
backend/community_settings_srv/src/upgrade.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
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 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(())
|
||||||
|
}
|
|
@ -31,7 +31,7 @@ const THINGS: &[u8] = &[
|
||||||
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(200);
|
const TIME_UNIT: std::time::Duration = std::time::Duration::from_millis(250);
|
||||||
|
|
||||||
pub fn flash_led() {
|
pub fn flash_led() {
|
||||||
use smokepatio::ec::ControllerSet;
|
use smokepatio::ec::ControllerSet;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "PowerTools",
|
"name": "PowerTools",
|
||||||
"version": "2.0.0-beta2",
|
"version": "2.0.1-beta1",
|
||||||
"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",
|
||||||
|
|
|
@ -40,18 +40,22 @@ export class StoreResultsPage extends Component<{onNewVariant: () => void}> {
|
||||||
{ tr("No results") /* TODO translate */ }
|
{ tr("No results") /* TODO translate */ }
|
||||||
</Focusable>);
|
</Focusable>);
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
return (
|
||||||
return (<Focusable
|
<div style={{
|
||||||
style={{
|
marginTop: "40px",
|
||||||
display: "flex",
|
marginBottom: "40px",
|
||||||
flexWrap: "wrap",
|
overflowY: "scroll",
|
||||||
justifyContent: "center",
|
}}>
|
||||||
rowGap: "5px",
|
<Focusable
|
||||||
columnGap: "5px",
|
style={{
|
||||||
maxWidth: "100%",
|
display: "flex",
|
||||||
margin: "2em 0.5em",
|
flexWrap: "wrap",
|
||||||
}}
|
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={{
|
||||||
|
@ -149,7 +153,8 @@ export class StoreResultsPage extends Component<{onNewVariant: () => void}> {
|
||||||
</Focusable>
|
</Focusable>
|
||||||
</PanelSectionRow>))
|
</PanelSectionRow>))
|
||||||
}
|
}
|
||||||
</Focusable>);
|
</Focusable>
|
||||||
|
</div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue