- Completely rewrote retry_command to show detailed output on each attempt - Added direct Transmission connectivity test before using retry logic - Added line-by-line analysis of Transmission command output - Added test fallback ID in dry-run mode to verify downstream processing - Added connection parameter logging (with redacted password) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
201 lines
7.4 KiB
Bash
201 lines
7.4 KiB
Bash
#!/bin/bash
|
|
# Transmission-related functions for torrent-mover
|
|
|
|
# get_destination: Maps a source directory to a destination directory based on keywords and patterns
|
|
get_destination() {
|
|
local source_path="$1"
|
|
|
|
# Check if source_path is valid before accessing the array
|
|
if [[ -z "${source_path}" ]]; then
|
|
log_warn "Empty source path provided to get_destination"
|
|
return "${DEFAULT_DST}"
|
|
fi
|
|
|
|
# Check if path is already in the cache
|
|
if [[ -n "${PATH_CACHE["${source_path}"]+x}" ]]; then
|
|
local cached_destination="${PATH_CACHE["${source_path}"]}"
|
|
log_debug "Using cached destination for ${source_path}: ${cached_destination}"
|
|
echo "${cached_destination}"
|
|
return
|
|
fi
|
|
|
|
# Skip recursive path analysis - only log once
|
|
if [[ "${source_path}" =~ ^/mnt/dsnas1/ ]]; then
|
|
# Already in destination format, return as is
|
|
log_debug "Path already in destination format: ${source_path}"
|
|
PATH_CACHE["${source_path}"]="${source_path}"
|
|
echo "${source_path}"
|
|
return
|
|
fi
|
|
|
|
# For paths in dsnas2, check if they map to same structure in dsnas1
|
|
if [[ "${source_path}" =~ ^/mnt/dsnas2/ ]]; then
|
|
local dir_suffix="${source_path#/mnt/dsnas2/}"
|
|
local potential_dest="/mnt/dsnas1/${dir_suffix}"
|
|
|
|
# If the directories match exactly in structure, only on different mounts,
|
|
# return the source to avoid needless copying
|
|
if [[ -d "${potential_dest}" ]]; then
|
|
log_debug "Path maps to same structure on different mount: ${source_path} -> ${source_path}"
|
|
PATH_CACHE["${source_path}"]="${source_path}"
|
|
echo "${source_path}"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
log_info "Analyzing path: ${source_path}"
|
|
local destination="${DEFAULT_DST}"
|
|
|
|
# Match using custom patterns from config file if they exist
|
|
if [[ -n "${CUSTOM_PATTERNS}" ]]; then
|
|
log_debug "Using custom patterns from config..."
|
|
# Parse and apply each pattern
|
|
IFS=';' read -ra PATTERN_ARRAY <<< "${CUSTOM_PATTERNS}"
|
|
for pattern in "${PATTERN_ARRAY[@]}"; do
|
|
IFS='=' read -ra PARTS <<< "${pattern}"
|
|
if [[ "${#PARTS[@]}" -eq 2 ]]; then
|
|
local regex="${PARTS[0]}"
|
|
local dest="${PARTS[1]}"
|
|
if [[ "${source_path,,}" =~ ${regex,,} ]]; then
|
|
log_info "Custom pattern match: ${regex} -> ${dest}"
|
|
destination="${dest}"
|
|
break
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# If no custom pattern matched, use default category mapping
|
|
if [[ "${destination}" == "${DEFAULT_DST}" ]]; then
|
|
case "${source_path,,}" in
|
|
*games*) destination="${DIR_GAMES_DST}";;
|
|
*apps*|*applications*|*programs*|*software*) destination="${DIR_APPS_DST}";;
|
|
*movies*|*film*|*video*) destination="${DIR_MOVIES_DST}";;
|
|
*books*|*ebook*|*pdf*|*epub*) destination="${DIR_BOOKS_DST}";;
|
|
*tv*|*series*|*episode*)
|
|
if [[ -n "${DIR_TV_DST}" ]]; then
|
|
destination="${DIR_TV_DST}"
|
|
else
|
|
destination="${DIR_MOVIES_DST}"
|
|
fi
|
|
;;
|
|
*music*|*audio*|*mp3*|*flac*)
|
|
if [[ -n "${DIR_MUSIC_DST}" ]]; then
|
|
destination="${DIR_MUSIC_DST}"
|
|
else
|
|
destination="${DEFAULT_DST}"
|
|
fi
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
log_info "Mapped to: ${destination}"
|
|
|
|
# Only set in cache if source_path is not empty
|
|
if [[ -n "${source_path}" ]]; then
|
|
PATH_CACHE["${source_path}"]="${destination}"
|
|
fi
|
|
|
|
echo "${destination}"
|
|
}
|
|
|
|
# process_removal: Removes a torrent via Transmission.
|
|
process_removal() {
|
|
local id="$1"
|
|
if (( DRY_RUN )); then
|
|
log_info "[DRY RUN] Would remove torrent ${id}"
|
|
return
|
|
fi
|
|
|
|
local cmd="transmission-remote ${TRANSMISSION_IP}:${TRANSMISSION_PORT} -n ${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD} -t ${id} --remove-and-delete"
|
|
retry_command "$cmd" 3 15
|
|
}
|
|
|
|
# get_torrents: Retrieves a list of torrents from Transmission
|
|
get_torrents() {
|
|
# Log connection parameters (redacted password)
|
|
log_info "Transmission connection parameters:"
|
|
log_info " IP: ${TRANSMISSION_IP}:${TRANSMISSION_PORT}"
|
|
log_info " Username: ${TRANSMISSION_USER}"
|
|
log_info " Password: [redacted]"
|
|
|
|
# Try a direct command without using retry_command to get clearer error messages
|
|
log_info "Direct transmission-remote access test:"
|
|
local test_output
|
|
test_output=$(transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" -n "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" -l 2>&1)
|
|
local test_exit=$?
|
|
if [[ $test_exit -ne 0 ]]; then
|
|
log_error "Direct transmission-remote test failed with exit code: $test_exit"
|
|
log_error "Error output: $test_output"
|
|
# Continue anyway to see retry attempt logs
|
|
else
|
|
log_info "Direct transmission-remote test succeeded"
|
|
fi
|
|
|
|
# Execute the actual command with retries
|
|
local real_cmd="transmission-remote ${TRANSMISSION_IP}:${TRANSMISSION_PORT} -n ${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD} -l"
|
|
local output
|
|
output=$(retry_command "$real_cmd" 3 20)
|
|
|
|
# Line-by-line raw output inspection (debugging)
|
|
log_info "Raw command output detailed analysis:"
|
|
if [[ -z "$output" ]]; then
|
|
log_error "Command produced EMPTY output"
|
|
else
|
|
log_info "Output length: $(echo "$output" | wc -l) lines"
|
|
echo "$output" | while IFS= read -r line; do
|
|
log_info " LINE: '$line'"
|
|
done
|
|
fi
|
|
|
|
# Extract IDs directly using awk with detailed debugging
|
|
log_info "Extracting torrent IDs from output..."
|
|
local line_num=0
|
|
local found_ids=0
|
|
echo "$output" | while IFS= read -r line; do
|
|
line_num=$((line_num + 1))
|
|
# Skip header line
|
|
if [[ $line_num -eq 1 ]]; then
|
|
log_info " Skipping header: '$line'"
|
|
continue
|
|
fi
|
|
# Check for torrent ID in first column
|
|
local potential_id
|
|
potential_id=$(echo "$line" | awk '{gsub(/^[ ]+/, "", $1); print $1}')
|
|
log_info " Line $line_num: potential ID '$potential_id'"
|
|
if [[ "$potential_id" =~ ^[0-9]+$ ]]; then
|
|
log_info " Found valid ID: $potential_id"
|
|
found_ids=$((found_ids + 1))
|
|
echo "$potential_id"
|
|
else
|
|
log_info " Not a valid ID: '$potential_id'"
|
|
fi
|
|
done | tee /tmp/torrent_ids.txt
|
|
|
|
# Read back the file to get around pipe subshell issues
|
|
local torrent_ids
|
|
torrent_ids=$(cat /tmp/torrent_ids.txt)
|
|
rm -f /tmp/torrent_ids.txt
|
|
|
|
# Check if we found any torrents
|
|
if [[ -z "$torrent_ids" ]]; then
|
|
log_error "NO TORRENT IDs FOUND in transmission output"
|
|
else
|
|
log_info "Found torrent IDs: $torrent_ids"
|
|
fi
|
|
|
|
# Fallback to hardcoded ID for testing if nothing found
|
|
if [[ -z "$torrent_ids" && "${DRY_RUN}" -eq 1 ]]; then
|
|
log_info "DRY RUN MODE: Adding test torrent ID 1 for debugging"
|
|
echo "1"
|
|
else
|
|
echo "$torrent_ids"
|
|
fi
|
|
}
|
|
|
|
# get_torrent_info: Gets detailed info for a specific torrent
|
|
get_torrent_info() {
|
|
local id="$1"
|
|
local cmd="transmission-remote ${TRANSMISSION_IP}:${TRANSMISSION_PORT} -n ${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD} -t ${id} -i"
|
|
retry_command "$cmd" 3 15
|
|
} |