Skip to main content

IT - Programming - BASH scripting - MY_LIBRARY.sh

#!/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

Popular posts from this blog

Movie - The Wizard of Oz (1939)

  My views Plot In rural  Kansas ,  Dorothy Gale  lives on a farm owned by her Uncle Henry and Aunt Em, and wishes she could be somewhere else. Dorothy's neighbor, Almira Gulch, who had been bitten by Dorothy's dog, Toto, obtains a sheriff's order authorizing her to seize Toto. Toto escapes and returns to Dorothy, who runs away to protect him. Professor Marvel, a charlatan fortune-teller, convinces Dorothy that Em is heartbroken, which prompts Dorothy to return home. She returns just as a  tornado  approaches the farm. Unable to get into the locked storm cellar, Dorothy takes cover in the farmhouse and is knocked unconscious. She seemingly awakens to find the house moving through the air, with her and Toto still inside it. The house comes down in an unknown land, and Dorothy is greeted by a good witch named  Glinda , who floats down in a bubble and explains that Dorothy has landed in Munchkinland in the  Land of Oz , and that the Munchkins are cel...

Movie - Se7en (1995)

  My views Plot In an unnamed city overcome with violent crime and corruption, disillusioned police Detective Lieutenant William Somerset is one week from retirement. He is partnered with David Mills, a young, short-tempered, idealistic detective who recently relocated to the city with his wife, Tracy. On Monday, Somerset and Mills investigate an obese man who was forced to eat until his stomach burst, killing him. The detectives find the word " gluttony " written on a wall. Somerset, considering the case too extreme for his last investigation, asks to be reassigned, but his request is denied. The following day, another victim, who had been forced to cut one pound (0.45 kg) of flesh from his body, is found; the crime scene is marked " greed ." Clues at the scene lead Somerset and Mills to the  sloth  victim, a drug-dealing  pederast  whom they find emaciated and restrained to a bed. Photographs reveal the victim was restrained for precisely one year. Somers...

IT - Which Is Faster: find | cpio -pdvm OR rsync?

To determine which is faster between find | cpio -pdvm and rsync for copying a large directory tree locally, we need to consider several factors: the nature of the operation, the tools' design, the system environment, and the specific use case. Let’s break this down based on the information provided in the web results and general knowledge about these tools. Overview of the Tools find | cpio -pdvm : find : Recursively lists all files and directories in a given path. cpio : A tool for copying files into or out of a cpio or tar archive. In this case, with the -pdvm options: -p : Pass-through mode (copy files from one directory tree to another). -d : Create directories as needed. -v : Verbose mod...