Merge pull request #188 from danrahn/capture_constraint_errors

[Windows] Add Honor/Ignore options and ensure all import errors are captured
This commit is contained in:
Chuck 2025-02-24 01:59:26 -05:00 committed by GitHub
commit d505486e95
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -77,6 +77,11 @@ class PlexDBRepair {
Write-Host " 7 - 'start' - Start PMS" Write-Host " 7 - 'start' - Start PMS"
Write-Host Write-Host
Write-Host " 21 - 'prune' - Prune (remove) old image files (jpeg,jpg,png) from PhotoTranscoder cache." Write-Host " 21 - 'prune' - Prune (remove) old image files (jpeg,jpg,png) from PhotoTranscoder cache."
if ($this.Options.IgnoreErrors) {
Write-Host " 42 - 'honor' - Honor all database errors."
} else {
Write-Host " 42 - 'ignore' - Ignore duplicate/constraint errors."
}
Write-Host Write-Host
Write-Host " 99 - 'quit' - Quit immediately. Keep all temporary files." Write-Host " 99 - 'quit' - Quit immediately. Keep all temporary files."
Write-Host " 'exit' - Exit with cleanup options." Write-Host " 'exit' - Exit with cleanup options."
@ -185,6 +190,16 @@ class PlexDBRepair {
"^(2|autom?a?t?i?c?)$" { $this.RunAutomaticDatabaseMaintenance() } "^(2|autom?a?t?i?c?)$" { $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?)$" {
if (($this.Options.IgnoreErrors -and ($Choice[0] -eq 'i')) -or (!$this.Options.IgnoreErrors -and ($Choice[0] -eq 'h'))) {
Write-Host "Honor/Ignore setting unchanged."
Break
}
$this.Options.IgnoreErrors = !$this.Options.IgnoreErrors
$msg = if ($this.Options.IgnoreErrors) { "Ignoring database errors." } else { "Honoring database errors." }
$this.WriteOutputLog($msg)
}
"^(99|quit)$" { "^(99|quit)$" {
$this.Output("Retaining all remporary work files.") $this.Output("Retaining all remporary work files.")
$this.WriteLog("Exit - Retain temp files.") $this.WriteLog("Exit - Retain temp files.")
@ -308,7 +323,7 @@ class PlexDBRepair {
return return
} }
if (!$this.RunSQLCommand("""$MainDB"" .dump | Set-Content ""$MainDBSQL"" -Encoding utf8", "Failed to export main database")) { return } if (!$this.ExportPlexDB($MainDB, $MainDBSQL)) { return }
$this.Output("Exporting Blobs DB") $this.Output("Exporting Blobs DB")
$BlobsDBName = "com.plexapp.plugins.library.blobs.db" $BlobsDBName = "com.plexapp.plugins.library.blobs.db"
@ -319,7 +334,7 @@ class PlexDBRepair {
return return
} }
if (!$this.RunSQLCommand("""$BlobsDB"" .dump | Set-Content ""$BlobsDBSQL"" -Encoding utf8", "Failed to export blobs database")) { return } if (!$this.ExportPlexDB($BlobsDB, $BlobsDBSQL)) { return }
$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")
@ -337,23 +352,11 @@ class PlexDBRepair {
$this.Output("Verifying databases integrity after importing.") $this.Output("Verifying databases integrity after importing.")
$VerifyResult = "" if (!$this.IntegrityCheck($MainDBImport, "Main")) { return }
if (!$this.GetSQLCommandResult("""$MainDBImport"" ""PRAGMA integrity_check(1)""", "Failed to verify main DB", [ref]$VerifyResult)) { return }
$this.Output("Main DB verification check is: $VerifyResult")
if ($VerifyResult -ne "ok") {
$this.ExitDBMaintenance("Main DB verification failed: $VerifyResult", $false)
return
}
$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.GetSQLCommandResult("""$BlobsDBImport"" ""PRAGMA integrity_check(1)""", "Failed to verify main DB", [ref]$VerifyResult)) { return } if (!$this.IntegrityCheck($BlobsDBImport, "Blobs")) { return }
if ($VerifyResult -ne "ok") {
$this.ExitDBMaintenance("Blobs DB verification failed: $VerifyResult", $false)
return
}
$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")
@ -652,8 +655,10 @@ class PlexDBRepair {
[bool] RunSQLCommandCore([string] $Command, [string] $ErrorMessage, [ref] $Output) { [bool] RunSQLCommandCore([string] $Command, [string] $ErrorMessage, [ref] $Output) {
$SqlError = $null $SqlError = $null
$SqlResult = $null $SqlResult = $null
$ExitCode = 0
try { try {
Invoke-Expression "& ""$($this.PlexSQL)"" $Command" -ev sqlError -OutVariable sqlResult -EA Stop *>$null Invoke-Expression "& ""$($this.PlexSQL)"" $Command" -ev sqlError -OutVariable sqlResult -EA Stop *>$null
$ExitCode = $LASTEXITCODE
} catch { } catch {
$Err = $Error -join "`n" $Err = $Error -join "`n"
$this.ExitDBMaintenance("Failed to run command '$Command': '$Err'", $false) $this.ExitDBMaintenance("Failed to run command '$Command': '$Err'", $false)
@ -661,16 +666,21 @@ class PlexDBRepair {
return $false return $false
} }
if ($SqlError) { if ($SqlError -or $ExitCode) {
$Err = $SqlError -join "`n" $Err = $SqlError -join "`n"
if (!$Err) { $Err = "Process exited with error code $ExitCode" }
$Msg = $ErrorMessage $Msg = $ErrorMessage
if (!$Msg) { if (!$Msg) {
$Msg = "Plex SQLite operation failed" $Msg = "Plex SQLite operation failed"
} }
$this.ExitDBMaintenance("${msg}: $Err", $false) if ($this.Options.IgnoreErrors -and $this.Options.CanIgnore) {
$this.OutputWarn("Ignoring database errors - ${Msg}: $Err")
} else {
$this.ExitDBMaintenance("${Msg}: $Err", $false)
return $false return $false
} }
}
if ($null -ne $Output.Value) { if ($null -ne $Output.Value) {
$Output.Value = $SqlResult $Output.Value = $SqlResult
@ -679,21 +689,58 @@ class PlexDBRepair {
return $true return $true
} }
# Import an exported .sql file into a new database [bool] ExportPlexDB([string] $Source, [string] $Destination) {
[bool] ImportPlexDB($Source, $Destination) { $SqlError = $null
$ImportError = $null $Command = """$Source"" .dump | Set-Content ""$Destination"" -Encoding utf8"
try { try {
# Use Start-Process, since PowerShell doesn't have '<', and alternatives ("Get-Content X | SQLite.exe OutDB") are subpar at best when dealing with large files like these database exports. Invoke-Expression "& ""$($this.PlexSQL)"" $Command" -ev SqlError -EA Stop *>$null
Start-Process $this.PlexSQL -ArgumentList @("""$Destination""") -RedirectStandardInput $Source -NoNewWindow -Wait -EA Stop -ErrorVariable importError
} catch { } catch {
$Err = $Error -join "`n" $Err = $Error -join "`n"
$this.ExitDBMaintenance("Failed to import Plex database (importing '$Source' into '$Destination): $Err", $false) # 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() $Error.Clear()
return $false 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
[bool] ImportPlexDB($Source, $Destination) {
$ImportError = $null
$ExitCode = 0
$Err = $null
try {
# Use Start-Process, since PowerShell doesn't have '<', and alternatives ("Get-Content X | SQLite.exe OutDB") are subpar at best when dealing with large files like these database exports.
$process = Start-Process $this.PlexSQL -ArgumentList @("""$Destination""") -RedirectStandardInput $Source -NoNewWindow -Wait -PassThru -EA Stop -ErrorVariable ImportError
$ExitCode = $process.ExitCode
} catch {
$Err = $Error -join "`n"
$Error.Clear()
}
if ($ImportError) { if ($ImportError) {
$Err = $ImportError -join "`n" $Err = $ImportError -join "`n"
} elseif ($ExitCode) {
if ($this.Options.IgnoreErrors) {
$this.OutputWarn("Ignoring errors found during import")
} else {
$Err = "Process exited with error code $ExitCode (constraint error?)"
}
}
if ($Err) {
$this.ExitDBMaintenance("Failed to import Plex database (importing '$Source' into '$Destination'): $Err", $false) $this.ExitDBMaintenance("Failed to import Plex database (importing '$Source' into '$Destination'): $Err", $false)
return $false return $false
} }
@ -701,6 +748,22 @@ class PlexDBRepair {
return $true return $true
} }
[bool] IntegrityCheck([string] $Database, [string] $DbName) {
$this.Options.CanIgnore = $false
$VerifyResult = ""
$result = $this.GetSQLCommandResult("""$Database"" ""PRAGMA integrity_check(1)""", "Failed to verify $dbName DB", [ref]$VerifyResult)
if ($result) {
$this.Output("$DbName DB verification check is: $VerifyResult")
if ($VerifyResult -ne "ok") {
$this.ExitDBMaintenance("$DbName DB verification failed: $VerifyResult", $false)
$result = $false
}
}
$this.Options.CanIgnore = $false
return $result
}
# Clear out the temp database directory. If $Confirm is $true, asks the user before doing so. # Clear out the temp database directory. If $Confirm is $true, asks the user before doing so.
[void] CleanDBTemp([bool] $Confirm) { [void] CleanDBTemp([bool] $Confirm) {
if ($Confirm -and !$this.GetYesNo("Ok to remove temporary databases/workfiles for this session")) { if ($Confirm -and !$this.GetYesNo("Ok to remove temporary databases/workfiles for this session")) {
@ -755,12 +818,16 @@ class PlexDBRepair {
class PlexDBRepairOptions { class PlexDBRepairOptions {
[bool] $Scripted # Whether we're running in scripted or interactive mode [bool] $Scripted # Whether we're running in scripted or interactive mode
[bool] $ShowMenu # Whether to show the menu after each command executes [bool] $ShowMenu # Whether to show the menu after each command executes
[bool] $IgnoreErrors # Whether to honor or ignore constraint errors on import
[bool] $CanIgnore # Some errors can't be ignored (e.g. integrity_check)
[int32] $CacheAge # The date cutoff for pruning PhotoTranscoder cached images [int32] $CacheAge # The date cutoff for pruning PhotoTranscoder cached images
PlexDBRepairOptions() { PlexDBRepairOptions() {
$this.CacheAge = 30 $this.CacheAge = 30
$this.ShowMenu = $true $this.ShowMenu = $true
$this.Scripted = $false $this.Scripted = $false
$this.IgnoreErrors = $false
$this.CanIgnore = $true
} }
} }