testsAndMisc/linux_configuration/utils/lib/steam_report.sh
Krzysztof kuhy Rudnicki 4f2b0ccdf7 Dissolve linux_configuration/scripts/ and single_use/
Two levels of pure taxonomy carrying no information, and a 2-directory
cap leaves no room for them. Categories now sit directly under
linux_configuration/: features, fixes, utils, misc, fresh-install,
installers, diagnostics, gaming, boot_recovery, periodic_background, lib,
meta, tests. A script's own lib/ and tests/ are exempt from the cap, so
those stay put.

Depth violations 248 -> 139; roughly 40 of the remainder are inside
digital_wellbeing and hosts, deferred until the guard-lib repoint.

The moves are the easy half. What breaks is every path that counted
levels, and three classes of it had to be found by resolving paths rather
than reading diffs:

  - 52 relative `source` lines for lib/common.sh and lib/android.sh. The
    correct prefix differs per file: 40 were ../../ and 12 were ../../../,
    so a blanket replace would have fixed one set and broken the other.
  - REPO_ROOT in 7 fixes/lib/tests harnesses and setup_syncyomi_guard.sh,
    each walking up four levels from a directory now two levels shallower.
    This caused a real regression -- the fixes suite went red with
    "log_warn: command not found" -- caught by running it and comparing
    against the pre-move baseline worktree.
  - conftest.py's sys.path entries, which took the pytest gate down with
    14 collection errors, and the `# shellcheck source=` directives, which
    took shellcheck down with SC1091.

16 symlinks in $HOME broke, including the entire i3blocks bar. All
repointed, and cpu_monitor/memory/disk/time/volume were run to confirm
they emit real output rather than merely resolving.

Verified: 1798 tests pass at 100% coverage, 8 of 9 lib/tests suites pass
(the ninth needs the coverage jail, identically at baseline), pre-commit
is clean, and the two untracked secret configs are still ignored after
their gitignore paths changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
2026-08-23 18:53:30 +02:00

