masterdraco f572a241ef Fix torrent processing issues in transmission_handler.sh
- Fix quote handling in transmission-remote commands
- Add robust handling for empty torrent IDs
- Improve path handling for empty directories
- Update version to 9.1 with shared directory handling
- Fix empty array subscript errors

On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   README.md
	modified:   etc/torrent/mover.conf
	modified:   install.sh
	new file:   usr/local/bin/smart-processor
	modified:   usr/local/bin/torrent-mover
	new file:   usr/local/bin/torrent-processor
	modified:   usr/local/lib/torrent-mover/common.sh
	modified:   usr/local/lib/torrent-mover/transmission_handler.sh
2025-03-04 17:15:51 +01:00

275 lines
8.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# Common utility functions and variables for torrent-mover
# Global Runtime Variables
DRY_RUN=0
INTERACTIVE=0
CACHE_WARMUP=0
DEBUG=0
# To avoid reprocessing the same source directory (across different torrents)
declare -A processed_source_dirs
declare -A CHECKED_MOUNTS=()
declare -A PATH_CACHE
# Logging Functions
# All log messages go to stderr.
log_debug() {
if [[ "${DEBUG}" -eq 1 ]]; then
echo -e "[DEBUG] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
if [[ "${USE_SYSLOG}" == "true" ]]; then
logger -t torrent-mover "[DEBUG] $*" || true
fi
fi
}
log_info() {
echo -e "[INFO] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
if [[ "${USE_SYSLOG}" == "true" ]]; then
logger -t torrent-mover "[INFO] $*" || true
fi
}
log_warn() {
echo -e "[WARN] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
if [[ "${USE_SYSLOG}" == "true" ]]; then
logger -t torrent-mover "[WARN] $*" || true
fi
}
log_error() {
echo -e "[ERROR] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
if [[ "${USE_SYSLOG}" == "true" ]]; then
logger -t torrent-mover "[ERROR] $*" || true
fi
}
# Error Handling & Notifications
error_handler() {
local lineno="$1"
local msg="$2"
log_error "Error on line ${lineno}: ${msg}"
# Optionally send a notification (e.g., email)
return 1
}
# translate_source: Converts the Transmissionreported path into the local path.
translate_source() {
local src="$1"
echo "${src/#${TRANSMISSION_PATH_PREFIX}/${LOCAL_PATH_PREFIX}}"
}
# parse_args: Processes commandline options.
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=1; shift ;;
--interactive) INTERACTIVE=1; shift ;;
--cache-warmup) CACHE_WARMUP=1; shift ;;
--debug) DEBUG=1; shift ;;
--help)
echo "Usage: $0 [--dry-run] [--interactive] [--cache-warmup] [--debug]" >&2
exit 0
;;
*) echo "Invalid option: $1" >&2; exit 1 ;;
esac
done
}
# check_dependencies: Ensures required commands are available.
check_dependencies() {
local deps=("transmission-remote" "unzip" "7z" "parallel" "bc")
for dep in "${deps[@]}"; do
command -v "${dep}" >/dev/null 2>&1 || { log_error "Missing dependency: ${dep}"; exit 1; }
done
# Check for unrar or unrar-free
if command -v unrar &>/dev/null; then
log_debug "Found unrar command"
elif command -v unrar-free &>/dev/null; then
log_debug "Found unrar-free command"
# Create an alias for unrar to point to unrar-free
alias unrar="unrar-free"
else
log_error "Missing dependency: unrar or unrar-free"
exit 1
fi
}
# check_disk_usage: Warn if disk usage is over 90%.
check_disk_usage() {
local dir="$1"
[[ -z "${dir}" ]] && return
log_debug "Checking disk usage for directory: ${dir}"
if ! df -P "${dir}" &>/dev/null; then
log_warn "Directory not found: ${dir}"
return
fi
local mount_point
mount_point=$(df -P "${dir}" | awk 'NR==2 {print $6}')
if [[ -z "${mount_point}" ]]; then
log_warn "Could not determine mount point for: ${dir}"
return
fi
log_debug "Mount point for ${dir} is ${mount_point}"
# Initialize CHECKED_MOUNTS as an empty array if not already done
if [[ -z "${CHECKED_MOUNTS+x}" ]]; then
declare -A CHECKED_MOUNTS
fi
# Check if we've already checked this mount point
if [[ -z "${CHECKED_MOUNTS[${mount_point}]+x}" ]]; then
local usage
usage=$(df -P "${dir}" | awk 'NR==2 {sub(/%/, "", $5); print $5}')
log_debug "Usage for ${mount_point}: ${usage}%"
if (( usage >= 90 )); then
log_warn "Storage warning: ${mount_point} at ${usage}% capacity"
fi
CHECKED_MOUNTS[${mount_point}]=1
else
log_debug "Mount point ${mount_point} already checked"
fi
}
# run_command_safely: Safer version of command execution that prevents injection
run_command_safely() {
# Instead of using eval with a command string, this function accepts the command and arguments separately
# This prevents command injection vulnerabilities
if [[ $# -eq 0 ]]; then
log_error "No command provided to run_command_safely"
return 1
fi
log_debug "Running command: $*"
"$@"
return $?
}
# retry_command: Execute a command with retries
retry_command() {
local cmd="$1"
local max_attempts="${2:-3}" # Default to 3 attempts
local wait_time="${3:-10}" # Default to 10 seconds wait between attempts
local attempt=1
local exit_code=0
local error_output=""
# Create a temporary file for capturing error output
local error_file
error_file=$(mktemp)
while (( attempt <= max_attempts )); do
log_debug "Attempt $attempt of $max_attempts: $cmd"
# Execute command and capture both exit code and stderr
error_output=$( { eval "$cmd"; exit_code=$?; } 2>&1 > >(tee /dev/stderr) )
if [[ ${exit_code} -eq 0 ]]; then
log_debug "Command succeeded on attempt $attempt"
rm -f "${error_file}"
return 0
else
# Log detailed error information
echo "${error_output}" > "${error_file}"
log_warn "Command failed (attempt $attempt, exit code: ${exit_code})"
log_debug "Error details: $(head -n 5 "${error_file}")"
if (( attempt == max_attempts )); then
log_error "Maximum attempts reached for command, last exit code: ${exit_code}"
log_error "Last error output: $(head -n 10 "${error_file}")"
rm -f "${error_file}"
return ${exit_code}
fi
# Exponential backoff - wait longer for each successive attempt
local adjusted_wait=$((wait_time * attempt))
log_debug "Waiting ${adjusted_wait} seconds before retry"
sleep ${adjusted_wait}
(( attempt++ ))
fi
done
rm -f "${error_file}"
return 1
}
# run_in_transaction: Runs commands with an atomic operation guarantee
# If any command fails, attempts to roll back changes
run_in_transaction() {
local action_desc="$1"
local cleanup_cmd="$2"
local main_cmd="$3"
log_debug "Starting transaction: ${action_desc}"
# Create marker file to indicate transaction in progress
local transaction_id
transaction_id=$(date +%s)-$$
local transaction_marker="/tmp/torrent-mover-transaction-${transaction_id}"
echo "${action_desc}" > "${transaction_marker}"
# Execute the main command
if ! eval "${main_cmd}"; then
log_error "Transaction failed: ${action_desc}"
# Only run cleanup if it exists
if [[ -n "${cleanup_cmd}" ]]; then
log_info "Attempting transaction rollback"
if ! eval "${cleanup_cmd}"; then
log_error "Rollback failed, manual intervention may be required"
else
log_info "Rollback completed successfully"
fi
fi
# Clean up marker
rm -f "${transaction_marker}"
return 1
fi
# Clean up marker on success
rm -f "${transaction_marker}"
log_debug "Transaction completed successfully: ${action_desc}"
return 0
}
# validate_directories: Ensure required directories exist and are writable
validate_directories() {
local directories=("$@")
local error_count=0
for dir in "${directories[@]}"; do
# Skip empty directory paths
if [[ -z "${dir}" ]]; then
continue
fi
if [[ ! -d "${dir}" ]]; then
log_error "Directory missing: ${dir}"
error_count=$((error_count + 1))
continue
fi
if [[ ! -w "${dir}" ]]; then
log_warn "Write permission denied for: ${dir}"
log_warn "This may cause problems - the script will continue but operations may fail"
# Don't increment error_count to allow script to continue
fi
done
if [[ ${error_count} -gt 0 ]]; then
log_error "${error_count} required directories are missing"
return 1
fi
return 0
}