#!/bin/bash #------------------------------------------------------------------------------ # MY_LIBRARY.sh - Utility functions for logging, environment detection, and file operations # # Description: # A collection of utility functions used for logging messages, identifying the # operating environment, managing file operations like safe deletion and linking # directories, and handling crashed lock files for TV devices and shows. Designed # to be sourced by other scripts for shared functionality. # # Globals: # nEnvType - Integer representing the environment type (e.g., Cygwin, Linux) (RW) # MIN_DISK_FREE_SPACE - Minimum disk free space threshold in bytes (RO) # SAFE_DELETE_DIR - Directory for safe deletion of files (RO) # DEBUG - Flag for enabling debug logging (RW) # CALL_STDIN_FD - File descriptor for redirected stdin (RO) # CALL_STDOUT_FD - File descriptor for redirected stdout (RO) # CALL_STDERR_FD - File descriptor for redirected stderr (RO) # # Arguments: # None (script is meant to be sourced) # # Outputs: # Writes informational, warning, error, and debug messages to redirected stdout/stderr. # Writes to rollback logs during safe deletion. # # Exit Codes: # 0 - Success (functions generally return 0 on success) # 1 - Failure (functions exit with 1 on error, e.g., missing directories, invalid arguments) # # Side Effects: # Redirects stdin, stdout, and stderr to custom file descriptors. # Creates directories and files for safe deletion and rollback logging. # Deletes or moves files during safe deletion or lock removal. # Modifies the environment variable `nEnvType` during environment detection. # Executes system commands like `cmd.exe`, `ln`, `mv`, `rm`, `ps`, and `find`. #------------------------------------------------------------------------------ # -1 : Invalid # 1 : Cygwin # 2 : Linux # 3 : Linux WSL # 4 : macOS declare -i nEnvType=-1 declare -r -i MIN_DISK_FREE_SPACE=$(( 100 * 1024 * 1024 )) declare -i DEBUG declare SAFE_DELETE_DIR : ${SAFE_DELETE_DIR:=/cygdrive/p/DELETED/} : ${DEBUG:=0} readonly SAFE_DELETE_DIR readonly CALL_STDIN_FD=10 readonly CALL_STDOUT_FD=11 readonly CALL_STDERR_FD=12 # Redirect stdin, stdout, and stderr to custom FDs using file descriptor duplication # Save the original file descriptors if ! eval "exec ${CALL_STDIN_FD}<&0"; then echo "ERROR: Failed to redirect stdin to FD ${CALL_STDIN_FD}" >&2 exit 1 fi if ! eval "exec ${CALL_STDOUT_FD}>&1"; then echo "ERROR: Failed to redirect stdout to FD ${CALL_STDOUT_FD}" >&2 exit 1 fi if ! eval "exec ${CALL_STDERR_FD}>&2"; then echo "ERROR: Failed to redirect stderr to FD ${CALL_STDERR_FD}" >&2 exit 1 fi #============================================================================== # EchoN - Outputs a message without a newline to redirected stdout #------------------------------------------------------------------------------ # Description: # Prints a formatted message with timestamp and program ID to the redirected # stdout file descriptor without a trailing newline. # # Globals: # sProgramId - Program identifier for logging (RW) # CALL_STDOUT_FD - File descriptor for redirected stdout (RO) # # Arguments: # $@ - Message to print # # Outputs: # Writes formatted message to redirected stdout (CALL_STDOUT_FD). # # Exit Codes: # None (function does not exit the script) # # Side Effects: # None #============================================================================== EchoN() { printf '%s|%-8s|%s' \ "$(date '+%Y-%m-%d %H:%M:%S')" \ "${sProgramId:-*UNSET*}" \ "$*" >&${CALL_STDOUT_FD} } #============================================================================== # Echo - Outputs a message with a newline to redirected stdout #------------------------------------------------------------------------------ # Description: # Prints a formatted message with timestamp and program ID to the redirected # stdout file descriptor, followed by a newline. # # Globals: # sProgramId - Program identifier for logging (RW) # CALL_STDOUT_FD - File descriptor for redirected stdout (RO) # # Arguments: # $@ - Message to print # # Outputs: # Writes formatted message with newline to redirected stdout (CALL_STDOUT_FD). # # Exit Codes: # None (function does not exit the script) # # Side Effects: # None #============================================================================== Echo() { EchoN "$*" printf '\n' >&${CALL_STDOUT_FD} } #============================================================================== # EchoD - Outputs a debug message to redirected stderr #------------------------------------------------------------------------------ # Description: # Prints a debug message with a "[DEBUG]" prefix to the redirected stderr file # descriptor. # # Globals: # CALL_STDERR_FD - File descriptor for redirected stderr (RO) # # Arguments: # $@ - Debug message to print # # Outputs: # Writes debug message to redirected stderr (CALL_STDERR_FD). # # Exit Codes: # None (function does not exit the script) # # Side Effects: # None #============================================================================== EchoD() { Echo "🛠 [DEBUG] $*" >&${CALL_STDERR_FD} } #============================================================================== # EchoW - Outputs a warning message to redirected stdout #------------------------------------------------------------------------------ # Description: # Prints a warning message with a "[WARNING]" prefix to the redirected stdout # file descriptor. # # Globals: # CALL_STDOUT_FD - File descriptor for redirected stdout (RO) # # Arguments: # $@ - Warning message to print # # Outputs: # Writes warning message to redirected stdout (CALL_STDOUT_FD). # # Exit Codes: # None (function does not exit the script) # # Side Effects: # None #============================================================================== EchoW() { Echo "⚠️ [WARNING] $*" >&${CALL_STDOUT_FD} } #============================================================================== # EchoE - Outputs an error message to redirected stderr #------------------------------------------------------------------------------ # Description: # Prints an error message with a "[ERROR]" prefix to the redirected stderr file # descriptor. # # Globals: # CALL_STDERR_FD - File descriptor for redirected stderr (RO) # # Arguments: # $@ - Error message to print # # Outputs: # Writes error message to redirected stderr (CALL_STDERR_FD). # # Exit Codes: # None (function does not exit the script) # # Side Effects: # None #============================================================================== EchoE() { Echo "❌ [ERROR] $*" >&${CALL_STDERR_FD} } #============================================================================== # ExitWithMsg - Outputs an error message and exits the script #------------------------------------------------------------------------------ # Description: # Prints an error message to redirected stderr and exits the script with a # failure status. # # Globals: # CALL_STDERR_FD - File descriptor for redirected stderr (RO) # # Arguments: # $@ - Error message to print # # Outputs: # Writes error message to redirected stderr (CALL_STDERR_FD). # # Exit Codes: # 1 - Failure (always exits with 1) # # Side Effects: # Terminates the script execution. # Enables `set -e` to ensure immediate exit on any subsequent error. #============================================================================== ExitWithMsg() { set -e echo >&${CALL_STDERR_FD} EchoE "$*" >&${CALL_STDERR_FD} exit 1 } #============================================================================== # IdentifyEnvironment - Detects the operating environment #------------------------------------------------------------------------------ # Description: # Identifies the operating environment (Cygwin, Linux, WSL, etc.) and sets the # global `nEnvType` variable accordingly. # # Globals: # nEnvType - Integer representing the environment type (RW) # # Arguments: # None # # Outputs: # None (sets global variable `nEnvType`) # # Exit Codes: # 0 - Success (always returns 0) # # Side Effects: # Modifies the global `nEnvType` variable based on the detected environment. # Executes `uname` to determine the environment. #============================================================================== IdentifyEnvironment() { if [[ "$(uname -o)" == Cygwin ]]; then nEnvType=1 elif [[ "$(uname -r)" == *-microsoft-* ]]; then nEnvType=3 elif [[ "$(uname -o)" == "GNU/Linux" ]]; then nEnvType=2 else nEnvType=-1 fi } #============================================================================== # LinkDirectory - Creates a symbolic link between directories #------------------------------------------------------------------------------ # Description: # Creates a symbolic link from one directory to another, with platform-specific # handling for Cygwin, WSL, and Linux environments. # # Globals: # nEnvType - Integer representing the environment type (RW) # DEBUG - Flag for enabling debug logging (RW) # # Arguments: # $1 - Source directory path (where the link will be created) # $2 - Target directory path (what the link points to) # # Outputs: # Writes error messages to redirected stderr via `EchoE` if validation fails. # Writes debug messages to redirected stderr via `EchoD` if DEBUG=1. # # Exit Codes: # 1 - Failure (via ExitWithMsg on invalid arguments or environment) # # Side Effects: # Calls `IdentifyEnvironment` if `nEnvType` is not set. # Creates a symbolic link using `cmd.exe` (Cygwin/WSL) or `ln` (Linux). # Executes external commands (`cmd.exe`, `ln`). #============================================================================== LinkDirectory() { local -r sLink="" local -r sTarget="" # Validate arguments [[ "$1" != "/cygdrive/"* || "$2" != "/cygdrive/"* ]] && ExitWithMsg "LinkDirectory: 1='$1' 2='$2'" [[ ! -d "$2" ]] && ExitWithMsg "LinkDirectory: directory does not exist or is not accessible: '$2'" # Ensure environment is identified [[ ${nEnvType} -eq -1 ]] && IdentifyEnvironment # 1 : Cygwin # 2 : Linux # 3 : Linux WSL # 4 : macOS case ${nEnvType} in 1|3) sLink="$(tr -s / <<< "${1:10:1}:${1:11}")" sTarget="$(tr -s / <<< "${2:10:1}:${2:11}")" cmd.exe <<< "mklink /D \"${sLink//\//\\\\}\" \"${sTarget//\//\\\\}\"" >/dev/null (( DEBUG )) && EchoD "LinkDirectory: Created link '${sLink}' -> '${sTarget}' using cmd.exe" ;; 2) ln -s "$2" "$1" || EchoE "LinkDirectory: Failed to create link '$1' -> '$2'" (( DEBUG )) && EchoD "LinkDirectory: Created link '$1' -> '$2' using ln" ;; *) ExitWithMsg "LinkDirectory: Unsupported environment type: ${nEnvType}" ;; esac } #============================================================================== # SafeDelete - Safely moves a file or link to a trash directory with rollback logging #------------------------------------------------------------------------------ # Description: # Moves a file or symbolic link to a designated trash directory, renaming it with # a timestamp and random suffix, and logs the operation for potential rollback. # # Globals: # SAFE_DELETE_DIR - Directory for safe deletion of files (RO) # lExecute - Flag to control execution (RW) # # Arguments: # $1 - Source file or link to delete (prefix with '@' for quiet mode) # # Outputs: # Writes informational messages to redirected stdout via `Echo` (unless quiet). # Writes error messages to redirected stderr via `EchoE` on failure. # Appends to rollback log file in SAFE_DELETE_DIR. # # Exit Codes: # 0 - Success (file/link moved successfully) # 1 - Failure (invalid arguments, missing directories, or move failure) # # Side Effects: # Creates directories under SAFE_DELETE_DIR if they don’t exist. # Moves the source file/link to SAFE_DELETE_DIR. # Appends an entry to the rollback log file. # Executes external commands (`mkdir`, `mv`). #============================================================================== SafeDelete() { local -r sSourceFile="$1" local -i lQuiet=0 local -r sBaseName="" local -r sTimeStamp="" local -r sNewName="" local -r sCommand="" # Validate SAFE_DELETE_DIR [[ -z "${SAFE_DELETE_DIR}" ]] && ExitWithMsg "SAFE_DELETE_DIR is not defined. Cannot proceed" # Determine if quiet mode is enabled and strip the '@' prefix if present [[ "${sSourceFile:0:1}" == "@" ]] && lQuiet=1 [[ ${lQuiet} -eq 1 ]] && sSourceFile="${sSourceFile:1}" # Validate source file if [[ -z "${sSourceFile}" ]]; then [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: No source item defined" return 1 elif [[ ! -e "${sSourceFile}" && ! -L "${sSourceFile}" ]]; then [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: '${sSourceFile}' does not exist" return 1 fi # Ensure trash directory exists [[ ! -d "${SAFE_DELETE_DIR}" ]] && mkdir -p -- "${SAFE_DELETE_DIR}" if [[ ! -d "${SAFE_DELETE_DIR}" ]]; then [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: Trash bin '${SAFE_DELETE_DIR}' does not exist" return 1 fi # Prepare new filename with timestamp and random suffix sBaseName="$(basename -- "${sSourceFile}")" sTimeStamp="$(date +"%Y%m%d%H%M%s")" sNewName="${sBaseName}_${sTimeStamp}_${RANDOM}" # Log the operation (unless quiet) [[ ${lQuiet} -eq 0 ]] && Echo " '${sSourceFile}' -> '${SAFE_DELETE_DIR}/${sNewName}'" # Determine the command (echo or execute) sCommand=$([[ ${lExecute} -eq 1 ]] && echo "" || echo "echo") # Move the file or link if [[ -L "${sSourceFile}" ]]; then [[ ! -d "${SAFE_DELETE_DIR}/.Links" ]] && mkdir -p -- "${SAFE_DELETE_DIR}/.Links" if [[ -d "${SAFE_DELETE_DIR}/.Links" ]]; then ${sCommand} mv -- "${sSourceFile}" "${SAFE_DELETE_DIR}/.Links/${sNewName}" || { [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: Failed to move link '${sSourceFile}' to '${SAFE_DELETE_DIR}/.Links/${sNewName}'" return 1 } else [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: Failed to create '${SAFE_DELETE_DIR}/.Links' directory" return 1 fi else ${sCommand} mv -- "${sSourceFile}" "${SAFE_DELETE_DIR}/${sNewName}" || { [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: Failed to move file '${sSourceFile}' to '${SAFE_DELETE_DIR}/${sNewName}'" return 1 } fi # Log the operation for rollback if ! echo "${sTimeStamp}|${sSourceFile}|${SAFE_DELETE_DIR}/${sNewName}" >> "${SAFE_DELETE_DIR}/.RollBack.log"; then [[ ${lQuiet} -eq 0 ]] && EchoE "SafeDelete: Failed to write to rollback log '${SAFE_DELETE_DIR}/.RollBack.log'" return 1 fi return 0 } #============================================================================== # RemoveCrashedLockTvDeviceAll - Removes crashed lock files for all TV devices #------------------------------------------------------------------------------ # Description: # Searches for '.IgnoreThisDeviceWhileMoving*' lock files under TV directories # and removes them if the associated process is no longer running. # # Globals: # None # # Arguments: # $1 - Verbosity level (0 for quiet, >0 for verbose output) # # Outputs: # Writes informational messages to redirected stdout via `Echo` if verbose. # # Exit Codes: # 0 - Success (always returns 0) # # Side Effects: # Deletes lock files if the associated process is not running. # Executes external commands (`find`, `ps`, `rm`). #============================================================================== RemoveCrashedLockTvDeviceAll() { local -r -i lVerbose="$1" local sProcessId="" (( lVerbose )) && Echo "RemoveCrashedLockTvDeviceAll" (( lVerbose )) && Echo " Looking for '.IgnoreThisDeviceWhileMoving*' files under '/cygdrive/*/MediaLibraryExtension/TV/'..." while IFS= read -r lock_file; do [[ -z "${lock_file}" ]] && continue (( lVerbose )) && Echo " '${lock_file}'" sProcessId="${lock_file##*.}" if [[ -n "${sProcessId}" ]]; then (( lVerbose )) && Echo " Checking process id ${sProcessId}" if ! ps -p "${sProcessId}" >/dev/null; then rm "${lock_file}" && Echo " Removed" fi fi done < <( find /cygdrive/*/MediaLibraryExtension/TV/ -type f -iname '.IgnoreThisDeviceWhileMoving*' 2>/dev/null ) (( lVerbose )) && Echo " Done" } #============================================================================== # RemoveCrashedLockTvDevice - Removes crashed lock files for a specific TV device #------------------------------------------------------------------------------ # Description: # Removes '.IgnoreThisDeviceWhileMoving.*' lock files for a specific mount point # if the associated process is no longer running. # # Globals: # None # # Arguments: # $1 - Mount point path to check for lock files # # Outputs: # None # # Exit Codes: # 0 - Success (always returns 0) # # Side Effects: # Deletes lock files if the associated process is not running. # Executes external commands (`ls`, `ps`, `rm`). #============================================================================== RemoveCrashedLockTvDevice() { local -r sMountPoint="$1" local -a processes=() local nProcess="" if [[ -n "${sMountPoint}" ]]; then # Collect all process IDs from lock files while IFS= read -r file; do nProcess=$(basename "${file}" | cut -d. -f3) [[ -n "${nProcess}" ]] && processes+=("${nProcess}") done < <( ls -1d "${sMountPoint}/MediaLibraryExtension/TV/.IgnoreThisDeviceWhileMoving."* 2>/dev/null ) # Remove lock files for processes that are no longer running for nProcess in "${processes[@]}"; do if ! ps -p "${nProcess}" >/dev/null; then rm -f "${sMountPoint}/MediaLibraryExtension/TV/.IgnoreThisDeviceWhileMoving.${nProcess}" fi done fi } #============================================================================== # RemoveCrashedLockShowAll - Removes crashed lock files for all TV shows #------------------------------------------------------------------------------ # Description: # Searches for '.IgnoreAlreadyRel*' lock files under TV directories and removes # them if the associated process is no longer running. # # Globals: # None # # Arguments: # $1 - Verbosity level (0 for quiet, >0 for verbose output) # # Outputs: # Writes informational messages to redirected stdout via `Echo` if verbose. # # Exit Codes: # 0 - Success (always returns 0) # # Side Effects: # Deletes lock files if the associated process is not running. # Executes external commands (`find`, `ps`, `rm`). #============================================================================== RemoveCrashedLockShowAll() { local -r -i lVerbose="$1" local sProcessId="" (( lVerbose )) && Echo "RemoveCrashedLockShowAll" (( lVerbose )) && Echo " Looking for '.IgnoreAlreadyRel*' files under '/cygdrive/*/MediaLibraryExtension/TV/'..." while IFS= read -r lock_file; do [[ -z "${lock_file}" ]] && continue (( lVerbose )) && Echo " '${lock_file}'" sProcessId="${lock_file##*.}" if [[ -n "${sProcessId}" ]]; then (( lVerbose )) && Echo " Checking process id ${sProcessId}" if ! ps -p "${sProcessId}" >/dev/null; then rm "${lock_file}" && Echo " Removed" fi fi done < <( find /cygdrive/*/MediaLibraryExtension/TV/ -type f -iname '.IgnoreAlreadyRel*' 2>/dev/null ) (( lVerbose )) && Echo " Done" } #============================================================================== # RemoveCrashedLockShow - Removes crashed lock files for a specific TV show #------------------------------------------------------------------------------ # Description: # Removes '.IgnoreAlreadyRelocating.*' lock files for a specific source link # if the associated process is no longer running. # # Globals: # None # # Arguments: # $1 - Source link path to check for lock files # # Outputs: # None # # Exit Codes: # 0 - Success (always returns 0) # # Side Effects: # Deletes lock files if the associated process is not running. # Executes external commands (`ls`, `ps`, `rm`). #============================================================================== RemoveCrashedLockShow() { local -r sSourceLink="$1" local -a processes=() local nProcess="" if [[ -n "${sSourceLink}" ]]; then # Collect all process IDs from lock files while IFS= read -r file; do nProcess=$(basename "${file}" | cut -d. -f3) [[ -n "${nProcess}" ]] && processes+=("${nProcess}") done < <( ls -1d "${sSourceLink}/.IgnoreAlreadyRelocating."* 2>/dev/null ) # Remove lock files for processes that are no longer running for nProcess in "${processes[@]}"; do if ! ps -p "${nProcess}" >/dev/null; then rm -f "${sSourceLink}/.IgnoreAlreadyRelocating.${nProcess}" fi done fi } # Initialize the environment IdentifyEnvironment
Comments
Post a Comment