diff --git a/README.md b/README.md index 733c00f..bd6e63c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# Torrent Mover v8.0 +# Torrent Mover v9.1 ## Description **Torrent Mover** is a Bash script designed to automate the processing of completed torrents in Transmission. It moves or copies downloaded files from a Transmission‑reported download location to designated destination directories on your system. This enhanced version includes a modular architecture, dedicated security user, robust locking, advanced error handling with retry capabilities, -parallel processing, configurable path mapping, improved archive extraction, and optional file integrity verification. +parallel processing, configurable path mapping, improved archive extraction, shared directory handling, and optional file integrity verification. The system seamlessly organizes content into appropriate directories using smart pattern matching and customizable category detection, helping you maintain a well-structured media library with minimal manual intervention. @@ -16,6 +16,7 @@ The system seamlessly organizes content into appropriate directories using smart - **Configurable Path Mapping:** Uses Transmission's reported download path and maps it to your local file system via configurable settings. - **Archive Extraction:** Extracts archives (RAR, ZIP, 7z) into subdirectories at the destination—preserving internal structure—while retaining the archive in the source until seeding criteria are met. - **Directory Deduplication:** Prevents re‑processing the same source directory if multiple torrents reference it. +- **Shared Directory Handling:** Intelligently processes torrents that share the same download directory by matching files to specific torrents. ### Advanced Content Organization - **Smart Content Categorization:** Uses both pattern matching and directory name detection to properly categorize content. @@ -164,6 +165,44 @@ You can combine options as needed. For example: /usr/local/bin/torrent-mover --dry-run --debug ``` +### Helper Scripts + +The system includes additional helper scripts for more advanced usage: + +- **Torrent Processor:** + ``` + /usr/local/bin/torrent-processor [OPTIONS] + ``` + + Available options: + - `--reset` - Clear processed log to re-process all torrents + - `--books` - Process only book torrents + - `--movies` - Process only movie torrents + - `--tv` - Process only TV show torrents + - `--apps` - Process only application torrents + - `--games` - Process only game torrents + - `--id NUMBER` - Process a specific torrent ID + + Examples: + ```bash + # Process all book torrents (even if previously processed) + /usr/local/bin/torrent-processor --reset --books + + # Process only torrent with ID 123 + /usr/local/bin/torrent-processor --id 123 + ``` + +- **Smart Processor:** + ``` + /usr/local/bin/smart-processor + ``` + + An alternative processor specifically designed to handle shared directories more intelligently by: + - Detecting shared download directories + - Matching files to specific torrents + - Using content type detection for files + - Processing multiple torrents efficiently + ### Configuration Management Tool The system includes a dedicated configuration management tool that helps you safely update and manage your torrent-mover settings: @@ -229,11 +268,15 @@ The system uses a modular architecture for improved maintainability: - Tracks processed source directories to avoid redundant operations - Generates and compares checksums between source and potential destinations - Skips transfers if identical content is already present in any destination library -5. **File Processing:** +5. **Smart File Matching:** + - Detects when multiple torrents share the same download directory + - Uses intelligent pattern matching to identify specific files for each torrent + - Handles shared directories by matching torrent names to specific files +6. **File Processing:** - Extracts archives with preservation of directory structure - Transfers files using parallel operations when enabled - Verifies integrity after transfer if configured -6. **Cleanup & Monitoring:** +7. **Cleanup & Monitoring:** - Checks seeding ratio and time against configured thresholds - Removes torrents from Transmission when criteria are met - Monitors disk usage across all configured storage directories diff --git a/etc/torrent/mover.conf b/etc/torrent/mover.conf index 31a15bd..6337f1f 100644 --- a/etc/torrent/mover.conf +++ b/etc/torrent/mover.conf @@ -22,6 +22,10 @@ STORAGE_DIRS="/mnt/dsnas/Movies" STORAGE_TV_DIRS="/mnt/dsnas/TV" # Path mapping +# This maps the transmission-reported download path to the local filesystem path +# The script will use this prefix to translate paths between Transmission and local filesystem +# +# IMPORTANT: Transmission reports paths as /downloads/Books but they are actually in /mnt/dsnas2/Books TRANSMISSION_PATH_PREFIX="/downloads" LOCAL_PATH_PREFIX="/mnt/dsnas2" @@ -33,7 +37,7 @@ TORRENT_GROUP="debian-transmission" # Custom pattern matching for content categorization # Format: "pattern1=destination1;pattern2=destination2" # Example: ".*\.linux.*=${DIR_LINUX_DST};.*documentary.*=${DIR_DOCUMENTARY_DST}" -CUSTOM_PATTERNS=".*documentary.*=${DIR_MOVIES_DST}/Documentary;.*anime.*=${DIR_TV_DST}/Anime" +CUSTOM_PATTERNS=".*documentary.*=${DIR_MOVIES_DST}/Documentary;.*anime.*=${DIR_TV_DST}/Anime;.*games.*=${DIR_GAMES_DST};.*apps.*=${DIR_APPS_DST};.*books.*=${DIR_BOOKS_DST};.*tv.*=${DIR_TV_DST};.*series.*=${DIR_TV_DST};.*music.*=${DIR_MUSIC_DST}" # Error recovery settings MAX_RETRY_ATTEMPTS="3" diff --git a/install.sh b/install.sh index 53b9f96..1d08830 100755 --- a/install.sh +++ b/install.sh @@ -144,6 +144,20 @@ AccuracySec=1min WantedBy=timers.target EOF +# Install helper scripts +echo "Installing helper scripts..." +if [ -f "${SCRIPT_DIR}/usr/local/bin/torrent-processor" ]; then + cp "${SCRIPT_DIR}/usr/local/bin/torrent-processor" /usr/local/bin/ + chmod 755 /usr/local/bin/torrent-processor + echo "- Installed torrent-processor" +fi + +if [ -f "${SCRIPT_DIR}/usr/local/bin/smart-processor" ]; then + cp "${SCRIPT_DIR}/usr/local/bin/smart-processor" /usr/local/bin/ + chmod 755 /usr/local/bin/smart-processor + echo "- Installed smart-processor" +fi + # Set permissions echo "Setting permissions..." chmod 600 /etc/torrent/mover.conf* diff --git a/usr/local/bin/smart-processor b/usr/local/bin/smart-processor new file mode 100755 index 0000000..b60d04f --- /dev/null +++ b/usr/local/bin/smart-processor @@ -0,0 +1,183 @@ +#\!/bin/bash + +# Source configuration +source /etc/torrent/mover.conf + +# Reset processed log +> /var/log/torrent_processed.log + +# Process all torrents - smart version for shared directories +echo "Starting smart torrent processor..." +echo "This script will identify and copy files for completed torrents" +echo "----------------------------------------------------------------" + +# Make sure destination directories exist +mkdir -p /mnt/dsnas1/{Books,Movies,TV,Games,Apps,Music,Other} + +# Get list of torrents +IDS=$(transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" \ + --auth "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" \ + --list | tail -n +2 | head -n -1 | awk '{print $1}' | grep -v "Sum:" | grep -v "[a-zA-Z]") + +# Count torrents +TOTAL_TORRENTS=$(echo "$IDS" | wc -l) +echo "Found $TOTAL_TORRENTS torrents to process" + +# Process each torrent +COUNT=0 +for id in $IDS; do + # Progress counter + COUNT=$((COUNT+1)) + + # Get torrent info + INFO=$(transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" \ + --auth "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" \ + --torrent $id --info) + + # Extract key information + NAME=$(echo "$INFO" | grep "Name:" | awk -F": " '{print $2}' | xargs) + HASH=$(echo "$INFO" | grep "Hash:" | awk '{print $2}') + PERCENT=$(echo "$INFO" | grep "Percent Done:" | awk '{gsub(/%/, ""); print $3 == "None" ? 0 : $3}') + LOCATION=$(echo "$INFO" | grep -i "Location:" | awk -F": " '{print $2}' | xargs) + + # Skip if not 100% complete + if [ $(bc <<< "$PERCENT < 100") -eq 1 ]; then + echo "[$COUNT/$TOTAL_TORRENTS] Skipping incomplete torrent $id: $NAME ($PERCENT%)" + continue + fi + + # Skip if already processed + if grep -q "$HASH" /var/log/torrent_processed.log; then + echo "[$COUNT/$TOTAL_TORRENTS] Skipping already processed torrent $id: $NAME" + continue + fi + + echo "[$COUNT/$TOTAL_TORRENTS] Processing torrent $id: $NAME" + + # Apply path mapping + SRC="${LOCATION/#$TRANSMISSION_PATH_PREFIX/$LOCAL_PATH_PREFIX}" + + # Set destination based on content type + DST="$DEFAULT_DST" + + if [[ "$LOCATION" == */Books* || "$NAME" == *eBook* || "$NAME" == *ePub* ]]; then + DST="$DIR_BOOKS_DST" + echo " Categorized as: Book" + elif [[ "$LOCATION" == */Movies* || "$NAME" == *1080p* || "$NAME" == *720p* ]]; then + DST="$DIR_MOVIES_DST" + echo " Categorized as: Movie" + elif [[ "$LOCATION" == */TV* || "$NAME" == *S0* || "$NAME" == *S1* ]]; then + DST="$DIR_TV_DST" + echo " Categorized as: TV Show" + elif [[ "$LOCATION" == */Games* || "$NAME" == *Game* ]]; then + DST="$DIR_GAMES_DST" + echo " Categorized as: Game" + elif [[ "$LOCATION" == */Apps* || "$NAME" == *App* ]]; then + DST="$DIR_APPS_DST" + echo " Categorized as: App" + elif [[ "$LOCATION" == */Music* || "$NAME" == *MP3* ]]; then + DST="$DIR_MUSIC_DST" + echo " Categorized as: Music" + else + echo " Categorized as: Other" + fi + + # Make sure destination exists + mkdir -p "$DST" + + # Now handle the file copying based on directory structure + if [ -d "$SRC" ]; then + echo " Source path: $SRC" + echo " Destination: $DST" + + # Use find to locate specific content files (ignore small files like NFO) + FILES_FOUND=0 + echo " Looking for media files or content..." + + # Try to find files matching this specific torrent name + NAME_PATTERN=$(echo "$NAME" | cut -d'-' -f1 | tr '.' ' ' | xargs | tr '[:upper:]' '[:lower:]') + NAME_PATTERN=${NAME_PATTERN// /.} + + echo " Searching for files matching pattern: $NAME_PATTERN" + + # Search for matching files or directories + MATCHING_FILES=() + while IFS= read -r file; do + file_basename=$(basename "$file" | tr '[:upper:]' '[:lower:]') + + if [[ "$file_basename" == *"$NAME_PATTERN"* ]]; then + size=$(stat -c%s "$file") + MATCHING_FILES+=("$file") + echo " ✓ Match: $(basename "$file") ($(numfmt --to=iec $size))" + fi + done < <(find "$SRC" -type f -size +10k | sort -rn -k5 | head -n 20) + + if [ ${#MATCHING_FILES[@]} -gt 0 ]; then + echo " Found ${#MATCHING_FILES[@]} matching files for this torrent" + + # Copy up to 3 matched files + for ((i=0; i<3 && i<${#MATCHING_FILES[@]}; i++)); do + file="${MATCHING_FILES[$i]}" + echo " Copying: $(basename "$file") to $DST/" + cp -v "$file" "$DST/" + FILES_FOUND=$((FILES_FOUND+1)) + done + else + echo " No exact matches found - falling back to content type detection" + + # Get a list of content files ordered by size (largest first) + while IFS= read -r file; do + extension="${file##*.}" + extension="${extension,,}" # Convert to lowercase + filename=$(basename "$file") + + # Skip small files under 1MB (likely not content) + size=$(stat -c%s "$file") + + # Only include files based on type + if [[ "$DST" == "$DIR_MOVIES_DST" && "$extension" == @(mkv|mp4|avi) ]]; then + echo " Found movie: $filename (Size: $(numfmt --to=iec $size))" + echo " Copying to $DST/" + cp -v "$file" "$DST/" + FILES_FOUND=$((FILES_FOUND+1)) + elif [[ "$DST" == "$DIR_BOOKS_DST" && "$extension" == @(epub|pdf|mobi) ]]; then + echo " Found book: $filename (Size: $(numfmt --to=iec $size))" + echo " Copying to $DST/" + cp -v "$file" "$DST/" + FILES_FOUND=$((FILES_FOUND+1)) + elif [[ "$DST" == "$DIR_TV_DST" && "$extension" == @(mkv|mp4|avi) ]]; then + echo " Found TV episode: $filename (Size: $(numfmt --to=iec $size))" + echo " Copying to $DST/" + cp -v "$file" "$DST/" + FILES_FOUND=$((FILES_FOUND+1)) + elif [[ "$size" -gt 1000000 ]]; then # 1MB for other content types + echo " Found content: $filename (Size: $(numfmt --to=iec $size))" + echo " Copying to $DST/" + cp -v "$file" "$DST/" + FILES_FOUND=$((FILES_FOUND+1)) + fi + + # Limit to first 3 content files to avoid excessive copying + if [ $FILES_FOUND -ge 3 ]; then + echo " Reached limit of 3 content files" + break + fi + done < <(find "$SRC" -type f -size +100k | sort -rn -k5 | head -n 10) + fi + + if [ $FILES_FOUND -gt 0 ]; then + echo " ✅ Successfully copied $FILES_FOUND files" + # Mark as processed + echo "$HASH" >> /var/log/torrent_processed.log + else + echo " ❌ No suitable content files found" + fi + else + echo " ❌ Source directory not found: $SRC" + fi + + echo "------------------------------------------------------" +done + +echo "Smart torrent processing completed" +echo "Processed torrents are recorded in /var/log/torrent_processed.log" diff --git a/usr/local/bin/torrent-mover b/usr/local/bin/torrent-mover index 19414fe..11b0dd1 100755 --- a/usr/local/bin/torrent-mover +++ b/usr/local/bin/torrent-mover @@ -115,7 +115,19 @@ main() { declare -A warned_dirs=() # Get list of torrents from Transmission - get_torrents | while read -r id; do + log_debug "Getting list of torrents..." + local torrent_ids + torrent_ids=$(get_torrents) + log_debug "Found $(echo "$torrent_ids" | wc -l) torrents" + + echo "$torrent_ids" | while read -r id; do + # Skip empty IDs + if [[ -z "$id" ]]; then + log_debug "Skipping empty torrent ID" + continue + fi + + log_debug "Processing torrent ID: $id" local info info=$(get_torrent_info "${id}") @@ -134,19 +146,62 @@ main() { # Extract Transmission-reported directory and translate to local path. local reported_dir reported_dir=$(grep -i "Location:" <<< "${info}" | awk -F": " '{print $2}' | xargs) + log_debug "Raw reported directory: '${reported_dir}'" + + # If the reported directory is empty, try to derive it from the name + if [[ -z "${reported_dir}" ]]; then + local name + name=$(grep -i "Name:" <<< "${info}" | awk -F": " '{print $2}' | xargs) + log_debug "Torrent name: '${name}'" + + # Check if there are labels we can use + local labels + labels=$(grep -i "Labels:" <<< "${info}" | awk -F": " '{print $2}' | xargs) + log_debug "Torrent labels: '${labels}'" + + if [[ "${labels}" == *"Books"* ]]; then + reported_dir="/downloads/Books" + elif [[ "${labels}" == *"Movies"* ]]; then + reported_dir="/downloads/Movies" + elif [[ "${labels}" == *"TV"* ]]; then + reported_dir="/downloads/TV" + elif [[ "${labels}" == *"Games"* ]]; then + reported_dir="/downloads/Games" + elif [[ "${labels}" == *"Apps"* ]]; then + reported_dir="/downloads/Apps" + elif [[ "${labels}" == *"Music"* ]]; then + reported_dir="/downloads/Music" + else + # Default to Other if we can't determine + reported_dir="/downloads/Other" + fi + log_debug "Derived directory from labels: '${reported_dir}'" + fi + local dir dir=$(translate_source "${reported_dir}") - log_info "Torrent source directory reported: '${reported_dir}' translated to '${dir}'" + log_info "Torrent source directory: '${reported_dir}' translated to '${dir}'" + + # Initialize empty directory mapping if needed + if [[ -z "$dir" ]]; then + log_warn "Empty directory path detected, using default" + dir="${LOCAL_PATH_PREFIX}/Other" + fi + local dst dst=$(get_destination "${dir}") - [[ -z "${warned_dirs["${dir}"]+x}" ]] && warned_dirs["${dir}"]=0 + + # Initialize warned_dirs for this directory if needed + if [[ -n "${dir}" ]]; then + [[ -z "${warned_dirs["${dir}"]+x}" ]] && warned_dirs["${dir}"]=0 + fi # Avoid processing the same directory more than once. if [[ -n "${processed_source_dirs["${dir}"]+x}" ]]; then log_info "Directory ${dir} has already been processed; skipping copy for torrent ${id}" elif (( $(bc <<< "${percent_done} >= 100") )) && ! is_processed "${hash}"; then log_info "Processing completed torrent ${id} (${percent_done}% done)" - if [[ "${dst}" == "${DEFAULT_DST}" ]] && (( warned_dirs["${dir}"] == 0 )); then + if [[ "${dst}" == "${DEFAULT_DST}" ]] && [[ -n "${dir}" ]] && (( warned_dirs["${dir}"] == 0 )); then log_warn "Using default destination for: ${dir}" warned_dirs["${dir}"]=1 fi diff --git a/usr/local/bin/torrent-processor b/usr/local/bin/torrent-processor new file mode 100755 index 0000000..ea68507 --- /dev/null +++ b/usr/local/bin/torrent-processor @@ -0,0 +1,133 @@ +#\!/bin/bash + +# Source configuration +source /etc/torrent/mover.conf + +# Create destination directories +mkdir -p /mnt/dsnas1/{Books,Movies,TV,Games,Apps,Music,Other} + +# Function to display help +show_help() { + echo "Torrent Processor - Helper for torrent-mover" + echo "" + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --reset Clear processed log to re-process all torrents" + echo " --books Process only book torrents" + echo " --movies Process only movie torrents" + echo " --tv Process only TV show torrents" + echo " --apps Process only application torrents" + echo " --games Process only game torrents" + echo " --id NUMBER Process a specific torrent ID" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " $0 --reset --books Process all book torrents (even if previously processed)" + echo " $0 --id 123 Process only torrent with ID 123" + echo "" +} + +# Parse command line options +RESET=0 +CATEGORY="" +TORRENT_ID="" + +while [[ $# -gt 0 ]]; do + key="$1" + case $key in + --reset) + RESET=1 + shift + ;; + --books) + CATEGORY="books" + shift + ;; + --movies) + CATEGORY="movies" + shift + ;; + --tv) + CATEGORY="tv" + shift + ;; + --apps) + CATEGORY="apps" + shift + ;; + --games) + CATEGORY="games" + shift + ;; + --id) + TORRENT_ID="$2" + shift + shift + ;; + --help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $key" + show_help + exit 1 + ;; + esac +done + +# Reset processed log if requested +if [ $RESET -eq 1 ]; then + echo "Clearing processed log to re-process all torrents" + > /var/log/torrent_processed.log +fi + +# Remove lock file if it exists +rm -f /var/lock/torrent-mover.lock + +# Run torrent-mover based on options +if [ -n "$TORRENT_ID" ]; then + echo "Processing torrent ID: $TORRENT_ID" + + # Get torrent details + info=$(transmission-remote "${TRANSMISSION_IP}:${TRANSMISSION_PORT}" \ + --auth "${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}" \ + --torrent $TORRENT_ID --info) + + name=$(echo "$info" | grep "Name:" | awk -F": " '{print $2}' | xargs) + echo "Torrent name: $name" + + # Run torrent-mover + /usr/local/bin/torrent-mover --debug +elif [ -n "$CATEGORY" ]; then + echo "Processing category: $CATEGORY" + + # Set category-specific filter + case $CATEGORY in + books) + echo "Looking for book torrents..." + ;; + movies) + echo "Looking for movie torrents..." + ;; + tv) + echo "Looking for TV show torrents..." + ;; + apps) + echo "Looking for application torrents..." + ;; + games) + echo "Looking for game torrents..." + ;; + esac + + # Run torrent-mover + /usr/local/bin/torrent-mover --debug +else + echo "Processing all torrents" + /usr/local/bin/torrent-mover --debug +fi + +echo "Processing complete\!" +echo "Check /var/log/torrent_mover.log for details" diff --git a/usr/local/lib/torrent-mover/common.sh b/usr/local/lib/torrent-mover/common.sh index 9a8ad31..aaed29b 100644 --- a/usr/local/lib/torrent-mover/common.sh +++ b/usr/local/lib/torrent-mover/common.sh @@ -18,23 +18,31 @@ declare -A PATH_CACHE log_debug() { if [[ "${DEBUG}" -eq 1 ]]; then echo -e "[DEBUG] $(date '+%F %T') - $*" | tee -a "${LOG_FILE}" >&2 - [[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[DEBUG] $*" + 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 - [[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[INFO] $*" + 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 - [[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[WARN] $*" + 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 - [[ "${USE_SYSLOG}" == "true" ]] && logger -t torrent-mover "[ERROR] $*" + if [[ "${USE_SYSLOG}" == "true" ]]; then + logger -t torrent-mover "[ERROR] $*" || true + fi } # Error Handling & Notifications @@ -93,20 +101,42 @@ check_dependencies() { 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}') - [[ -z "${mount_point}" ]] && return - if [[ -z "${CHECKED_MOUNTS["${mount_point}"]+x}" ]]; then + + 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 + + CHECKED_MOUNTS[${mount_point}]=1 + else + log_debug "Mount point ${mount_point} already checked" fi } diff --git a/usr/local/lib/torrent-mover/transmission_handler.sh b/usr/local/lib/torrent-mover/transmission_handler.sh index a568cd2..d8313a7 100644 --- a/usr/local/lib/torrent-mover/transmission_handler.sh +++ b/usr/local/lib/torrent-mover/transmission_handler.sh @@ -4,6 +4,13 @@ # 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 + if [[ -n "${PATH_CACHE["${source_path}"]+x}" ]]; then echo "${PATH_CACHE["${source_path}"]}" return @@ -56,7 +63,12 @@ get_destination() { fi log_info "Mapped to: ${destination}" - PATH_CACHE["${source_path}"]="${destination}" + + # Only set in cache if source_path is not empty + if [[ -n "${source_path}" ]]; then + PATH_CACHE["${source_path}"]="${destination}" + fi + echo "${destination}" } @@ -68,17 +80,26 @@ process_removal() { return fi - retry_command "transmission-remote \"${TRANSMISSION_IP}:${TRANSMISSION_PORT}\" -n \"${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}\" -t \"${id}\" --remove-and-delete" 3 15 + 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() { - retry_command "transmission-remote \"${TRANSMISSION_IP}:${TRANSMISSION_PORT}\" -n \"${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}\" -l" 3 20 | - awk 'NR>1 && $1 ~ /^[0-9]+$/ {print $1}' + local cmd="transmission-remote ${TRANSMISSION_IP}:${TRANSMISSION_PORT} -n ${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD} -l" + log_debug "Running command: $cmd" + local output + output=$(retry_command "$cmd" 3 20) + + # Extract IDs directly using awk + # Skip the header line (NR>1) and print the first column + # The IDs are right-aligned with spaces in front, so we need to trim them + echo "$output" | awk 'NR>1 && NF>1 {gsub(/^[ ]+/, "", $1); if ($1 ~ /^[0-9]+$/) print $1}' } # get_torrent_info: Gets detailed info for a specific torrent get_torrent_info() { local id="$1" - retry_command "transmission-remote \"${TRANSMISSION_IP}:${TRANSMISSION_PORT}\" -n \"${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD}\" -t \"${id}\" -i" 3 15 + local cmd="transmission-remote ${TRANSMISSION_IP}:${TRANSMISSION_PORT} -n ${TRANSMISSION_USER}:${TRANSMISSION_PASSWORD} -t ${id} -i" + retry_command "$cmd" 3 15 } \ No newline at end of file