diff --git a/DBRepair.sh b/DBRepair.sh new file mode 100644 index 0000000..c99caa4 --- /dev/null +++ b/DBRepair.sh @@ -0,0 +1,1030 @@ +#!/bin/sh +######################################################################### +# Plex Media Server database check and repair utility script. # +# Maintainer: ChuckPa # +######################################################################### + +# Flag when temp files are to be retained +Retain=0 + +# Have the databases passed integrity checks +CheckedDB=0 + +# Universal output function +Output() { + echo "$@" + $LOG_TOOL "$@" +} + +# Write to Repair Tool log +WriteLog() { + + # Write given message into tool log file with TimeStamp + echo "$(date "+%Y-%m-%d %H.%M.%S") - $*" >> "$LOGFILE" + return 0 +} + +# Check given database file integrity +CheckDB() { + + # Confirm the DB exists + [ ! -f "$1" ] && Output "ERROR: $1 does not exist." && return 1 + + # Now check database for corruption + Result="$("$PLEX_SQLITE" "$1" "PRAGMA integrity_check(1)")" + if [ "$Result" = "ok" ]; then + return 0 + else + SQLerror="$(echo $Result | sed -e 's/.*code //')" + return 1 + fi + +} + +# Check all databases +CheckDatabases() { + + # Arg1 = calling function + # Arg2 = 'force' if present + + # Check each of the databases. If all pass, set the 'CheckedDB' flag + # Only force recheck if flag given + + # Check if not checked or forced + NeedCheck=0 + [ $CheckedDB -eq 0 ] && NeedCheck=1 + [ $CheckedDB -eq 1 ] && [ "$2" = "force" ] && NeedCheck=1 + + # Do we need to check + if [ $NeedCheck -eq 1 ]; then + + # Clear Damaged flag + Damaged=0 + CheckedDB=0 + + # Info + Output "Checking the PMS databases" + + # Check main DB + if CheckDB $CPPL.db ; then + Output "Check complete. PMS main database is OK." + WriteLog "$1"" - Check $CPPL.db - PASS" + else + Output "Check complete. PMS main database is damaged." + WriteLog "$1"" - Check $CPPL.db - FAIL ($SQLerror)" + Damaged=1 + fi + + # Check blobs DB + if CheckDB $CPPL.blobs.db ; then + Output "Check complete. PMS blobs database is OK." + WriteLog "$1"" - Check $CPPL.blobs.db - PASS" + else + Output "Check complete. PMS blobs database is damaged." + WriteLog "$1"" - Check $CPPL.blobs.db - FAIL ($SQLerror)" + Damaged=1 + fi + fi + + [ $Damaged -eq 0 ] && CheckedDB=1 + + # return status + return $Damaged +} + +# Return list of database backup dates for consideration in replace action +GetDates(){ + + Dates="" + Tempfile="/tmp/DBRepairTool.$$.tmp" + touch "$Tempfile" + + for i in $(find . -name 'com.plexapp.plugins.library.db-????-??-??' | sort -r) + do + # echo Date - "${i//[^.]*db-/}" + Date="$(echo $i | sed -e 's/.*.db-//')" + + # Only add if companion blobs DB exists + [ -e "$CPPL.blobs.db-$Date" ] && echo $Date >> "$Tempfile" + + done + + # Reload dates in sorted order + Dates="$(sort -r <$Tempfile)" + + # Remove tempfile + rm -f "$Tempfile" + + # Give results + echo $Dates + return +} + +# Non-fatal SQLite error code check +SQLiteOK() { + + # Global error variable + SQLerror=0 + + # Quick exit- known OK + [ $1 -eq 0 ] && return 0 + + # Put list of acceptable error codes here + Codes="19 28" + + # By default assume the given code is an error + CodeError=1 + + for i in $Codes + do + if [ $i -eq $1 ]; then + CodeError=0 + SQLerror=$i + break + fi + done + return $CodeError +} + +# Perform the actual copying for MakeBackup() +DoBackup() { + + if [ -e $2 ]; then + cp -p "$2" "$3" + Result=$? + if [ $Result -ne 0 ]; then + Output "Error $Result while backing up '$2'. Cannot continue." + WriteLog "$1 - MakeBackup $2 - FAIL" + + # Remove partial copied file and return + rm -f "$3" + return 1 + else + WriteLog "$1 - MakeBackup $2 - PASS" + return 0 + fi + fi +} + +# Make a backup of the current database files and tag with TimeStamp +MakeBackups() { + + Output "Backup current databases with '-ORIG-$TimeStamp'" + + for i in "db" "db-wal" "db-shm" "blobs.db" "blobs.db-wal" "blobs.db-shm" + do + DoBackup "$1" "${CPPL}.${i}" "$DBTMP/${CPPL}.${i}-ORIG-$TimeStamp" + Result=$? + done + + return $Result + +} + +ConfirmYesNo() { + + Answer="" + while [ "$Answer" = "" ] + do + echo -n "$1 (Y/N) ? " + read Input + + # EOF = No + [ "$Input" = "" ] && Answer=N ; [ "$Input" = "n" ] && Answer=N ; [ "$Input" = "N" ] && Answer=N + [ "$Input" = "y" ] && Answer=Y ; [ "$Input" = "Y" ] && Answer=Y + + # Unrecognized + if [ "$Answer" != "Y" ] && [ "$Answer" != "N" ]; then + echo "$Input" was not a valid reply. Please try again. + continue + fi + done + + # If no, done. + if [ "$Answer" = "N" ]; then + return 1 + fi + + # User said Yes. Be 100% certain + Answer="" + while [ "$Answer" = "" ] + do + echo -n "Are you sure (Y/N) ? " + read Input + + # EOF = No + [ "$Input" = "" ] && Answer=N ; [ "$Input" = "n" ] && Answer=N ; [ "$Input" = "N" ] && Answer=N + [ "$Input" = "y" ] && Answer=Y ; [ "$Input" = "Y" ] && Answer=Y + + # Unrecognized + if [ "$Answer" != "Y" ] && [ "$Answer" != "N" ]; then + echo "$Input" was not a valid reply. Please try again. + continue + fi + done + + if [ "$Answer" = "Y" ]; then + # Confirmed Yes + return 0 + else + return 1 + fi +} + +# Restore previously saved DB from given TimeStamp +RestoreSaved() { + + T="$1" + + for i in "db" "db-wal" "db-shm" "blobs.db" "blobs.db-wal" "blobs.db-shm" + do + [ -e "${CPPL}.${i}" ] && rm -f "${CPPL}.${i}" + [ -e "$DBTMP/${CPPL}.${i}-ORIG-$T" ] && mv "$DBTMP/${CPPL}.${i}-ORIG-$T" "${CPPL}.${i}" + done +} + +# Determine which host we are running on and set variables +HostConfig() { + + # Synology (DSM 7) + if [ -d /var/packages/PlexMediaServer ] && \ + [ -d "/var/packages/PlexMediaServer/shares/PlexMediaServer/AppData/Plex Media Server" ]; then + + # Where is the software + PKGDIR="/var/packages/PlexMediaServer/target" + PLEX_SQLITE="$PKGDIR/Plex SQLite" + LOG_TOOL="logger" + + # Where is the data + AppSuppDir="/var/packages/PlexMediaServer/shares/PlexMediaServer/AppData" + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + LOGFILE="$DBDIR/DBRepair.log" + + # We are done + HostType="Synology (DSM 7)" + return 0 + + # Synology (DSM 6) + elif [ -d "/var/packages/Plex Media Server" ] && \ + [ -f "/usr/syno/sbin/synoshare" ]; then + + # Where is the software + PKGDIR="/var/packages/Plex Media Server/target" + PLEX_SQLITE="$PKGDIR/Plex SQLite" + LOG_TOOL="logger" + + # Get shared folder path + AppSuppDir="$(synoshare --get Plex | grep Path | awk -F\[ '{print $2}' | awk -F\] '{print $1}')" + + # Where is the data + AppSuppDir="$AppSuppDir/Library/Application Support" + if [ -d "$AppSuppDir/Plex Media Server" ]; then + + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + LOGFILE="$DBDIR/DBRepair.log" + + HostType="Synology (DSM 6)" + return 0 + fi + + # QNAP (QTS & QuTS) + elif [ -f /etc/config/qpkg.conf ]; then + + # Where is the software + PKGDIR="$(getcfg -f /etc/config/qpkg.conf PlexMediaServer Install_path)" + PLEX_SQLITE="$PKGDIR/Plex SQLite" + LOG_TOOL="/sbin/log_tool -t 0 -a" + + # Where is the data + AppSuppDir="$PKGDIR/Library" + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + LOGFILE="$DBDIR/DBRepair.log" + + HostType="QNAP" + return 0 + + # Standard configuration Linux host + elif [ -f /etc/os-release ] && \ + [ -d /usr/lib/plexmediaserver ] && \ + [ -d /var/lib/plexmediaserver ]; then + + # Where is the software + PKGDIR="/usr/lib/plexmediaserver" + PLEX_SQLITE="$PKGDIR/Plex SQLite" + LOG_TOOL="logger" + + # Where is the data + AppSuppDir="/var/lib/plexmediaserver/Library/Application Support" + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + + # Find the metadata dir if customized + if [ -e /etc/systemd/system/plexmediaserver.service.d ]; then + + # Glob up all 'conf files' found + NewSuppDir="$(cd /etc/systemd/system/plexmediaserver.service.d ; \ + cat override.conf local.conf *.conf 2>/dev/null | grep "APPLICATION_SUPPORT_DIR" | head -1)" + + if [ "$NewSuppDir" != "" ]; then + NewSuppDir="${NewSuppDir//[^=]*=/}" + NewSuppDir="${AppSuppDir//\"}" + + if [ -d "$NewSuppDir" ]; then + AppSuppDir="$NewSuppDir" + else + echo "Given application support directory override specified does not exist: '$NewSuppDir'". Ignoring. + fi + fi + fi + + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + LOGFILE="$DBDIR/DBRepair.log" + + HostType="$(grep ^PRETTY_NAME= /etc/os-release | sed -e 's/PRETTY_NAME=//' | sed -e 's/"//g')" + return 0 + + # Netgear ReadyNAS + elif [ -e /etc/os-release ] && [ "$(cat /etc/os-release | grep ReadyNASOS)" != "" ]; then + + # Find PMS + if [ "$(echo /apps/plexmediaserver*)" != "/apps/plexmediaserver*" ]; then + + PKGDIR="$(echo /apps/plexmediaserver*)" + + # Where is the code + PLEX_SQLITE="$PKGDIR/Binaries/Plex SQLite" + AppSuppDir="$PKGDIR/MediaLibrary" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + LOGFILE="$DBDIR/DBRepair.log" + LOG_TOOL="logger" + + HostType="Netgear ReadyNAS" + return 0 + fi + + # ASUSTOR + elif [ -f /etc/nas.conf ] && grep ASUSTOR /etc/nas.conf >/dev/null && \ + [ -d "/volume1/Plex/Library/Plex Media Server" ]; then + + # Where are things + PLEX_SQLITE="/volume1/.@plugins/AppCentral/plexmediaserver/Plex SQLite" + AppSuppDir="/volume1/Plex/Library" + PID_FILE="$AppSuppDir/Plex Media Server/plexmediaserver.pid" + DBDIR="$AppSuppDir/Plex Media Server/Plug-in Support/Databases" + LOGFDILE="$DBDIR/DBRepair.log" + LOG_TOOL="logger" + + HostType="ASUSTOR" + return 0 + fi + + # Unknown / currently unsupported host + return 1 +} + +# Simple function to set variables +SetLast() { + + LastName="$1" + LastTimestamp="$2" + return 0 +} +############################################################# +# Main utility begins here # +############################################################# + +# Global variable - main database +CPPL=com.plexapp.plugins.library + +# Initial timestamp +TimeStamp="$(date "+%Y-%m-%d_%H:%M:%S")" + +# Initialize LastName LastTimestamp +SetLast "" "" + +# Identify this host +HostType="" +if ! HostConfig; then + Output 'Error: Unknown host. Currently supported hosts are: QNAP, Synology (DSM 6 & DSM 7), Linux Workstation/Server' + exit 1 +fi + +echo " " +# echo Detected Host: $HostType +WriteLog "============================================================" +WriteLog "Session start: Host is $HostType" + +# Make sure we have a logfile +touch "$LOGFILE" + +# Basic checks; PMS installed +if [ ! -f "$PLEX_SQLITE" ] ; then + Output "PMS is not installed. Cannot continue. Exiting." + WriteLog "Attempt to run utility without PMS installed." + rm -rf $TMPDIR + exit 1 +fi + +# Databases exist or Backups exist to restore from +if [ ! -f "$DBDIR/$CPPL.db" ] && \ + [ ! -f "$DBDIR/$CPPL.blobs.db" ] && \ + [ "$(echo com.plexapp.plugins.*-????-??-??)" = "com.plexapp.plugins.*-????-??-??" ]; then + + Output "Cannot locate databases. Cannot continue. Exiting." + WriteLog "No databases or backups found." + rm -rf $TMPDIR + exit 1 +fi + +# Set tmp dir so we don't use RAM when in DBDIR +DBTMP="./dbtmp" +mkdir -p "$DBDIR/$DBTMP" +export TMPDIR="$DBTMP" +export TMP="$DBTMP" + +# Work in the Databases directory +cd "$DBDIR" + +# Run entire utility in a loop until all arguments used, EOF on input, or commanded to exit +while true +do + + # Is PMS already running? + if [ -f "$PID_FILE" ] && [ "$(pidof 'Plex Media Server')" != "" ] ; then + Output "Plex Media Server is currently running, cannot continue." + Output "Please stop Plex Media Server and restart this utility." + WriteLog "PMS running. Could not continue." + exit 1 + fi + + # Main menu loop + Choice=0; Exit=0 + while [ $Choice -eq 0 ] + do + echo " " + echo " " + echo " (DEVELOPMENT) Plex Media Server Database Repair Utility ($HostType)" + echo " " + echo "Select" + echo " " + echo " 1. Check database" + echo " 2. Vacuum database" + echo " 3. Reindex database" + echo " 4. Attempt database repair" + echo " 5. Replace current database with newest usable backup copy" + echo " 6. Undo last successful action (Vacuum, Reindex, Repair, or Replace)" + echo " 7. Show logfile" + echo " 8. Exit" + echo " " + echo -n "Enter choice: " + if [ "$1" != "" ]; then + Input="$1" + echo "$1" + shift + else + read Input + + # Handle EOF/forced exit + [ "$Input" = "" ] && Input=7 && Exit=1 + fi + [ "$Input" = "1" ] && Choice=1 + [ "$Input" = "2" ] && Choice=2 + [ "$Input" = "3" ] && Choice=3 + [ "$Input" = "4" ] && Choice=4 + [ "$Input" = "5" ] && Choice=5 + [ "$Input" = "6" ] && Choice=6 + [ "$Input" = "7" ] && Choice=7 + [ "$Input" = "8" ] && Choice=8 + [ "$Choice" -eq 0 ] && echo " " && echo "'$Input' - Is invalid. Try again" + + # Update timestamp + TimeStamp="$(date "+%Y-%m-%d_%H:%M:%S")" + done + + # Spacing for legibility + echo ' ' + + # 1. - Check database + if [ $Choice -eq 1 ]; then + + # CHECK DBs + if CheckDatabases "Check " force ; then + WriteLog "Check - PASS" + CheckedDB=1 + else + WriteLog "Check - FAIL" + CheckedDB=0 + fi + + # 2. Vacuum DB + elif [ $Choice -eq 2 ]; then + + # Clear flags + Fail=0 + Damaged=0 + + # Check databases before Indexing if not previously checked + if ! CheckDatabases "Vacuum " ; then + Damaged=1 + Fail=1 + fi + + # If damaged, exit + if [ $Damaged -eq 1 ]; then + Output "Databases are damaged. Vacuum operation not available. Please repair or replace first." + WriteLog "Vacuum - Databases damaged." + continue + fi + + + # Make a backup + Output "Backing up databases" + if ! MakeBackups "Vacuum "; then + Output "Error making backups. Cannot continue." + WriteLog "Vacuum - MakeBackups - FAIL" + Fail=1 + continue + else + WriteLog "Vacuum - MakeBackups - PASS" + fi + + # Start vacuuming + Output "Vacuuming main database" + "$PLEX_SQLITE" $CPPL.db 'VACUUM;' + Result=$? + if SQLiteOK $Result; then + Output "Vacuuming main database successful." + WriteLog "Vacuum - Vacuum main database - PASS" + else + Output "Vaccuming main database failed. Error code $Result from Plex SQLite" + WriteLog "Vacuum - Vacuum main database - FAIL ($Result)" + Fail=1 + fi + + Output "Vacuuming blobs database" + "$PLEX_SQLITE" $CPPL.blobs.db 'VACUUM;' + Result=$? + if SQLiteOK $Result; then + Output "Vacuuming blobs database successful." + WriteLog "Vacuum - Vacuum blobs database - PASS" + else + Output "Vaccuming blobs database failed. Error code $Result from Plex SQLite" + WriteLog "Vacuum - Vacuum blobs database - FAIL ($Result)" + Fail=1 + fi + + if [ $Fail -eq 0 ]; then + Output "Vacuum complete." + WriteLog "Vacuum - PASS" + SetLast "Vacuum" "$TimeStamp" + else + Output "Vacuum failed." + WriteLog "Vacuum - FAIL" + RestoreSaved "$TimeStamp" + fi + continue + + # 3. Reindex DB + elif [ $Choice -eq 3 ]; then + + # Clear flag + Damaged=0 + Fail=0 + # Check databases before Indexing if not previously checked + if ! CheckDatabases "Reindex" ; then + Damaged=1 + Fail=1 + fi + + + # If damaged, exit + if [ $Damaged -eq 1 ]; then + Output "Databases are damaged. Reindex operation not available. Please repair or replace first." + continue + fi + + # Databases are OK, Make a backup + Output "Backing up of databases" + MakeBackups "Reindex" + Result=$? + if [ $Result -eq 0 ]; then + WriteLog "Reindex - MakeBackup - PASS" + else + Output "Error making backups. Cannot continue." + WriteLog "Reindex - MakeBackup - FAIL ($Result)" + Fail=1 + continue + fi + + # Databases are OK, Start reindexing + Output "Reindexing main database" + "$PLEX_SQLITE" $CPPL.db 'REINDEX;' + Result=$? + if SQLiteOK $Result; then + Output "Reindexing main database successful." + WriteLog "Reindex - Reindex: $CPPL.db - PASS" + else + Output "Reindexing main database failed. Error code $Result from Plex SQLite" + WriteLog "Reindex - Reindex: $CPPL.db - FAIL ($Result)" + Fail=1 + fi + + Output "Reindexing blobs database" + "$PLEX_SQLITE" $CPPL.blobs.db 'REINDEX;' + Result=$? + if SQLiteOK $Result; then + Output "Reindexing blobs database successful." + WriteLog "Reindex - Reindex: $CPPL.blobs.db - PASS" + else + Output "Reindexing blobs database failed. Error code $Result from Plex SQLite" + WriteLog "Reindex - Reindex: $CPPL.blobs.db - FAIL ($Result)" + Fail=1 + fi + + Output "Reindex complete." + + if [ $Fail -eq 0 ]; then + SetLast "Reindex" "$TimeStamp" + WriteLog "Reindex - PASS" + else + RestoreSaved "$TimeStamp" + WriteLog "Reindex - FAIL" + fi + continue + + + # 4. - Attempt DB repair + elif [ $Choice -eq 4 ]; then + + Damaged=0 + Fail=0 + + # Verify DBs are here + if [ ! -e $CPPL.db ]; then + Output "No main Plex database exists to repair. Exiting." + WriteLog "Repair - No main database - FAIL" + Fail=1 + continue + fi + + # Check size + Size=$(stat -c '%s' $CPPL.db) + + # Exit if not valid + if [ $Size -lt 300000 ]; then + Output "Main database is too small/truncated, repair is not possible. Please try restoring a backup. " + WriteLog "Repair - Main databse too small - FAIL" + Fail=1 + continue + fi + + # Continue + Output "Exporting current databases using timestamp: $TimeStamp" + Fail=0 + + # Get the owning UID/GID before we proceed so we can restore + Owner="$(stat -c '%u:%g' $CPPL.db)" + + # Attempt to export main db to SQL file (Step 1) + echo -n 'Export: (main)..' + "$PLEX_SQLITE" $CPPL.db ".output '$TMPDIR/library.plexapp.sql-$TimeStamp'" .dump + Result=$? + if ! SQLiteOK $Result; then + + # Cannot dump file + Output "Error $Result from Plex SQLite while exporting $CPPL.db" + Output "Could not successfully export the main database to repair it. Please try restoring a backup." + WriteLog "Repair - Cannot recover main database to '$TMPDIR/library.plexapp.sql-$TimeStamp' - FAIL ($Result)" + Fail=1 + continue + fi + + # Attempt to export blobs db to SQL file + echo -n '(blobs)..' + "$PLEX_SQLITE" $CPPL.blobs.db ".output '$TMPDIR/blobs.plexapp.sql-$TimeStamp'" .dump + Result=$? + if ! SQLiteOK $Result; then + + # Cannot dump file + Output "Error $Result from Plex SQLite while exporting $CPPL.blobs.db" + Output "Could not successfully export the blobs database to repair it. Please try restoring a backup." + WriteLog "Repair - Cannot recover blobs database to '$TMPDIR/blobs.plexapp.sql-$TimeStamp' - FAIL ($Result)" + Fail=1 + continue + fi + + # Edit the .SQL files if all OK + if [ $Fail -eq 0 ]; then + + # Edit + sed -i -e 's/ROLLBACK;/COMMIT;/' "$TMPDIR/library.plexapp.sql-$TimeStamp" + sed -i -e 's/ROLLBACK;/COMMIT;/' "$TMPDIR/blobs.plexapp.sql-$TimeStamp" + fi + + # Inform user + echo done. + Output "Successfully exported the main and blobs databases. Proceeding to import into new databases." + WriteLog "Repair - Export databases - PASS" + + # Library and blobs successfully exported, create new + echo -n 'Import: (main)..' + "$PLEX_SQLITE" $CPPL.db-$TimeStamp < "$TMPDIR/library.plexapp.sql-$TimeStamp" + Result=$? + if ! SQLiteOK $Result; then + Output "Error $Result from Plex SQLite while importing from '$TMPDIR/library.plexapp.sql-$TimeStamp'" + WriteLog "Repair - Cannot import main database from '$TMPDIR/library.plexapp.sql-$TimeStamp' - FAIL ($Result)" + Output "Cannot continue." + Fail=1 + continue + fi + + echo -n '(blobs)..' + "$PLEX_SQLITE" $CPPL.blobs.db-$TimeStamp < "$TMPDIR/blobs.plexapp.sql-$TimeStamp" + Result=$? + if ! SQLiteOK $Result ; then + Output "Error $Result from Plex SQLite while importing from '$TMPDIR/blobs.plexapp.sql-$TimeStamp'" + WriteLog "Repair - Cannot import blobs database from '$TMPDIR/blobs.plexapp.sql-$TimeStamp' - FAIL ($Result)" + Output "Cannot continue." + Fail=1 + continue + fi + + # Made it to here, now verify + echo done. + Output "Successfully imported data from exported SQL files." + WriteLog "Repair - Import - PASS" + + # Verify databases are intact and pass testing + Output "Verifying databases integrity after importing." + + # Check main DB + if CheckDB $CPPL.db-$TimeStamp ; then + Output "Verification complete. PMS main database is OK." + WriteLog "Repair - Verify main database - PASS" + else + Output "Verification complete. PMS main database import failed." + WriteLog "Repair - Verify main database - FAIL ($SQLerror)" + Fail=1 + fi + + # Check blobs DB + if CheckDB $CPPL.blobs.db-$TimeStamp ; then + Output "Verification complete. PMS blobs database is OK." + WriteLog "Repair - Verify blobs database - PASS" + else + Output "Verification complete. PMS blobs database import failed." + WriteLog "Repair - Verify main database - FAIL ($SQLerror)" + Fail=1 + fi + + # If not failed, move files normally + if [ $Fail -eq 0 ]; then + + Output "Saving current databases with '-ORIG-$TimeStamp'" + [ -e $CPPL.db ] && mv $CPPL.db "$TMPDIR/$CPPL.db-ORIG-$TimeStamp" + [ -e $CPPL.blobs.db ] && mv $CPPL.blobs.db "$TMPDIR/$CPPL.blobs.db-ORIG-$TimeStamp" + + Output "Making imported databases active" + mv $CPPL.db-$TimeStamp $CPPL.db + mv $CPPL.blobs.db-$TimeStamp $CPPL.blobs.db + + Output "Import complete. Please check your library settings and contents for completeness." + Output "Recommend: Scan Files and Refresh all metadata for each library section." + + # Remove .sql temp files from $TMPDIR + # rm -f "$TMPDIR"/*.sql-* + + # Ensure WAL and SHM are gone + [ -e $CPPL.blobs.db-wal ] && rm -f $CPPL.blobs.db-wal + [ -e $CPPL.blobs.db-shm ] && rm -f $CPPL.blobs.db-shm + [ -e $CPPL.db-wal ] && rm -f $CPPL.db-wal + [ -e $CPPL.db-shm ] && rm -f $CPPL.db-shm + + # Set ownership on new files + chown $Owner $CPPL.db $CPPL.blobs.db + + # We didn't fail, set CheckedDB status true (passed above checks) + CheckedDB=1 + + WriteLog "Repair - Move files - PASS" + WriteLog "Repair - PASS" + + SetLast "Repair" "$TimeStamp" + else + + rm -f $CPPL.db-$TimeStamp + rm -f $CPPL.blobs.db-$TimeStamp + + Output "Repair has failed. No files changed" + WriteLog "Repair - $TimeStamp - FAIL" + Retain=1 + fi + continue + + # 5. Replace database from backup copy + elif [ $Choice -eq 5 ]; then + + # If Databases already checked, confirm the user really wants to do this + Confirmed=0 + Fail=0 + if CheckDatabases "Replace"; then + if ConfirmYesNo "Are you sure you want to restore a previous database backup"; then + Confirmed=1 + fi + fi + + if [ $Damaged -eq 1 ] || [ $Confirmed -eq 1 ]; then + # Get list of dates to use + Dates=$(GetDates) + + # If no backups, error and exit + if [ "$Dates" == "" ] && [ $Damaged -eq 1 ]; then + Output "Database is damaged and no backups avaiable." + Output "Only available option is Repair." + WriteLog "Replace - Scan for usable candidates - FAIL" + continue + fi + + Output "Checking for a usable backup." + Candidate="" + + for i in $Dates + do + + # Check candidate + if [ -e $CPPL.db-$i ] && \ + [ -e $CPPL.blobs.db-$i ] && \ + CheckDB $CPPL.db-$i && \ + CheckDB $CPPL.blobs.db-$i ; then + + Output "Found valid database backup date: $i" + Candidate=$i + + UseThis=0 + if ConfirmYesNo "Use backup '$Candidate' ?"; then + UseThis=1 + fi + + # OK, use this one + if [ $UseThis -eq 1 ]; then + + # Move database, wal, and shm (keep safe) with timestamp + Output "Saving current databases with timestamp: '-ORIG-$TimeStamp'" + + for j in "db" "db-wal" "db-shm" "blobs.db" "blobs.db-wal" "blobs.db-shm" + do + [ -e "$CPPL.$j" ] && mv -f $CPPL.$j "$TMPDIR/$CPPL.$j-ORIG-$TimeStamp" + done + WriteLog "Replace - Move Files - PASS" + + # Copy this backup into position as primary + Output "Copying backup database $Candidate to use as new database." + + cp -p $CPPL.db-$Candidate $CPPL.db-$TimeStamp + Result=$? + + if [ $Result -ne 0 ]; then + Output "Error $Result while copying $CPPL.db" + Output "Database file is incomplete. Please resolve manually." + WriteLog "Replace - Copy $CPPL.db-$Candidate - FAIL" + Fail=1 + else + WriteLog "Replace - Copy $CPPL.db-$i - PASS" + fi + + cp -p $CPPL.blobs.db-$Candidate $CPPL.blobs.db-$TimeStamp + Result=$? + + if [ $Result -ne 0 ]; then + Output "Error $Result while copying $CPPL.blobs.db" + Output "Database file is incomplete. Please resolve manually." + WriteLog "Replace - Copy $CPPL.blobs.db-$Candidate - FAIL" + Fail=1 + else + WriteLog "Replace - Copy $CPPL.blobs.db-$Candidate - PASS" + fi + + # If no failure copying, check and make active + if [ $Fail -eq 0 ]; then + # Final checks + Output "Copy complete. Performing final check" + + if CheckDB $CPPL.db-$TimeStamp && \ + CheckDB $CPPL.blobs.db-$TimeStamp ; then + + # Move into position as active + mv $CPPL.db-$TimeStamp $CPPL.db + mv $CPPL.blobs.db-$TimeStamp $CPPL.blobs.db + + # done + Output "Database recovery and verification complete." + WriteLog "Replace - Verify databases - PASS" + + else + + # DB did not verify after copy -- Something wrong + + rm -f $CPPL.db-$TimeStamp $CPPL.blobs.db-$TimeStamp + Output "Final check failed. Keeping existing databases" + WriteLog "Replace - Verify databases - FAIL" + WriteLog "Replace - Failed Databses - REMOVED" + fi + else + + Output "Could not copy backup databases. Out of disk space?" + Output "Restoring original databases" + + for k in "db" "db-wal" "db-shm" "blobs.db" "blobs.db-wal" "blobs.db-shm" + do + [ -e "$TMPDIR/$CPPL.$k-ORIG-$TimeStamp" ] && mv -f "$TMPDIR/$CPPL.$k-ORIG-$TimeStamp" $CPPL.$k + done + WriteLog "Replace - Verify databases - FAIL" + Fail=1 + fi + fi + + # If successful, save + [ $Fail -eq 0 ] && SetLast "Replace" "$TimeStamp" + break + fi + done + + # Error check if no Candidate found + if [ "$Candidate" = "" ]; then + Output "Error. No valid matching main and blobs database pairs. Cannot replace." + WriteLog "Replace - Select candidate - FAIL" + fi + fi + + # 6. - Undo last successful action + elif [ $Choice -eq 6 ]; then + + + # Confirm there is something to undo + if [ "$LastTimestamp" != "" ]; then + + # Educate user + echo " " + echo "'Undo' restores the databases to the state prior to the last SUCCESSFUL action." + echo "If any action fails before it completes, that action is automatically undone for you." + echo "Be advised: Undo restores the databases to their state PRIOR TO the last action of 'Vacuum', 'Reindex', or 'Replace'" + echo "WARNING: Once Undo completes, there will be nothing more to Undo untl another successful action is completed" + echo " " + + if ConfirmYesNo "Undo '$LastName' performed at timestamp '$LastTimestamp' ? "; then + + Output "Undoing $LastName ($LastTimestamp)" + for j in "db" "db-wal" "db-shm" "blobs.db" "blobs.db-wal" "blobs.db-shm" + do + [ -e "$TMPDIR/$CPPL.$j-ORIG-$LastTimestamp" ] && mv -f "$TMPDIR/$CPPL.$j-ORIG-$LastTimestamp" $CPPL.$j + done + + Output "Undo complete." + WriteLog "Undo - Undo $LastName , TimeStamp $LastTimestamp" + SetLast "Undo" "" + fi + + else + Output "Nothing to undo." + WriteLog "Undo - Nothing to Undo." + fi + + # 7. - Show Logfile + elif [ $Choice -eq 7 ]; then + + echo ================================================================================== + cat "$LOGFILE" + echo ================================================================================== + + # 8. - Exit + elif [ $Choice -eq 8 ]; then + + # Ask questions on graceful exit + if [ $Exit -eq 0 ]; then + # Ask if the user wants to remove the DBTMP directory and all backups thus far + if ConfirmYesNo "Ok to remove temporary work files for this session?" ; then + + # Here it goes + Output "Deleting all temporary work files." + WriteLog "Exit - Delete temp files." + rm -rf "$TMPDIR" + else + Output "Retaining all temporary work files." + WriteLog "Exit - Retain temp files." + fi + else + Output "Unexpected exit command. Keeping all temporary work files." + WriteLog "EOFExit - Retain temp files." + fi + + WriteLog "Session end." + WriteLog "============================================================" + exit 0 + fi +done +exit 0