New userdata backup function, supports full and partial backups from CLI or Configurator

This commit is contained in:
icenine451 2025-03-19 09:10:47 -04:00
parent d0d55cb06b
commit 57ffddec18

View file

@ -354,8 +354,233 @@ update_vita3k_firmware() {
}
backup_retrodeck_userdata() {
# This function can compress one or more RetroDECK userdata folders into a single zip file for backup.
# The function can do a "standard" backup of all the normal userdata files (which can be very big if there is a lot of media) or a "custom" backup of only specified paths
# The function can take both folder names as defined in retrodeck.cfg or full paths as arguments for folders to backup
# It will also validate that all the provided paths exist and that there is enough free space to perform the backup before actually proceeding.
# It will also rotate backups so that there are only 3 maximum of each type (standard or custom)
# USAGE: backup_retrodeck_userdata standard
# backup_retrodeck_userdata custom saves_folder states_folder /some/other/path
create_dir "$backups_folder"
zip -rq9 "$backups_folder/$(date +"%0m%0d")_retrodeck_userdata.zip" "$saves_folder" "$states_folder" "$bios_folder" "$media_folder" "$themes_folder" "$rdhome/ES-DE/collections" "$rdhome/ES-DE/gamelists" "$logs_folder" "$screenshots_folder" "$mods_folder" "$texture_packs_folder" "$borders_folder" > $logs_folder/$(date +"%0m%0d")_backup_log.log
backup_date=$(date +"%0m%0d_%H%M")
backup_log_file="$logs_folder/${backup_date}_${backup_type}_backup_log.log"
# Check if first argument is the type
if [[ "$1" == "standard" || "$1" == "custom" ]]; then
backup_type="$1"
shift # Remove the first argument
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "No valid backup option chosen. Valid options are <standard> and <custom>."
fi
log e "No valid backup option chosen. Valid options are <standard> and <custom>."
exit 1
fi
zip_file="$backups_folder/retrodeck_${backup_date}_${backup_type}.zip"
# Initialize paths arrays
paths_to_backup=()
declare -A config_paths # Requires an associative (dictionary) array to work
# Build array of folder names and real paths from retrodeck.cfg
while read -r config_line; do
local current_setting_name=$(get_setting_name "$config_line" "retrodeck")
if [[ ! $current_setting_name =~ (rdhome|sdcard|backups_folder) ]]; then # Ignore these locations
local current_setting_value=$(get_setting_value "$rd_conf" "$current_setting_name" "retrodeck" "paths")
config_paths["$current_setting_name"]="$current_setting_value"
fi
done < <(grep -v '^\s*$' $rd_conf | awk '/^\[paths\]/{f=1;next} /^\[/{f=0} f')
# Determine which paths to backup
if [[ "$backup_type" == "standard" ]]; then
for folder_name in "${!config_paths[@]}"; do
path_value="${config_paths[$folder_name]}"
if [[ -e "$path_value" ]]; then
paths_to_backup+=("$path_value")
log i "Adding to backup: $folder_name = $path_value"
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "The $folder_name was not found at its expected location, $path_value\nSomething may be wrong with your RetroDECK installation."
fi
log i "Warning: Path does not exist: $folder_name = $path_value"
fi
done
# Add static paths not defined in retrodeck.cfg
if [[ -e "$rdhome/ES-DE/collections" ]]; then
paths_to_backup+=("$rdhome/ES-DE/collections")
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "The ES-DE collections folder was not found at its expected location, $rdhome/ES-DE/collections\nSomething may be wrong with your RetroDECK installation."
fi
log i "Warning: Path does not exist: ES-DE/collections = $rdhome/ES-DE/collections"
fi
if [[ -e "$rdhome/ES-DE/gamelists" ]]; then
paths_to_backup+=("$rdhome/ES-DE/gamelists")
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "The ES-DE gamelists folder was not found at its expected location, $rdhome/ES-DE/gamelists\nSomething may be wrong with your RetroDECK installation."
fi
log i "Warning: Path does not exist: ES-DE/gamelists = $rdhome/ES-DE/gamelists"
fi
if [[ -e "$rdhome/ES-DE/custom_systems" ]]; then
paths_to_backup+=("$rdhome/ES-DE/custom_systems")
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "The ES-DE custom_systems folder was not found at its expected location, $rdhome/ES-DE/custom_systems\nSomething may be wrong with your RetroDECK installation."
fi
log i "Warning: Path does not exist: ES-DE/custom_systems = $rdhome/ES-DE/custom_systems"
fi
# Check if we found any valid paths
if [[ ${#paths_to_backup[@]} -eq 0 ]]; then
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "No valid userdata folders were found.\nSomething may be wrong with your RetroDECK installation."
fi
log e "Error: No valid paths found in config file"
return 1
fi
elif [[ "$backup_type" == "custom" ]]; then
if [[ "$#" -eq 0 ]]; then # Check if any paths were provided in the arguments
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "No valid backup locations were specified. Please try again."
fi
log e "Error: No paths specified for custom backup"
return 1
fi
# Process each argument - it could be a variable name or a direct path
for arg in "$@"; do
# Check if argument is a variable name in the config
if [[ -n "${config_paths[$arg]}" ]]; then
path_value="${config_paths[$arg]}"
if [[ -e "$path_value" ]]; then
paths_to_backup+=("$path_value")
log i "Added to backup: $arg = $path_value"
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "The $arg was not found at its expected location, $path_value.\nSomething may be wrong with your RetroDECK installation."
fi
log e "Error: Path from variable '$arg' does not exist: $path_value"
return 1
fi
# Otherwise treat it as a direct path
elif [[ -e "$arg" ]]; then
paths_to_backup+=("$arg")
log i "Added to backup: $arg"
else
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "The path $arg was not found at its expected location.\nPlease check the path and try again."
fi
log e "Error: '$arg' is neither a valid variable name nor an existing path"
return 1
fi
done
fi
# Calculate total size of selected paths
log i "Calculating size of backup data..."
total_size=0
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then # Show progress dialog if running Zenity Configurator
total_size_file=$(mktemp) # Create temp file for Zenity subshell data extraction
(
for path in "${paths_to_backup[@]}"; do
if [[ -e "$path" ]]; then
log d "Checking size of path $path"
path_size=$(du -sk "$path" 2>/dev/null | cut -f1) # Get size in KB
path_size=$((path_size * 1024)) # Convert to bytes for calculation
total_size=$((total_size + path_size))
echo "$total_size" > $total_size_file
fi
done
) |
rd_zenity --icon-name=net.retrodeck.retrodeck --progress --no-cancel --pulsate --auto-close \
--window-icon="/app/share/icons/hicolor/scalable/apps/net.retrodeck.retrodeck.svg" \
--title "RetroDECK Configurator Utility - Userdata Backup" \
--text="Verifying there is enough free space for the backup, please wait..."
total_size=$(cat "$total_size_file")
rm "$total_size_file" # Clean up temp file
else # If running in CLI
for path in "${paths_to_backup[@]}"; do
if [[ -e "$path" ]]; then
log d "Checking size of path $path"
path_size=$(du -sk "$path" 2>/dev/null | cut -f1) # Get size in KB
path_size=$((path_size * 1024)) # Convert to bytes for calculation
total_size=$((total_size + path_size))
fi
done
fi
# Get available space at destination
available_space=$(df -B1 "$backups_folder" | awk 'NR==2 {print $4}')
# Log sizes for reference
log i "Total size of backup data: $(numfmt --to=iec-i --suffix=B $total_size)"
log i "Available space at destination: $(numfmt --to=iec-i --suffix=B $available_space)"
# Check if we have enough space (using uncompressed size as a conservative estimate)
if [[ "$available_space" -lt "$total_size" ]]; then
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then
configurator_generic_dialog "RetroDECK Userdata Backup" "There is not enough free space to perform this backup.\n\nYou need at least $(numfmt --to=iec-i --suffix=B $total_size),\nplease free up some space and try again."
fi
log e "Error: Not enough space to perform backup. Need at least $(numfmt --to=iec-i --suffix=B $total_size)"
return 1
fi
log i "Starting backup process..."
if [[ "$CONFIGURATOR_GUI" == "zenity" ]]; then # Show progress dialog if running Zenity Configurator
(
# Create zip with selected paths
if zip -rq9 "$zip_file" "${paths_to_backup[@]}" >> "$backup_log_file" 2>&1; then
# Rotate backups for the specific type
cd "$backups_folder" || return 1
ls -t *_${backup_type}.zip | tail -n +4 | xargs -r rm
final_size=$(du -h "$zip_file" | cut -f1)
configurator_generic_dialog "RetroDECK Userdata Backup" "The backup to $zip_file was successful, final size is $final_size.\n\nThe backups have been rotated, keeping the last 3 of the $backup_type backup type."
log i "Backup completed successfully: $zip_file (Size: $final_size)"
log i "Older backups rotated, keeping latest 3 of type $backup_type"
if [[ ! -s "$backup_log_file" ]]; then # If the backup log file is empty, meaning zip threw no errors
rm -f "$backup_log_file"
fi
else
configurator_generic_dialog "RetroDECK Userdata Backup" "Something went wrong with the backup process. Please check the log $backup_log_file for more information."
log i "Error: Backup failed"
return 1
fi
) |
rd_zenity --icon-name=net.retrodeck.retrodeck --progress --no-cancel --pulsate --auto-close \
--window-icon="/app/share/icons/hicolor/scalable/apps/net.retrodeck.retrodeck.svg" \
--title "RetroDECK Configurator Utility - Userdata Backup" \
--text="Compressing files into backup, please wait..."
else
if zip -rq9 "$zip_file" "${paths_to_backup[@]}" >> "$backup_log_file" 2>&1; then
# Rotate backups for the specific type
cd "$backups_folder" || return 1
ls -t *_${backup_type}.zip | tail -n +4 | xargs -r rm
final_size=$(du -h "$zip_file" | cut -f1)
log i "Backup completed successfully: $zip_file (Size: $final_size)"
log i "Older backups rotated, keeping latest 3 of type $backup_type"
if [[ ! -s "$backup_log_file" ]]; then # If the backup log file is empty, meaning zip threw no errors
rm -f "$backup_log_file"
fi
else
log i "Error: Backup failed"
return 1
fi
fi
}
make_name_pretty() {