From 3204b17632d1f7e8a74429d80a70d33d9927e57b Mon Sep 17 00:00:00 2001 From: wytch Date: Fri, 27 Feb 2026 22:15:59 -0600 Subject: [PATCH] FIFO pipes for speed --- ..._event[rungame,endgame](sync).ash.donotuse | 48 ++++++ ...sync-logger[start](permanent).ash.donotuse | 0 savesync.conf => old/savesync.conf.donotuse | 0 old/savesync[start](permanent).ash.donotuse | 79 +++++++++ old/savesync[start](permanent).ash.donotuse_ | 67 ++++++++ pub_event[rungame,endgame](sync).ash | 50 +----- savesync[start](permanent).ash | 153 ++++++++++-------- 7 files changed, 283 insertions(+), 114 deletions(-) create mode 100644 old/pub_event[rungame,endgame](sync).ash.donotuse rename savesync-logger[start](permanent).ash => old/savesync-logger[start](permanent).ash.donotuse (100%) rename savesync.conf => old/savesync.conf.donotuse (100%) create mode 100644 old/savesync[start](permanent).ash.donotuse create mode 100644 old/savesync[start](permanent).ash.donotuse_ diff --git a/old/pub_event[rungame,endgame](sync).ash.donotuse b/old/pub_event[rungame,endgame](sync).ash.donotuse new file mode 100644 index 0000000..bfaecaf --- /dev/null +++ b/old/pub_event[rungame,endgame](sync).ash.donotuse @@ -0,0 +1,48 @@ +#!/bin/ash +# shellcheck shell=dash + +. /recalbox/share/system/configs/savesync/savesync.conf + +# Constants +EVENT_FILE="/tmp/es_state.inf" +TIMEOUT_SEC=10 + +log() { + mosquitto_pub -h 127.0.0.1 -p 1883 -q 0 -t "$LOG_TOPIC" -m "SaveLog=[$(date '+%H:%M:%S')] [PUB] $1" 2>/dev/null +} + +# 1. Validation +[ ! -s "$EVENT_FILE" ] && exit 0 +PAYLOAD=$(cat "$EVENT_FILE") + +# 2. Network Check (Short-circuit exit) +nc -z -w 1 "$RCLONE_ENDPOINT" "$RCLONE_PORT" || { + log "Offline: Skipping sync" + exit 0 +} + +# 3. Publish Event +mosquitto_pub -h 127.0.0.1 -p 1883 -t "$TOPIC" -m "$PAYLOAD" || { + log "MQTT Fail" + exit 1 +} + +# 4. Wait for Response (Crucial: Using -C 1 and the same Response Key) +( + sleep "$TIMEOUT_SEC" + mosquitto_pub -h 127.0.0.1 -t "$RESPONSE_TOPIC" -m "SaveSync=timeout" +) & +TIMER_PID=$! + +log "Waiting for Daemon response..." +# Listen for the specific "SaveSync" key +RESPONSE=$(mosquitto_sub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -C 1) +kill "$TIMER_PID" 2>/dev/null + +case "$RESPONSE" in +*"timeout"*) log "WARN: Daemon timed out. Starting game anyway." ;; +*"0"*) log "INFO: Sync Complete. Launching game." ;; +*) log "INFO: Received response: $RESPONSE" ;; +esac + +exit 0 diff --git a/savesync-logger[start](permanent).ash b/old/savesync-logger[start](permanent).ash.donotuse similarity index 100% rename from savesync-logger[start](permanent).ash rename to old/savesync-logger[start](permanent).ash.donotuse diff --git a/savesync.conf b/old/savesync.conf.donotuse similarity index 100% rename from savesync.conf rename to old/savesync.conf.donotuse diff --git a/old/savesync[start](permanent).ash.donotuse b/old/savesync[start](permanent).ash.donotuse new file mode 100644 index 0000000..5bce545 --- /dev/null +++ b/old/savesync[start](permanent).ash.donotuse @@ -0,0 +1,79 @@ +#!/bin/ash +# shellcheck shell=dash + +. /recalbox/share/system/configs/savesync/savesync.conf + +log() { + mosquitto_pub -h 127.0.0.1 -p 1883 -q 0 -t "$LOG_TOPIC" -m "SaveLog=[$(date '+%H:%M:%S')] [DAEMON] [$1] $2" 2>/dev/null +} + +log "INFO" "--- Daemon Active ---" + +mosquitto_sub -h 127.0.0.1 -p 1883 -t "$TOPIC" | while IFS="=" read -r key value; do + # Clean input + val=$(echo "$value" | tr -d '\r') + + case "$key" in + "SystemId") sid="$val" ;; + "GamePath") + gp="$val" + # Fast path transformation using sed + sp=$(echo "$val" | sed 's|roms|saves|; s|\.[^.]*$|.srm|') + bp=$(echo "$sp" | sed 's|saves|archives|') + ;; + "Action") act="$val" ;; + "State") + st="$val" + + # Check if we have the full "Start Game" context + if [ "$st" = "playing" ] && [ "$act" = "rungame" ]; then + # FORK to background, but keep current variables! + ( + log "INFO" "Syncing START for $(basename "$gp")" + + remote_f="$REMOTE_BASE/$sid/$(basename "$sp")" + loc_sz=$(stat -c %s "$sp" 2>/dev/null || echo 0) + + # Fetch remote size + log "DEBUG" "rclone lsjson "$remote_f"" + rem_sz=$(rclone lsjson "$remote_f" 2>/dev/null | grep -o '"Size":[0-9]*' | cut -d: -f2) + : "${rem_sz:=0}" + + if [ "$loc_sz" -lt "$rem_sz" ]; then + log "WARN" "Cloud save larger. Restoring..." + mkdir -p "$(dirname "$bp")" + log "DEBUG" "rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")"" + rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")" + else + log "INFO" "Local save current. Updating..." + log "DEBUG" "rclone update "$remote_f" "$sp"" + rclone update "$remote_f" "$sp" + fi + + # SIGNAL SUCCESS (Key must match Publisher's expectation) + mosquitto_pub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -m "SaveSync=0" + ) & + + elif [ "$st" = "endgame" ]; then + # Syncing on end - we don't necessarily need to block here + ( + log "INFO" "Syncing END for $(basename "$sp")" + log "DEBUG" "rclone update "$sp" "$REMOTE_BASE/$sid/"" + rclone update "$sp" "$REMOTE_BASE/$sid/" + log "INFO" "Final Sync Done." + + # Optional: Tell publisher we are done (though ES usually doesn't wait on endgame) + mosquitto_pub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -m "SaveSync=0" + ) & + fi + + # Clear variables for next burst of events + sid="" + gp="" + sp="" + bp="" + act="" + st="" + ;; + esac +done diff --git a/old/savesync[start](permanent).ash.donotuse_ b/old/savesync[start](permanent).ash.donotuse_ new file mode 100644 index 0000000..b6cd49a --- /dev/null +++ b/old/savesync[start](permanent).ash.donotuse_ @@ -0,0 +1,67 @@ + + # Clean input + val=$(echo "$value" | tr -d '\r') + + case "$key" in + "SystemId") sid="$val" ;; + "GamePath") + gp="$val" + # Fast path transformation using sed + sp=$(echo "$val" | sed 's|roms|saves|; s|\.[^.]*$|.srm|') + bp=$(echo "$sp" | sed 's|saves|archives|') + ;; + "Action") act="$val" ;; + "State") + st="$val" + + # Check if we have the full "Start Game" context + if [ "$st" = "playing" ] && [ "$act" = "rungame" ]; then + # FORK to background, but keep current variables! + ( + log "INFO" "Syncing START for $(basename "$gp")" + + remote_f="$REMOTE_BASE/$sid/$(basename "$sp")" + loc_sz=$(stat -c %s "$sp" 2>/dev/null || echo 0) + + # Fetch remote size + log "DEBUG" "rclone lsjson "$remote_f"" + rem_sz=$(rclone lsjson "$remote_f" 2>/dev/null | grep -o '"Size":[0-9]*' | cut -d: -f2) + : "${rem_sz:=0}" + + if [ "$loc_sz" -lt "$rem_sz" ]; then + log "WARN" "Cloud save larger. Restoring..." + mkdir -p "$(dirname "$bp")" + log "DEBUG" "rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")"" + rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")" + else + log "INFO" "Local save current. Updating..." + log "DEBUG" "rclone update "$remote_f" "$sp"" + rclone update "$remote_f" "$sp" + fi + + # SIGNAL SUCCESS (Key must match Publisher's expectation) + mosquitto_pub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -m "SaveSync=0" + ) & + + elif [ "$st" = "endgame" ]; then + # Syncing on end - we don't necessarily need to block here + ( + log "INFO" "Syncing END for $(basename "$sp")" + log "DEBUG" "rclone update "$sp" "$REMOTE_BASE/$sid/"" + rclone update "$sp" "$REMOTE_BASE/$sid/" + log "INFO" "Final Sync Done." + + # Optional: Tell publisher we are done (though ES usually doesn't wait on endgame) + mosquitto_pub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -m "SaveSync=0" + ) & + fi + + # Clear variables for next burst of events + sid="" + gp="" + sp="" + bp="" + act="" + st="" + ;; + esac diff --git a/pub_event[rungame,endgame](sync).ash b/pub_event[rungame,endgame](sync).ash index bfaecaf..66ab900 100644 --- a/pub_event[rungame,endgame](sync).ash +++ b/pub_event[rungame,endgame](sync).ash @@ -1,48 +1,4 @@ #!/bin/ash -# shellcheck shell=dash - -. /recalbox/share/system/configs/savesync/savesync.conf - -# Constants -EVENT_FILE="/tmp/es_state.inf" -TIMEOUT_SEC=10 - -log() { - mosquitto_pub -h 127.0.0.1 -p 1883 -q 0 -t "$LOG_TOPIC" -m "SaveLog=[$(date '+%H:%M:%S')] [PUB] $1" 2>/dev/null -} - -# 1. Validation -[ ! -s "$EVENT_FILE" ] && exit 0 -PAYLOAD=$(cat "$EVENT_FILE") - -# 2. Network Check (Short-circuit exit) -nc -z -w 1 "$RCLONE_ENDPOINT" "$RCLONE_PORT" || { - log "Offline: Skipping sync" - exit 0 -} - -# 3. Publish Event -mosquitto_pub -h 127.0.0.1 -p 1883 -t "$TOPIC" -m "$PAYLOAD" || { - log "MQTT Fail" - exit 1 -} - -# 4. Wait for Response (Crucial: Using -C 1 and the same Response Key) -( - sleep "$TIMEOUT_SEC" - mosquitto_pub -h 127.0.0.1 -t "$RESPONSE_TOPIC" -m "SaveSync=timeout" -) & -TIMER_PID=$! - -log "Waiting for Daemon response..." -# Listen for the specific "SaveSync" key -RESPONSE=$(mosquitto_sub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -C 1) -kill "$TIMER_PID" 2>/dev/null - -case "$RESPONSE" in -*"timeout"*) log "WARN: Daemon timed out. Starting game anyway." ;; -*"0"*) log "INFO: Sync Complete. Launching game." ;; -*) log "INFO: Received response: $RESPONSE" ;; -esac - -exit 0 +# +PAYLOAD=$(cat /tmp/es_state.inf) +echo "EVENT:$PAYLOAD" >/tmp/savesync_pipe diff --git a/savesync[start](permanent).ash b/savesync[start](permanent).ash index 5bce545..9a01f9a 100644 --- a/savesync[start](permanent).ash +++ b/savesync[start](permanent).ash @@ -1,79 +1,98 @@ #!/bin/ash -# shellcheck shell=dash - . /recalbox/share/system/configs/savesync/savesync.conf -log() { - mosquitto_pub -h 127.0.0.1 -p 1883 -q 0 -t "$LOG_TOPIC" -m "SaveLog=[$(date '+%H:%M:%S')] [DAEMON] [$1] $2" 2>/dev/null +PIPE="/tmp/savesync_pipe" +LOG_FILE="/recalbox/share/system/logs/savesync.log" + +mkfifo "$PIPE" +chmod 666 "$PIPE" + +# Function to write to disk +write_log() { + printf "[%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >>"$LOG_FILE" } -log "INFO" "--- Daemon Active ---" - -mosquitto_sub -h 127.0.0.1 -p 1883 -t "$TOPIC" | while IFS="=" read -r key value; do - # Clean input - val=$(echo "$value" | tr -d '\r') - - case "$key" in - "SystemId") sid="$val" ;; - "GamePath") - gp="$val" - # Fast path transformation using sed - sp=$(echo "$val" | sed 's|roms|saves|; s|\.[^.]*$|.srm|') - bp=$(echo "$sp" | sed 's|saves|archives|') - ;; - "Action") act="$val" ;; - "State") - st="$val" - - # Check if we have the full "Start Game" context - if [ "$st" = "playing" ] && [ "$act" = "rungame" ]; then - # FORK to background, but keep current variables! +# The listener loop +while true; do + if read -r line <"$PIPE"; then + case "$line" in + LOG:*) + # Remove the "LOG:" prefix and write + write_log "${line#LOG:}" + ;; + EVENT:*) + # Handle Game Events (forked to background) ( - log "INFO" "Syncing START for $(basename "$gp")" + + PIPE="/tmp/savesync_pipe" + event_data="${line#EVENT:}" - remote_f="$REMOTE_BASE/$sid/$(basename "$sp")" - loc_sz=$(stat -c %s "$sp" 2>/dev/null || echo 0) + # Clean input + $(echo "$event_data" | tr -d '\r') + + while IFS= read -r key val; do + case "$key" in + "SystemId") sid="$val" ;; + "GamePath") + gp="$val" + # Fast path transformation using sed + sp=$(echo "$val" | sed 's|roms|saves|; s|\.[^.]*$|.srm|') + bp=$(echo "$sp" | sed 's|saves|archives|') + ;; + "Action") act="$val" ;; + "State") + st="$val" - # Fetch remote size - log "DEBUG" "rclone lsjson "$remote_f"" - rem_sz=$(rclone lsjson "$remote_f" 2>/dev/null | grep -o '"Size":[0-9]*' | cut -d: -f2) - : "${rem_sz:=0}" + # Check if we have the full "Start Game" context + if [ "$st" = "playing" ] && [ "$act" = "rungame" ]; then + # FORK to background, but keep current variables! + ( + printf "%s %s\n" "LOG:" "Syncing START for $(basename "$gp")" >${PIPE} - if [ "$loc_sz" -lt "$rem_sz" ]; then - log "WARN" "Cloud save larger. Restoring..." - mkdir -p "$(dirname "$bp")" - log "DEBUG" "rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")"" - rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")" - else - log "INFO" "Local save current. Updating..." - log "DEBUG" "rclone update "$remote_f" "$sp"" - rclone update "$remote_f" "$sp" - fi + remote_f="$REMOTE_BASE/$sid/$(basename "$sp")" + loc_sz=$(stat -c %s "$sp" 2>/dev/null || echo 0) - # SIGNAL SUCCESS (Key must match Publisher's expectation) - mosquitto_pub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -m "SaveSync=0" + # Fetch remote size + rem_sz=$(rclone lsjson "$remote_f" 2>/dev/null | grep -o '"Size":[0-9]*' | cut -d: -f2) + : "${rem_sz:=0}" + + if [ "$loc_sz" -lt "$rem_sz" ]; then + printf "%s %s\n" "LOG:" "Cloud save larger. Restoring..." >${PIPE} + mkdir -p "$(dirname "$bp")" + rclone copyto "$remote_f" "$sp" --backup-dir "$(dirname "$bp")" + else + printf "%s %s\n" "LOG:" "Local save current. Updating..."> >${PIPE} + rclone update "$remote_f" "$sp" + fi + + # SIGNAL SUCCESS (Key must match Publisher's expectation) + printf "%s %s\n" "LOG:" "Save synced successfully" >${PIPE} + ) & + + elif [ "$st" = "endgame" ]; then + # Syncing on end - we don't necessarily need to block here + ( + printf "%s %s\n" "LOG:" "Syncing END for $(basename "$sp")" >${PIPE} + rclone update "$sp" "$REMOTE_BASE/$sid/" + printf "%s %s\n" "LOG:" "Final Sync Done." >${PIPE} + + ) & + fi + + # Clear variables for next burst of events + sid="" + gp="" + sp="" + bp="" + act="" + st="" + ;; + esac + done<$(printf "%s\n" "$event_data" | tr -d '\r') + + printf '%s\n' "LOG:Sync complete for $sp" >"$PIPE" ) & - - elif [ "$st" = "endgame" ]; then - # Syncing on end - we don't necessarily need to block here - ( - log "INFO" "Syncing END for $(basename "$sp")" - log "DEBUG" "rclone update "$sp" "$REMOTE_BASE/$sid/"" - rclone update "$sp" "$REMOTE_BASE/$sid/" - log "INFO" "Final Sync Done." - - # Optional: Tell publisher we are done (though ES usually doesn't wait on endgame) - mosquitto_pub -h 127.0.0.1 -p 1883 -t "$RESPONSE_TOPIC" -m "SaveSync=0" - ) & - fi - - # Clear variables for next burst of events - sid="" - gp="" - sp="" - bp="" - act="" - st="" - ;; - esac + ;; + esac + fi done