9bcb8501ab
- Introduced `publish-with-sql.ps1` to publish Jellyfin with SQL files. - Added `remove-sqlite.ps1` to facilitate the removal of SQLite dependencies for PostgreSQL-only deployment. - Created `rollback-to-net10.ps1` to revert .NET 11 changes back to .NET 10. - Implemented `test_api.py` for testing API interactions. - Added `verify-migration.ps1` to verify PostgreSQL migration steps. - Updated various `.csproj` files to include `Microsoft.Kiota.Abstractions` package. - Enhanced `JellyfinDbContext` to handle concurrency exceptions during save operations. - Updated tests to include new package references and ensure compatibility with changes.
389 lines
12 KiB
PowerShell
389 lines
12 KiB
PowerShell
#!/usr/bin/env pwsh
|
|
<#
|
|
.SYNOPSIS
|
|
Converts C# files from block-scoped to file-scoped namespaces and fixes System namespace conflicts.
|
|
|
|
.DESCRIPTION
|
|
This script converts all C# files in MediaBrowser.Model from block-scoped namespaces
|
|
to file-scoped namespaces, and replaces System using directives with global::System
|
|
to resolve conflicts with MediaBrowser.Model.System namespace.
|
|
|
|
.PARAMETER Path
|
|
The root path to search for C# files. Defaults to "MediaBrowser.Model"
|
|
|
|
.PARAMETER DryRun
|
|
If specified, shows what would be changed without actually modifying files
|
|
|
|
.PARAMETER Verbose
|
|
Shows detailed processing information
|
|
|
|
.PARAMETER BackupFiles
|
|
Creates .bak files before conversion
|
|
|
|
.EXAMPLE
|
|
.\ConvertToFileScopedNamespaces.ps1 -DryRun
|
|
|
|
.EXAMPLE
|
|
.\ConvertToFileScopedNamespaces.ps1 -BackupFiles
|
|
|
|
.EXAMPLE
|
|
.\ConvertToFileScopedNamespaces.ps1 -Path "MediaBrowser.Model\Dlna" -Verbose
|
|
#>
|
|
|
|
param(
|
|
[string]$Path = "MediaBrowser.Model",
|
|
[switch]$DryRun,
|
|
[switch]$BackupFiles
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$VerbosePreference = if ($PSBoundParameters.ContainsKey('Verbose')) { 'Continue' } else { 'SilentlyContinue' }
|
|
|
|
function Get-IndentString {
|
|
param([string]$Line)
|
|
|
|
if ($Line -match '^(\s+)') {
|
|
return $Matches[1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
function Get-IndentLevel {
|
|
param([string]$IndentString)
|
|
|
|
# Count spaces (4 spaces = 1 level) or tabs (1 tab = 1 level)
|
|
$spaces = ($IndentString.ToCharArray() | Where-Object { $_ -eq ' ' }).Count
|
|
$tabs = ($IndentString.ToCharArray() | Where-Object { $_ -eq "`t" }).Count
|
|
|
|
return [Math]::Max($tabs, [Math]::Floor($spaces / 4))
|
|
}
|
|
|
|
function Remove-OneIndentLevel {
|
|
param([string]$Line, [ref]$IndentChar, [ref]$IndentSize)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Line)) {
|
|
return $Line
|
|
}
|
|
|
|
$indent = Get-IndentString -Line $Line
|
|
|
|
if ($indent.Length -eq 0) {
|
|
return $Line
|
|
}
|
|
|
|
# Detect indent character and size on first indented line
|
|
if ($IndentChar.Value -eq $null) {
|
|
if ($indent[0] -eq "`t") {
|
|
$IndentChar.Value = "`t"
|
|
$IndentSize.Value = 1
|
|
} else {
|
|
$IndentChar.Value = ' '
|
|
# Detect indent size (usually 4 spaces)
|
|
$IndentSize.Value = 4
|
|
if ($indent.Length -ge 2) {
|
|
# Try to detect actual indent size
|
|
for ($i = 1; $i -le 8; $i++) {
|
|
if ($indent.Length % $i -eq 0) {
|
|
$IndentSize.Value = $i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Write-Verbose "Detected indent: '$($IndentChar.Value)' x $($IndentSize.Value)"
|
|
}
|
|
|
|
# Remove one level of indentation
|
|
if ($IndentChar.Value -eq "`t") {
|
|
if ($Line -match '^\t(.*)') {
|
|
return $Matches[1]
|
|
}
|
|
} else {
|
|
$pattern = "^" + (' ' * $IndentSize.Value) + '(.*)'
|
|
if ($Line -match $pattern) {
|
|
return $Matches[1]
|
|
}
|
|
}
|
|
|
|
return $Line
|
|
}
|
|
|
|
function Test-FileScopedNamespace {
|
|
param([string]$Content)
|
|
|
|
# Check if already using file-scoped namespace (semicolon after namespace declaration)
|
|
return $Content -match 'namespace\s+[\w\.]+\s*;'
|
|
}
|
|
|
|
function Test-BlockScopedNamespace {
|
|
param([string]$Content)
|
|
|
|
# Check for block-scoped namespace
|
|
return $Content -match 'namespace\s+[\w\.]+\s*\r?\n?\s*\{'
|
|
}
|
|
|
|
function Add-GlobalPrefix {
|
|
param([string]$Content)
|
|
|
|
# System namespaces to prefix (ordered from most specific to least)
|
|
$systemNamespaces = @(
|
|
'System\.Xml\.Serialization',
|
|
'System\.ComponentModel\.DataAnnotations',
|
|
'System\.ComponentModel',
|
|
'System\.Collections\.Generic',
|
|
'System\.Collections\.Concurrent',
|
|
'System\.Collections',
|
|
'System\.Diagnostics\.CodeAnalysis',
|
|
'System\.Diagnostics',
|
|
'System\.Globalization',
|
|
'System\.Linq\.Expressions',
|
|
'System\.Linq',
|
|
'System\.Text\.Json\.Serialization',
|
|
'System\.Text\.Json',
|
|
'System\.Text\.RegularExpressions',
|
|
'System\.Text',
|
|
'System\.Threading\.Tasks',
|
|
'System\.Threading',
|
|
'System\.Runtime\.Serialization',
|
|
'System\.Runtime\.CompilerServices',
|
|
'System\.Runtime',
|
|
'System\.Net\.Http',
|
|
'System\.Net\.Sockets',
|
|
'System\.Net\.WebSockets',
|
|
'System\.Net\.Mime',
|
|
'System\.Net',
|
|
'System\.IO\.Compression',
|
|
'System\.IO',
|
|
'System\.Security',
|
|
'System' # Must be last to avoid partial matches
|
|
)
|
|
|
|
foreach ($ns in $systemNamespaces) {
|
|
# Don't add global:: if it's already there
|
|
# Match: "using System.X" but not "using global::System.X"
|
|
$pattern = '(\s+using\s+)(?!global::)(' + $ns + ')(\s|;)'
|
|
$replacement = '${1}global::${2}${3}'
|
|
$Content = $Content -replace $pattern, $replacement
|
|
}
|
|
|
|
return $Content
|
|
}
|
|
|
|
function Convert-ToFileScopedNamespace {
|
|
param(
|
|
[string]$FilePath,
|
|
[switch]$DryRun,
|
|
[switch]$CreateBackup
|
|
)
|
|
|
|
$content = Get-Content -Path $FilePath -Raw
|
|
$originalContent = $content
|
|
$modified = $false
|
|
|
|
# Check current state
|
|
$hasFileScopedNS = Test-FileScopedNamespace -Content $content
|
|
$hasBlockScopedNS = Test-BlockScopedNamespace -Content $content
|
|
|
|
if ($hasFileScopedNS) {
|
|
Write-Verbose " Already using file-scoped namespace"
|
|
|
|
# But still add global:: prefixes if needed
|
|
$contentWithGlobal = Add-GlobalPrefix -Content $content
|
|
if ($contentWithGlobal -ne $content) {
|
|
$content = $contentWithGlobal
|
|
$modified = $true
|
|
Write-Host " [UPDATED] Added global:: prefixes" -ForegroundColor Yellow
|
|
} else {
|
|
Write-Host " [SKIP] Already correct" -ForegroundColor Gray
|
|
return $false
|
|
}
|
|
}
|
|
elseif (-not $hasBlockScopedNS) {
|
|
Write-Host " [SKIP] No namespace found" -ForegroundColor Gray
|
|
return $false
|
|
}
|
|
else {
|
|
Write-Verbose " Converting block-scoped to file-scoped namespace"
|
|
|
|
# Extract namespace name and line
|
|
if ($content -match 'namespace\s+([\w\.]+)\s*\r?\n?\s*\{') {
|
|
$namespaceName = $Matches[1]
|
|
Write-Verbose " Namespace: $namespaceName"
|
|
} else {
|
|
Write-Warning " Could not parse namespace"
|
|
return $false
|
|
}
|
|
|
|
# Step 1: Convert namespace declaration to file-scoped
|
|
$content = $content -replace '(namespace\s+[\w\.]+)\s*\r?\n?\s*\{', '$1;'
|
|
$modified = $true
|
|
|
|
# Step 2: Add global:: prefixes to System using directives
|
|
$content = Add-GlobalPrefix -Content $content
|
|
|
|
# Step 3: Parse into lines for indentation removal
|
|
$lines = $content -split '\r?\n'
|
|
$hasWindowsLineEndings = $originalContent -match '\r\n'
|
|
|
|
# Find namespace declaration line
|
|
$namespaceLineIndex = -1
|
|
for ($i = 0; $i -lt $lines.Length; $i++) {
|
|
if ($lines[$i] -match '^namespace\s+[\w\.]+\s*;') {
|
|
$namespaceLineIndex = $i
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($namespaceLineIndex -lt 0) {
|
|
Write-Warning " Could not find namespace declaration after conversion"
|
|
return $false
|
|
}
|
|
|
|
# Step 4: Remove one level of indentation from all lines after namespace
|
|
$indentChar = $null
|
|
$indentSize = 4
|
|
$indentCharRef = [ref]$indentChar
|
|
$indentSizeRef = [ref]$indentSize
|
|
|
|
for ($i = $namespaceLineIndex + 1; $i -lt $lines.Length; $i++) {
|
|
$lines[$i] = Remove-OneIndentLevel -Line $lines[$i] -IndentChar $indentCharRef -IndentSize $indentSizeRef
|
|
}
|
|
|
|
# Step 5: Remove the closing brace of the namespace
|
|
# Find the last non-empty, non-whitespace line that's just a closing brace
|
|
$closingBraceIndex = -1
|
|
for ($i = $lines.Length - 1; $i -gt $namespaceLineIndex; $i--) {
|
|
$trimmed = $lines[$i].Trim()
|
|
if ($trimmed -eq '}') {
|
|
$closingBraceIndex = $i
|
|
break
|
|
}
|
|
elseif ($trimmed -ne '' -and $trimmed -ne '}') {
|
|
# Found non-brace content, stop looking
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($closingBraceIndex -ge 0) {
|
|
Write-Verbose " Removing closing brace at line $closingBraceIndex"
|
|
$lines = $lines[0..($closingBraceIndex-1)] + $lines[($closingBraceIndex+1)..($lines.Length-1)]
|
|
}
|
|
|
|
# Step 6: Rejoin lines
|
|
$lineEnding = if ($hasWindowsLineEndings) { "`r`n" } else { "`n" }
|
|
$content = ($lines | Where-Object { $_ -ne $null }) -join $lineEnding
|
|
|
|
# Step 7: Clean up trailing whitespace and ensure single newline at end
|
|
$content = $content.TrimEnd()
|
|
$content += $lineEnding
|
|
|
|
Write-Host " [CONVERTED] Block-scoped → File-scoped" -ForegroundColor Green
|
|
}
|
|
|
|
# Apply changes
|
|
if ($modified -and $content -ne $originalContent) {
|
|
if ($DryRun) {
|
|
Write-Host " [DRY-RUN] Would save changes" -ForegroundColor Cyan
|
|
return $true
|
|
}
|
|
|
|
if ($CreateBackup) {
|
|
$backupPath = "$FilePath.bak"
|
|
Copy-Item -Path $FilePath -Destination $backupPath -Force
|
|
Write-Verbose " Created backup: $backupPath"
|
|
}
|
|
|
|
# Write with correct encoding (UTF-8 without BOM for .cs files)
|
|
[System.IO.File]::WriteAllText($FilePath, $content, [System.Text.UTF8Encoding]::new($false))
|
|
return $true
|
|
}
|
|
|
|
return $false
|
|
}
|
|
|
|
# Main script
|
|
Write-Host ("=" * 80) -ForegroundColor Cyan
|
|
Write-Host "C# File-Scoped Namespace Converter" -ForegroundColor Cyan
|
|
Write-Host "Fixes MediaBrowser.Model.System namespace conflicts" -ForegroundColor Cyan
|
|
Write-Host ("=" * 80) -ForegroundColor Cyan
|
|
Write-Host
|
|
|
|
if ($DryRun) {
|
|
Write-Host "DRY RUN MODE - No files will be modified" -ForegroundColor Yellow
|
|
Write-Host
|
|
}
|
|
|
|
if ($BackupFiles) {
|
|
Write-Host "Backup mode enabled - .bak files will be created" -ForegroundColor Yellow
|
|
Write-Host
|
|
}
|
|
|
|
if (-not (Test-Path $Path)) {
|
|
Write-Error "❌ Path not found: $Path"
|
|
exit 1
|
|
}
|
|
|
|
# Find all C# files
|
|
$files = Get-ChildItem -Path $Path -Filter "*.cs" -Recurse | Where-Object {
|
|
$_.FullName -notmatch "\\obj\\" -and
|
|
$_.FullName -notmatch "\\bin\\" -and
|
|
$_.FullName -notmatch "\\Properties\\AssemblyInfo\.cs$"
|
|
}
|
|
|
|
Write-Host "Found $($files.Count) C# files in $Path" -ForegroundColor Cyan
|
|
Write-Host
|
|
|
|
$convertedCount = 0
|
|
$skippedCount = 0
|
|
$errorCount = 0
|
|
$updatedCount = 0
|
|
|
|
foreach ($file in $files) {
|
|
$relativePath = $file.FullName.Replace((Get-Location).Path, "").TrimStart([char]92, [char]47)
|
|
Write-Host "[Processing] $relativePath" -ForegroundColor White
|
|
|
|
try {
|
|
$result = Convert-ToFileScopedNamespace -FilePath $file.FullName -DryRun:$DryRun -CreateBackup:$BackupFiles
|
|
|
|
if ($result) {
|
|
if ($result -eq "updated") {
|
|
$updatedCount++
|
|
} else {
|
|
$convertedCount++
|
|
}
|
|
} else {
|
|
$skippedCount++
|
|
}
|
|
}
|
|
catch {
|
|
Write-Host " [ERROR] $_" -ForegroundColor Red
|
|
Write-Verbose $_.ScriptStackTrace
|
|
$errorCount++
|
|
}
|
|
}
|
|
|
|
Write-Host
|
|
Write-Host ("=" * 80) -ForegroundColor Cyan
|
|
Write-Host "Summary:" -ForegroundColor Cyan
|
|
Write-Host ("=" * 80) -ForegroundColor Cyan
|
|
Write-Host " Total files: $($files.Count)" -ForegroundColor White
|
|
Write-Host " Converted: $convertedCount" -ForegroundColor Green
|
|
Write-Host " Updated (global::): $updatedCount" -ForegroundColor Yellow
|
|
Write-Host " Skipped: $skippedCount" -ForegroundColor Gray
|
|
Write-Host " Errors: $errorCount" -ForegroundColor Red
|
|
|
|
if ($DryRun) {
|
|
Write-Host
|
|
Write-Host "This was a DRY RUN. Run without -DryRun to apply changes." -ForegroundColor Yellow
|
|
} elseif ($convertedCount -gt 0 -or $updatedCount -gt 0) {
|
|
Write-Host
|
|
Write-Host "Conversion complete! Next steps:" -ForegroundColor Green
|
|
Write-Host " 1. Review changes with: git diff" -ForegroundColor White
|
|
Write-Host " 2. Build project: dotnet build MediaBrowser.Model\MediaBrowser.Model.csproj" -ForegroundColor White
|
|
Write-Host " 3. If successful: git add . && git commit -m `"Convert to file-scoped namespaces`"" -ForegroundColor White
|
|
}
|
|
|
|
Write-Host ("=" * 80) -ForegroundColor Cyan
|
|
|
|
exit $errorCount
|