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

IT - My Home Platform View - All Infrastructure

Some ideas Deploy a harverster cluster Deploy a rancher server

Movie - The Gray Man (2022)

  My views Plot In 2003, senior  CIA  official Donald Fitzroy visits a prisoner named Courtland Gentry in Florida. Eight years earlier, Courtland was a minor convicted of killing his abusive father to protect his brother. Fitzroy offers him his freedom in exchange for working as an assassin in the CIA's  Sierra  program, an elite black ops unit, which will allow him to exist in the gray. In 2021, Courtland, now known as  Sierra Six , is working with fellow CIA agent Dani Miranda to assassinate a target named Dining Car suspected of selling off  national security  secrets in  Bangkok  during the national  Songkran  festival. Unable to do so stealthily without harming civilians, he attacks Dining Car directly, mortally wounding him. Before dying, he reveals he was also in the Sierra program as Sierra Four. He hands Six an encrypted drive detailing the corruption of CIA official Denny Carmichael, the lead agent on the assassinatio...

Movie - Some Like It Hot (1959)

  My views See other movies with: Marilyn Monroe Jack Lemmon Tony Curtis Plot In Prohibition-era  Chicago , Joe is a jazz  saxophone  player and an irresponsible, impulsive gambler and  ladies' man ; Jerry, his anxious friend, is a jazz  double bass  player. They work in a  speakeasy  owned by local Mafia boss "Spats" Colombo. Tipped off by informant "Toothpick" Charlie, the police raid the joint. Joe and Jerry escape, but later accidentally witness Spats and his henchmen gunning down Toothpick and his gang in revenge (an incident inspired by the  Saint Valentine's Day Massacre ). [ 7 ]  Spats and his gang see them as they flee. Broke, terrified, and desperate to leave Chicago, Joe and Jerry  disguise themselves as women  named Josephine and Daphne so they can join Sweet Sue and her Society Syncopators, an all-female band headed by train to  Miami . On the train, Joe and Jerry befriend Sugar Kane, the band's vocalist ...