Merge pull request #194 from danrahn/master

[Windows] Check PMS state in more places
This commit is contained in:
Chuck 2025-04-30 20:06:45 -04:00 committed by GitHub
commit e2a3d1d04b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 77 deletions

View File

@ -3,7 +3,7 @@
# # # #
######################################################################### #########################################################################
$PlexDBRepairVersion = 'v1.00.01' $PlexDBRepairVersion = 'v1.00.02'
class PlexDBRepair { class PlexDBRepair {
[PlexDBRepairOptions] $Options [PlexDBRepairOptions] $Options
@ -14,10 +14,12 @@ class PlexDBRepair {
[string] $Timestamp # Timestamp used for temporary database files [string] $Timestamp # Timestamp used for temporary database files
[string] $LogFile # Path of our log file [string] $LogFile # Path of our log file
[string] $Version # Current script version [string] $Version # Current script version
[bool] $IsError # Whether we're currently in an error state
PlexDBRepair($Arguments, $Version) { PlexDBRepair($Arguments, $Version) {
$this.Options = [PlexDBRepairOptions]::new() $this.Options = [PlexDBRepairOptions]::new()
$this.Version = $Version $this.Version = $Version
$this.IsError = $false
$Commands = $this.PreprocessArgs($Arguments) $Commands = $this.PreprocessArgs($Arguments)
if ($null -eq $Commands) { if ($null -eq $Commands) {
return return
@ -187,7 +189,9 @@ class PlexDBRepair {
switch -Regex ($Choice) { switch -Regex ($Choice) {
"^(1|stop)$" { $this.DoStop() } "^(1|stop)$" { $this.DoStop() }
"^(2|autom?a?t?i?c?)$" { $this.RunAutomaticDatabaseMaintenance() } "^(2|autom?a?t?i?c?)$" {
$this.IsError = !$this.RunAutomaticDatabaseMaintenance()
}
"^(7|start?)$" { $this.StartPMS() } "^(7|start?)$" { $this.StartPMS() }
"^(21|(prune?|remov?e?))$" { $this.PrunePhotoTranscoderCache() } "^(21|(prune?|remov?e?))$" { $this.PrunePhotoTranscoderCache() }
"^(42|ignor?e?|honor?)$" { "^(42|ignor?e?|honor?)$" {
@ -213,6 +217,14 @@ class PlexDBRepair {
return return
} }
# If our last DB operation failed, we don't want to automatically delete
# temporary files when in scripted mode.
if ($this.IsError -and $this.Options.Scripted) {
$this.Output("Exiting with errors. Keeping all temporary work files.")
$this.WriteLog("Exit - Retain temp files.")
return
}
$this.CleanDBTemp(!$this.Options.Scripted) $this.CleanDBTemp(!$this.Options.Scripted)
$this.WriteEnd() $this.WriteEnd()
return return
@ -242,13 +254,12 @@ class PlexDBRepair {
[void] DoStop() { [void] DoStop() {
$this.WriteLog("Stop - START") $this.WriteLog("Stop - START")
$PMS = $this.GetPMS() $PMS = $this.GetPMS()
if ($PMS) { if ($null -eq $PMS) {
$this.Output("Stopping PMS.")
} else {
$this.Output("PMS already stopped.") $this.Output("PMS already stopped.")
return return
} }
$this.Output("Stopping PMS.")
# Plex doesn't respond to CloseMainWindow because it doesn't have a window, # Plex doesn't respond to CloseMainWindow because it doesn't have a window,
# and Stop-Process does a forced exit of the process, so use taskkill to ask # and Stop-Process does a forced exit of the process, so use taskkill to ask
@ -265,10 +276,11 @@ class PlexDBRepair {
if ($PMS.HasExited) { if ($PMS.HasExited) {
$this.WriteLog("Stop - PASS") $this.WriteLog("Stop - PASS")
$this.Output("Stopped PMS.") $this.Output("Stopped PMS.")
} else { return
$this.OutputWarn("Could not stop PMS. PMS did not shutdown within 30 second limit.")
$this.WriteLog("Stop - FAIL (Timeout)")
} }
$this.OutputWarn("Could not stop PMS. PMS did not shutdown within 30 second limit.")
$this.WriteLog("Stop - FAIL (Timeout)")
} }
# Start Plex Media Server if it isn't already running # Start Plex Media Server if it isn't already running
@ -293,14 +305,14 @@ class PlexDBRepair {
} }
# All-in-one database utility - Repair/Check/Reindex # All-in-one database utility - Repair/Check/Reindex
[void] RunAutomaticDatabaseMaintenance() { [bool] RunAutomaticDatabaseMaintenance() {
$this.Output("Automatic Check,Repair,Index started.") $this.Output("Automatic Check,Repair,Index started.")
$this.WriteLog("Auto - START") $this.WriteLog("Auto - START")
if ($this.PMSRunning()) { if ($this.PMSRunning()) {
$this.WriteLog("Auto - FAIL - PMS running") $this.WriteLog("Auto - FAIL - PMS running")
$this.OutputWarn("Unable to run automatic sequence. PMS is running. Please stop PlexMediaServer.") $this.OutputWarn("Unable to run automatic sequence. PMS is running. Please stop PlexMediaServer.")
return return $false
} }
# Create temporary backup directory # Create temporary backup directory
@ -310,7 +322,7 @@ class PlexDBRepair {
New-Item -Path $DBTemp -ItemType "directory" -ErrorVariable tempDirError *>$null New-Item -Path $DBTemp -ItemType "directory" -ErrorVariable tempDirError *>$null
if ($TempDirError) { if ($TempDirError) {
$this.ExitDBMaintenance("Unable to create temporary database directory", $false) $this.ExitDBMaintenance("Unable to create temporary database directory", $false)
return return $false
} }
} }
@ -320,10 +332,10 @@ class PlexDBRepair {
$MainDBSQL = Join-Path $DBTemp -ChildPath "library.sql_$($this.TimeStamp)" $MainDBSQL = Join-Path $DBTemp -ChildPath "library.sql_$($this.TimeStamp)"
if (!$this.FileExists($MainDB)) { if (!$this.FileExists($MainDB)) {
$this.ExitDBMaintenance("Could not find $MainDBName in database directory", $false) $this.ExitDBMaintenance("Could not find $MainDBName in database directory", $false)
return return $false
} }
if (!$this.ExportPlexDB($MainDB, $MainDBSQL)) { return } if (!$this.ExportPlexDB($MainDB, $MainDBSQL)) { return $false }
$this.Output("Exporting Blobs DB") $this.Output("Exporting Blobs DB")
$BlobsDBName = "com.plexapp.plugins.library.blobs.db" $BlobsDBName = "com.plexapp.plugins.library.blobs.db"
@ -331,56 +343,86 @@ class PlexDBRepair {
$BlobsDBSQL = Join-Path $DBTemp -ChildPath "blobs.sql_$($this.Timestamp)" $BlobsDBSQL = Join-Path $DBTemp -ChildPath "blobs.sql_$($this.Timestamp)"
if (!$this.FileExists($BlobsDB)) { if (!$this.FileExists($BlobsDB)) {
$this.ExitDBMaintenance("Could not find $BlobsDBName in database directory", $false) $this.ExitDBMaintenance("Could not find $BlobsDBName in database directory", $false)
return return $false
} }
if (!$this.ExportPlexDB($BlobsDB, $BlobsDBSQL)) { return } if (!$this.ExportPlexDB($BlobsDB, $BlobsDBSQL)) { return $false }
$this.Output("Successfully exported the main and blobs databases. Proceeding to import into new database.") $this.Output("Successfully exported the main and blobs databases. Proceeding to import into new database.")
$this.WriteLog("Repair - Export databases - PASS") $this.WriteLog("Repair - Export databases - PASS")
# Make sure Plex hasn't been started while we were exporting
if (!$this.CheckPMS("Auto ", "export")) { return $false }
$this.Output("Importing Main DB.") $this.Output("Importing Main DB.")
$MainDBImport = Join-Path $this.PlexDBDir -ChildPath "${MainDBName}_$($this.Timestamp)" $MainDBImport = Join-Path $this.PlexDBDir -ChildPath "${MainDBName}_$($this.Timestamp)"
if (!$this.ImportPlexDB($MainDBSQL, $MainDBImport)) { return } if (!$this.ImportPlexDB($MainDBSQL, $MainDBImport)) { return $false }
$this.Output("Creating Blobs DB") $this.Output("Importing Blobs DB.")
$BlobsDBImport = Join-Path $this.PlexDBDir -ChildPath "${BlobsDBName}_$($this.Timestamp)" $BlobsDBImport = Join-Path $this.PlexDBDir -ChildPath "${BlobsDBName}_$($this.Timestamp)"
if (!$this.ImportPlexDB($BlobsDBSQL, $BlobsDBImport)) { return } if (!$this.ImportPlexDB($BlobsDBSQL, $BlobsDBImport)) { return $false }
$this.Output("Successfully imported databases.") $this.Output("Successfully imported databases.")
$this.WriteLog("Repair - Import - PASS") $this.WriteLog("Repair - Import - PASS")
$this.Output("Verifying databases integrity after importing.") $this.Output("Verifying databases integrity after importing.")
if (!$this.IntegrityCheck($MainDBImport, "Main")) { return } if (!$this.IntegrityCheck($MainDBImport, "Main")) { return $false }
$this.Output("Verification complete. PMS main database is OK.") $this.Output("Verification complete. PMS main database is OK.")
$this.WriteLog("Repair - Verify main database - PASS") $this.WriteLog("Repair - Verify main database - PASS")
if (!$this.IntegrityCheck($BlobsDBImport, "Blobs")) { return } if (!$this.IntegrityCheck($BlobsDBImport, "Blobs")) { return $false }
$this.Output("Verification complete. PMS blobs database is OK.") $this.Output("Verification complete. PMS blobs database is OK.")
$this.WriteLog("Repair - Verify blobs database - PASS") $this.WriteLog("Repair - Verify blobs database - PASS")
if (!$this.CheckPMS("Auto ", "import")) { return $false }
# Import complete, now reindex # Import complete, now reindex
$this.WriteOutputLog("Reindexing Main DB") $this.WriteOutputLog("Reindexing Main DB")
if (!$this.RunSQLCommand("""$MainDBImport"" ""REINDEX;""", "Failed to reindex Main DB")) { return } if (!$this.RunSQLCommand("""$MainDBImport"" ""REINDEX;""", "Failed to reindex Main DB")) { return $false }
$this.WriteOutputLog("Reindexing Blobs DB") $this.WriteOutputLog("Reindexing Blobs DB")
if (!$this.RunSQLCommand("""$BlobsDBImport"" ""REINDEX;""", "Failed to reindex Blobs DB")) { return } if (!$this.RunSQLCommand("""$BlobsDBImport"" ""REINDEX;""", "Failed to reindex Blobs DB")) { return $false }
$this.WriteOutputLog("Reindexing complete.") $this.WriteOutputLog("Reindexing complete.")
$this.WriteOutputLog("Moving current DBs to DBTMP and making new databases active") $this.WriteOutputLog("Moving current DBs to DBTMP and making new databases active")
if (!$this.CheckPMS("Auto ", "new database copy")) { return $false }
$MoveError = $null try {
Move-Item -Path $MainDB -Destination (Join-Path $DBTemp -ChildPath "${MainDBName}_$($this.TimeStamp)") -ErrorVariable moveError *>$null $this.MoveDatabase($MainDB, (Join-Path $DBTemp -ChildPath "${MainDBName}_$($this.Timestamp)"), "move Main DB to DBTMP")
if ($MoveError) { $this.ExitDBMaintenance("Unable to move Main DB to DBTMP: $MoveError", $false); return } $this.MoveDatabase($MainDBImport, $MainDB, "replace Main DB with rebuilt DB")
Move-Item -Path $MainDBImport -Destination $MainDB -ErrorVariable moveError *>$null
if ($MoveError) { $this.ExitDBMaintenance("Unable to replace Main DB with rebuilt DB: $MoveError", $false); return } $this.MoveDatabase($BlobsDB, (Join-Path $DBTemp -ChildPath "${BlobsDBName}_$($this.Timestamp)"), "move Blobs DB to DBTMP")
$this.MoveDatabase($BlobsDBImport, $BlobsDB, "replace Blobs DB with rebuilt DB")
Move-Item -Path $BlobsDB -Destination (Join-Path $DBTemp -ChildPath "${BlobsDBName}_$($this.TimeStamp)") -ErrorVariable moveError *>$null } catch {
if ($MoveError) { $this.ExitDBMaintenance("Unable to move Blobs DB to DBTMP: $MoveError", $false) } $Error.Clear()
Move-Item -Path $BlobsDBImport -Destination $BlobsDB -ErrorVariable moveError *>$null return $false
if ($MoveError) { $this.ExitDBMaintenance("Unable to replace Blobs DB with rebuilt DB: $MoveError", $false); return } }
$this.ExitDBMaintenance("Database repair/rebuild/reindex completed.", $true) $this.ExitDBMaintenance("Database repair/rebuild/reindex completed.", $true)
return $true
}
# Return whether we can continue DB repair (i.e. whether PMS is running) at the given stage in the process.
[bool] CheckPMS([string] $Stage, [string] $SubStage) {
if ($this.PMSRunning()) {
$SubMessage = if ($SubStage) { "during $SubStage" } else { "" }
$this.WriteLog("$Stage - FAIL - PMS running $SubMessage")
$this.OutputWarn("Unable to run $Stage. PMS is running. Please stop PlexMediaServer.")
return $false
}
return $true
}
# Try to move the source file to the destination. If it fails, attempt to find
# open file handles (requires handle.exe on PATH) and throw.
[void] MoveDatabase([string] $Source, [string] $Destination, [string] $FriendlyString) {
$MoveError = $null
Move-Item -Path $Source -Destination $Destination -ErrorVariable MoveError *>$null
if ($MoveError) {
$this.ExitDBMaintenance("Unable to $($FriendlyString): $MoveError", $false)
throw "Unable to move database"
}
} }
# Attempts to prune PhotoTranscoder images that are older than the specified date cutoff (30 days by default) # Attempts to prune PhotoTranscoder images that are older than the specified date cutoff (30 days by default)
@ -639,6 +681,10 @@ class PlexDBRepair {
} }
} }
[bool] ExportPlexDB([string] $Source, [string] $Destination) {
return $this.RunSQLCommand("""$Source"" "".output '$Destination'"" .dump", "Failed to export '$Source' to '$Destination'")
}
# Run an SQL command. # Run an SQL command.
# ErrorMessage is the message to output/write to the log on failure # ErrorMessage is the message to output/write to the log on failure
[bool] RunSQLCommand([string] $Command, [string] $ErrorMessage) { [bool] RunSQLCommand([string] $Command, [string] $ErrorMessage) {
@ -689,35 +735,15 @@ class PlexDBRepair {
return $true return $true
} }
[bool] ExportPlexDB([string] $Source, [string] $Destination) {
$SqlError = $null
$Command = """$Source"" .dump | Set-Content ""$Destination"" -Encoding utf8"
try {
Invoke-Expression "& ""$($this.PlexSQL)"" $Command" -ev SqlError -EA Stop *>$null
} catch {
$Err = $Error -join "`n"
# Even if we ignore errors, we can't continue if the export failed.
$this.ExitDBMaintenance("Failed to export '$Source' to '$Destination': '$Err'", $false)
$Error.Clear()
return $false
}
if ($SqlError) {
$Err = $SqlError -join "`n"
if ($this.Options.IgnoreErrors) {
$this.OutputWarn("Ignoring database errors during export: $Err")
} else {
$this.ExitDBMaintenance("Failed to export '$Source' to '$Destination': '$Err'", $false)
return $false
}
}
return $true
}
# Import an exported .sql file into a new database # Import an exported .sql file into a new database
[bool] ImportPlexDB($Source, $Destination) { [bool] ImportPlexDB($Source, $Destination) {
# SQLite's .read can't handle files larger than 2GB on versions <3.45.0 (https://sqlite.org/forum/forumpost/9af57ba66fbb5349),
# and Plex SQLite is currently on 3.39.4 (as of PMS 1.41.6).
# If the source is smaller than 2GB we can .read it directly, otherwise do things in a more roundabout way.
if ($this.FileExists($Source) -and (Get-Item $Source).Length -lt 2GB) {
return $this.RunSQLCommand("""$Destination"" "".read '$Source'""", "Failed to import Plex database (importing '$Source' into '$Destination')")
}
$ImportError = $null $ImportError = $null
$ExitCode = 0 $ExitCode = 0
$Err = $null $Err = $null
@ -791,7 +817,7 @@ class PlexDBRepair {
# Return whether PMS is running # Return whether PMS is running
[bool] PMSRunning() { [bool] PMSRunning() {
return !!$this.GetPMS() return $null -ne $this.GetPMS()
} }
# Retrieve the PMS process, if running # Retrieve the PMS process, if running

View File

@ -1,17 +1,22 @@
# PlexDBRepair-Windows # PlexDBRepair-Windows
Release notes for the Windows counterpart to DBRepair.sh (DBRepair-Windows.ps1) Release notes for the Windows counterpart to DBRepair.sh (DBRepair-Windows.ps1)
# Release Info # Release Info
v1.00.01 v1.00.02
- Bug Fix: Ensure UTF-8 characters get exported/imported properly. - Check whether PMS is running at more points in the process.
- Don't remove temp files in scripted mode if the last operation failed.
v1.00.00 - Better export process that improves upon the UTF-8 fix in v1.00.01.
- Initial Windows PowerShell script release, aiming to provide a similar experience as DBRepair.sh, with command-name-based input.
- Initial command support: v1.00.01
- AUTO(matic) - Bug Fix: Ensure UTF-8 characters get exported/imported properly.
- EXIT
- PRUN(e) v1.00.00
- STAR(t) - Initial Windows PowerShell script release, aiming to provide a similar experience as DBRepair.sh, with command-name-based input.
- STOP - Initial command support:
- AUTO(matic)
- EXIT
- PRUN(e)
- STAR(t)
- STOP