157 lines
6.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# The run loop and the on-disk cache for steam_compatibility.sh.
#
# load_cache_map writes CACHE_MAP and main reads it, so writer and reader sit
# in the same file.
load_cache_map() {
CACHE_MAP=()
if [[ -r $RESULTS_CACHE ]]; then
while IFS= read -r raw_line; do
# Normalize historical caches that contain literal "\t" instead of real tabs
local norm_line
norm_line=$(printf "%s" "$raw_line" | sed -E $'s/\\t/\t/g; s/\r$//')
IFS=$'\t' read -r c_score c_appid c_linux c_min c_rec c_name c_pdb <<< "$norm_line"
[[ -z ${c_appid:-} ]] && continue
c_pdb=${c_pdb:-unknown}
CACHE_MAP["$c_appid"]="$c_score\t$c_appid\t$c_linux\t$c_min\t$c_rec\t$c_name\t$c_pdb"
done < "$RESULTS_CACHE"
fi
}
main() {
parse_args "$@"
detect_system
log "System: CPU=[$SYSTEM_CPU_MODEL] class=$SYSTEM_CPU_CLASS | GPU=$SYSTEM_GPU_VENDOR | RAM=${SYSTEM_RAM_GB}GB | Arch=$SYSTEM_ARCH"
local tmpdir
tmpdir=$(mktemp -d)
trap '[[ -n "${tmpdir:-}" ]] && rm -rf "$tmpdir"' EXIT
local games_tsv="$tmpdir/games.tsv"
: > "$games_tsv"
# Ensure credentials exist: load from env/config or prompt, else exit
if ! load_credentials; then
prompt_for_credentials
fi
# Fail fast if we cannot reach the store API to avoid noisy per-app errors
check_network_or_exit
if list_owned_games_via_api > "$games_tsv" 2> /dev/null; then
log "Fetched owned games via Steam Web API"
fi
if [[ ! -s $games_tsv ]]; then
die "No games found from Steam Web API. Check STEAM_API_KEY/STEAM_ID64 and network connectivity."
fi
# Fail fast if we cannot reach the store API to avoid noisy per-app errors
check_network_or_exit
ensure_cache_dir
if [[ $CLEAR_CACHE -eq 1 ]] && [[ -f $RESULTS_CACHE ]]; then
rm -f "$RESULTS_CACHE" || true
log "Cleared cache: $RESULTS_CACHE"
fi
if [[ $FORCE_REFRESH -eq 0 ]]; then
load_cache_map
else
CACHE_MAP=()
fi
local results_combined="$tmpdir/results.tsv"
: > "$results_combined"
local count=0
local total
total=$(wc -l < "$games_tsv" | tr -d ' ')
[[ -z $total ]] && total=0
while IFS=$'\t' read -r appid name; do
[[ $ABORT -eq 1 ]] && break
[[ -n $appid ]] || continue
if is_known_tool_name "$name"; then
vlog "[$((count + 1))/$total] Skipping compatibility tool: $name ($appid)"
continue
fi
# If cached, reuse; else analyze and cache
if [[ $FORCE_REFRESH -eq 0 && -n ${CACHE_MAP[$appid]+isset} ]]; then
# Normalize to include ProtonDB column if older cache lacked it
IFS=$'\t' read -r c_score c_a c_linux c_min c_rec c_name c_pdb <<< "${CACHE_MAP[$appid]}"
c_pdb=${c_pdb:-unknown}
vlog "[$((count + 1))/$total] Cache hit: $name ($appid) | ProtonDB=$c_pdb"
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$c_score" "$c_a" "$c_linux" "$c_min" "$c_rec" "$c_name" "$c_pdb" >> "$results_combined"
continue
fi
count=$((count + 1))
log "Analyzing: $name ($appid) [$count/$total]"
local row url
url="https://store.steampowered.com/api/appdetails?appids=${appid}&l=en&cc=us"
vlog "[$count/$total] Fetching store appdetails: $url"
# Be gentle with the store API
sleep 0.1
row=$(http_get "$url" | extract_requirements_and_platforms_stdin "$appid" || true)
if [[ -z $row ]]; then continue; fi
local status linux _windows _mac min_txt rec_txt type
IFS=$'\t' read -r status linux _windows _mac min_txt rec_txt type <<< "$row"
vlog "[$count/$total] Parsed store data: status=$status linux=$linux type=$type"
# Occasionally Steam returns success=false spuriously; retry once
if [[ $status != "ok" ]]; then
vlog "[$count/$total] Store status=fail; retrying once..."
sleep 0.3
row=$(http_get "$url" | extract_requirements_and_platforms_stdin "$appid" || true)
IFS=$'\t' read -r status linux _windows _mac min_txt rec_txt type <<< "$row"
vlog "[$count/$total] After retry: status=$status"
fi
# Try filtered endpoint that often bypasses age/region gates
if [[ $status != "ok" ]]; then
local url2="https://store.steampowered.com/api/appdetails?appids=${appid}&filters=platforms,linux_requirements,pc_requirements,type&l=en&cc=us"
vlog "[$count/$total] Retrying with filters: $url2"
sleep 0.1
row=$(http_get "$url2" | extract_requirements_and_platforms_stdin "$appid" || true)
IFS=$'\t' read -r status linux _windows _mac min_txt rec_txt type <<< "$row"
vlog "[$count/$total] Filtered fetch status=$status"
fi
if [[ $status != "ok" ]]; then continue; fi
if [[ $type != "game" && $type != "dlc" && $type != "" ]]; then continue; fi
# ProtonDB tier
[[ $ABORT -eq 1 ]] && break
local pdb_tier
vlog "[$count/$total] Fetching ProtonDB tier for appid=$appid"
pdb_tier=$(fetch_protondb_tier "$appid")
vlog "[$count/$total] ProtonDB tier=$pdb_tier"
# Compute hardware-based score
local score_line s_score s_min_ram s_rec_ram
score_line=$(score_game "$linux" "$min_txt" "$rec_txt")
IFS=$'\t' read -r s_score s_min_ram s_rec_ram <<< "$score_line"
# Gate by ProtonDB: if bronze or below -> mark unplayable and force low score
if ! protondb_allowed "$pdb_tier"; then
s_score=-999
fi
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$s_score" "$appid" "$linux" "$s_min_ram" "$s_rec_ram" "$name" "$pdb_tier" >> "$results_combined"
vlog "[$count/$total] Scored and recorded: score=$s_score min=${s_min_ram}G rec=${s_rec_ram}G"
done < "$games_tsv"
if [[ ! -s $results_combined ]]; then
die "No compatible entries parsed from store API."
fi
print_header
local rank=0
sort -t $'\t' -k1,1nr -k6,6 "$results_combined" | while IFS=$'\t' read -r score appid linux min_ram rec_ram name pdb_tier; do
rank=$((rank + 1))
local display_name="$name"
if ! protondb_allowed "$pdb_tier"; then
display_name="$name [UNPLAYABLE]"
fi
printf "%-5s %-8s %-6s %-6s %-8s %-9s %s\n" "$rank" "$score" "${min_ram}G" "${rec_ram}G" "$linux" "${pdb_tier:-unknown}" "$display_name"
done
# Persist updated results for future runs (only current library entries)
cp -f "$results_combined" "$RESULTS_CACHE" 2> /dev/null || cat "$results_combined" > "$RESULTS_CACHE"
}