unpacking failed - files where copied and unpacked

This commit is contained in:
masterdraco 2025-02-25 12:03:10 +01:00
parent d190672cc4
commit 7464b41b18
2 changed files with 82 additions and 29 deletions

View File

@ -37,6 +37,12 @@ LOG_FILE="/var/log/torrent_mover.log"
# Logging level: set to "DEBUG" to enable debug messages; otherwise "INFO" # Logging level: set to "DEBUG" to enable debug messages; otherwise "INFO"
LOG_LEVEL="INFO" LOG_LEVEL="INFO"
# Set CHECK_TRANSFER_INTEGRITY to "true" to verify copied files after transfer.
CHECK_TRANSFER_INTEGRITY="true"
# Optionally, set USE_SYSLOG="true" to also log messages to syslog.
USE_SYSLOG="false"
# Auto-create directories # Auto-create directories
mkdir -p "${DIR_GAMES_DST}" "${DIR_APPS_DST}" \ mkdir -p "${DIR_GAMES_DST}" "${DIR_APPS_DST}" \
"${DIR_MOVIES_DST}" "${DIR_BOOKS_DST}" \ "${DIR_MOVIES_DST}" "${DIR_BOOKS_DST}" \

View File

@ -1,46 +1,68 @@
#!/bin/bash #!/bin/bash
# Torrent Mover v7.2 - Enhanced Version with Directory Deduplication # Torrent Mover v7.2 - Enhanced & Robust Version with Directory Deduplication,
# Improved Archive Handling (keeping archives until ratio limits are reached)
# #
# This script processes completed torrents reported by Transmission, # This script processes completed torrents reported by Transmission,
# moving or copying files to designated destination directories. # moving or copying files to designated destination directories.
# It includes improved logging (with debug support), error handling, # It includes robust locking, advanced error handling & notifications,
# configurable path mappings, and avoids re-processing the same source directory. # improved logging, optional post-transfer integrity checks, configurable path mapping,
# and improved archive extraction that preserves directory structure.
############################# #
# Global Variables & Config # # Future improvements might include using Transmissions RPC API.
#############################
##############################
# Robust Locking with flock #
##############################
LOCK_FILE="/var/lock/torrent-mover.lock" LOCK_FILE="/var/lock/torrent-mover.lock"
MAX_AGE=300 # 5 minutes in seconds exec 200>"${LOCK_FILE}" || { echo "Cannot open lock file" >&2; exit 1; }
flock -n 200 || { echo "Another instance is running." >&2; exit 1; }
# Runtime flags (default values) ##############################
# Global Runtime Variables #
##############################
DRY_RUN=0 DRY_RUN=0
INTERACTIVE=0 INTERACTIVE=0
CACHE_WARMUP=0 CACHE_WARMUP=0
DEBUG=0 # Will be set to 1 if LOG_LEVEL is DEBUG or if --debug is passed DEBUG=0 # Set to 1 if LOG_LEVEL is DEBUG or --debug is passed
# Associative array to track source directories that have been processed. # To avoid reprocessing the same source directory (across different torrents)
declare -A processed_source_dirs declare -A processed_source_dirs
#################### ####################
# Logging Functions# # Logging Functions#
#################### ####################
# All log output goes to stderr so that command outputs remain clean. # All log messages go to stderr.
log_debug() { log_debug() {
if [[ "${DEBUG}" -eq 1 ]]; then if [[ "${DEBUG}" -eq 1 ]]; then
echo -e "[DEBUG] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2 echo -e "[DEBUG] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
[[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[DEBUG] $*"
fi fi
} }
log_info() { log_info() {
echo -e "[INFO] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2 echo -e "[INFO] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
[[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[INFO] $*"
} }
log_warn() { log_warn() {
echo -e "[WARN] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2 echo -e "[WARN] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
[[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[WARN] $*"
} }
log_error() { log_error() {
echo -e "[ERROR] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2 echo -e "[ERROR] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2
[[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[ERROR] $*"
} }
#################################
# 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)
exit 1
}
trap 'error_handler ${LINENO} "$BASH_COMMAND"' ERR
############################## ##############################
# Configuration & Validation # # Configuration & Validation #
############################## ##############################
@ -51,13 +73,12 @@ if [[ ! -f "${CONFIG_FILE}" ]]; then
fi fi
source "${CONFIG_FILE}" source "${CONFIG_FILE}"
# Validate mandatory configuration values. # Validate required configuration values.
if [[ -z "${TRANSMISSION_PATH_PREFIX:-}" || -z "${LOCAL_PATH_PREFIX:-}" ]]; then if [[ -z "${TRANSMISSION_PATH_PREFIX:-}" || -z "${LOCAL_PATH_PREFIX:-}" ]]; then
echo "FATAL: TRANSMISSION_PATH_PREFIX and LOCAL_PATH_PREFIX must be set in ${CONFIG_FILE}" >&2 echo "FATAL: TRANSMISSION_PATH_PREFIX and LOCAL_PATH_PREFIX must be set in ${CONFIG_FILE}" >&2
exit 1 exit 1
fi fi
# Set DEBUG flag if LOG_LEVEL is DEBUG.
if [[ "${LOG_LEVEL}" == "DEBUG" ]]; then if [[ "${LOG_LEVEL}" == "DEBUG" ]]; then
DEBUG=1 DEBUG=1
fi fi
@ -68,14 +89,13 @@ if [[ -n "${STORAGE_DIRS}" ]]; then
IFS=',' read -ra STORAGE_DIRS_ARRAY <<< "${STORAGE_DIRS}" IFS=',' read -ra STORAGE_DIRS_ARRAY <<< "${STORAGE_DIRS}"
fi fi
########################### ##############################
# Helper & Utility Functions # # Helper & Utility Functions #
########################### ##############################
# translate_source: Converts the Transmissionreported path into the local path. # translate_source: Converts the Transmissionreported path into the local path.
translate_source() { translate_source() {
local src="$1" local src="$1"
# Replace the Transmission reported prefix with the local prefix.
echo "${src/#${TRANSMISSION_PATH_PREFIX}/${LOCAL_PATH_PREFIX}}" echo "${src/#${TRANSMISSION_PATH_PREFIX}/${LOCAL_PATH_PREFIX}}"
} }
@ -100,10 +120,7 @@ parse_args() {
check_dependencies() { check_dependencies() {
local deps=("transmission-remote" "unrar" "unzip" "7z" "parallel" "bc") local deps=("transmission-remote" "unrar" "unzip" "7z" "parallel" "bc")
for dep in "${deps[@]}"; do for dep in "${deps[@]}"; do
if ! command -v "${dep}" >/dev/null 2>&1; then command -v "${dep}" >/dev/null 2>&1 || { log_error "Missing dependency: ${dep}"; exit 1; }
log_error "Missing dependency: ${dep}"
exit 1
fi
done done
} }
@ -144,7 +161,7 @@ record_checksums() {
mv "${CHECKSUM_DB}.tmp" "${CHECKSUM_DB}" mv "${CHECKSUM_DB}.tmp" "${CHECKSUM_DB}"
} }
# file_metadata: Returns an md5 hash for the file metadata. # file_metadata: Returns an md5 hash for file metadata.
file_metadata() { file_metadata() {
find "$1" -type f ! \( -iname "*.nfo" -o -iname "*.sfv" \) -exec md5sum {} \; | sort | awk '{print $1}' find "$1" -type f ! \( -iname "*.nfo" -o -iname "*.sfv" \) -exec md5sum {} \; | sort | awk '{print $1}'
} }
@ -265,16 +282,33 @@ get_destination() {
echo "${destination}" echo "${destination}"
} }
# handle_archives: Extracts archives found in the source directory. ######################################
# Improved Archive Extraction Handler #
######################################
# For each archive found in the source directory, create a subdirectory in the destination
# named after the archive (without its extension) and extract into that subdirectory.
# IMPORTANT: The archive is now retained in the source, so it will remain until the ratio
# limits are reached and Transmission removes the torrent data.
handle_archives() { handle_archives() {
local src="$1" dst="$2" local src="$1" dst="$2"
find "${src}" -type f \( -iname "*.rar" -o -iname "*.zip" -o -iname "*.7z" \) | while read -r arch; do find "${src}" -type f \( -iname "*.rar" -o -iname "*.zip" -o -iname "*.7z" \) | while read -r arch; do
log_info "Extracting ${arch##*/}" log_info "Extracting archive: ${arch}"
local base
base=$(basename "${arch}")
local subdir="${dst}/${base%.*}"
mkdir -p "${subdir}" || { log_error "Failed to create subdirectory ${subdir}"; continue; }
case "${arch##*.}" in case "${arch##*.}" in
rar) unrar x -o- "${arch}" "${dst}" || log_error "unrar failed for ${arch}";; rar)
zip) unzip -o "${arch}" -d "${dst}" || log_error "unzip failed for ${arch}";; unrar x -o- "${arch}" "${subdir}" || { log_error "unrar failed for ${arch}"; continue; }
7z) 7z x "${arch}" -o"${dst}" || log_error "7z extraction failed for ${arch}";; ;;
zip)
unzip -o "${arch}" -d "${subdir}" || { log_error "unzip failed for ${arch}"; continue; }
;;
7z)
7z x "${arch}" -o"${subdir}" || { log_error "7z extraction failed for ${arch}"; continue; }
;;
esac esac
log_info "Archive ${arch} retained in source until ratio limits are reached."
done done
} }
@ -297,6 +331,7 @@ copy_files() {
} }
# process_copy: Validates directories, then copies/moves files from source to destination. # process_copy: Validates directories, then copies/moves files from source to destination.
# Optionally verifies integrity after transfer if CHECK_TRANSFER_INTEGRITY is "true".
process_copy() { process_copy() {
local id="$1" hash="$2" src="$3" dst="$4" local id="$1" hash="$2" src="$3" dst="$4"
if [[ ! -d "${src}" ]]; then if [[ ! -d "${src}" ]]; then
@ -331,6 +366,18 @@ process_copy() {
;; ;;
esac esac
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
if [[ "${CHECK_TRANSFER_INTEGRITY}" == "true" ]]; then
log_info "Verifying integrity of transferred files..."
local src_checksum target_checksum
src_checksum=$(find "${src}" -type f ! \( -iname "*.nfo" -o -iname "*.sfv" \) -exec md5sum {} \; | sort)
target_checksum=$(find "${dst}" -type f ! \( -iname "*.nfo" -o -iname "*.sfv" \) -exec md5sum {} \; | sort)
if diff <(echo "${src_checksum}") <(echo "${target_checksum}") >/dev/null; then
log_info "Integrity check passed."
else
log_error "Integrity check FAILED for ${src}"
return 1
fi
fi
log_info "Transfer completed successfully" log_info "Transfer completed successfully"
mark_processed "${hash}" mark_processed "${hash}"
else else
@ -402,7 +449,7 @@ main() {
percent_done=$(grep "Percent Done:" <<< "${info}" | awk '{gsub(/%/, ""); print $3 == "None" ? 0 : $3}') percent_done=$(grep "Percent Done:" <<< "${info}" | awk '{gsub(/%/, ""); print $3 == "None" ? 0 : $3}')
percent_done=${percent_done:-0} percent_done=${percent_done:-0}
# Extract Transmission reported directory and translate to local path. # Extract Transmission-reported directory and translate to local path.
local reported_dir local reported_dir
reported_dir=$(grep -i "Location:" <<< "${info}" | awk -F": " '{print $2}' | xargs) reported_dir=$(grep -i "Location:" <<< "${info}" | awk -F": " '{print $2}' | xargs)
local dir local dir
@ -412,7 +459,7 @@ main() {
dst=$(get_destination "${dir}") dst=$(get_destination "${dir}")
[[ -z "${warned_dirs["${dir}"]+x}" ]] && warned_dirs["${dir}"]=0 [[ -z "${warned_dirs["${dir}"]+x}" ]] && warned_dirs["${dir}"]=0
# Check if this source directory has already been processed. # Avoid processing the same directory more than once.
if [[ -n "${processed_source_dirs["${dir}"]+x}" ]]; then if [[ -n "${processed_source_dirs["${dir}"]+x}" ]]; then
log_info "Directory ${dir} has already been processed; skipping copy for torrent ${id}" log_info "Directory ${dir} has already been processed; skipping copy for torrent ${id}"
elif (( $(bc <<< "${percent_done} >= 100") )) && ! is_processed "${hash}"; then elif (( $(bc <<< "${percent_done} >= 100") )) && ! is_processed "${hash}"; then