# Async Migration Analysis Script # Scans the Jellyfin codebase for synchronous database operations that need conversion param( [string]$Path = ".", [switch]$DetailedReport, [switch]$ExportCsv ) Write-Host "🔍 Scanning for synchronous database operations..." -ForegroundColor Cyan Write-Host "" # Define patterns to search for $patterns = @{ "SaveChanges" = @{ Pattern = "\.SaveChanges\s*\(\s*\)" Replacement = "await .SaveChangesAsync(cancellationToken)" Severity = "HIGH" Description = "Synchronous database save operation" } "ToList" = @{ Pattern = "\.ToList\s*\(\s*\)" Replacement = "await .ToListAsync(cancellationToken)" Severity = "HIGH" Description = "Synchronous list materialization" } "ToArray" = @{ Pattern = "\.ToArray\s*\(\s*\)" Replacement = "await .ToArrayAsync(cancellationToken)" Severity = "HIGH" Description = "Synchronous array materialization" } "FirstOrDefault" = @{ Pattern = "\.FirstOrDefault\s*\(" Replacement = 'await .FirstOrDefaultAsync(' Severity = "HIGH" Description = "Synchronous first element query" } "First" = @{ Pattern = "\.First\s*\(" Replacement = 'await .FirstAsync(' Severity = "HIGH" Description = "Synchronous first element query" } "SingleOrDefault" = @{ Pattern = "\.SingleOrDefault\s*\(" Replacement = 'await .SingleOrDefaultAsync(' Severity = "MEDIUM" Description = "Synchronous single element query" } "Single" = @{ Pattern = "\.Single\s*\(" Replacement = 'await .SingleAsync(' Severity = "MEDIUM" Description = "Synchronous single element query" } "Any" = @{ Pattern = "\.Any\s*\(" Replacement = 'await .AnyAsync(' Severity = "MEDIUM" Description = "Synchronous existence check" } "Count" = @{ Pattern = "\.Count\s*\(\s*\)" Replacement = "await .CountAsync(cancellationToken)" Severity = "MEDIUM" Description = "Synchronous count operation" } "LongCount" = @{ Pattern = "\.LongCount\s*\(" Replacement = "await .LongCountAsync(" Severity = "MEDIUM" Description = "Synchronous long count operation" } "ExecuteDelete" = @{ Pattern = "\.ExecuteDelete\s*\(\s*\)" Replacement = "await .ExecuteDeleteAsync(cancellationToken)" Severity = "HIGH" Description = "Synchronous bulk delete" } "ExecuteUpdate" = @{ Pattern = "\.ExecuteUpdate\s*\(" Replacement = "await .ExecuteUpdateAsync(" Severity = "HIGH" Description = "Synchronous bulk update" } "BeginTransaction" = @{ Pattern = "\.BeginTransaction\s*\(\s*\)" Replacement = "await .BeginTransactionAsync(cancellationToken)" Severity = "HIGH" Description = "Synchronous transaction start" } "Commit" = @{ Pattern = "transaction\.Commit\s*\(\s*\)" Replacement = "await transaction.CommitAsync(cancellationToken)" Severity = "HIGH" Description = "Synchronous transaction commit" } "Rollback" = @{ Pattern = "transaction\.Rollback\s*\(\s*\)" Replacement = "await transaction.RollbackAsync(cancellationToken)" Severity = "MEDIUM" Description = "Synchronous transaction rollback" } "Sum" = @{ Pattern = "\.Sum\s*\(" Replacement = 'await .SumAsync(' Severity = "LOW" Description = "Synchronous sum aggregation" } "Average" = @{ Pattern = "\.Average\s*\(" Replacement = 'await .AverageAsync(' Severity = "LOW" Description = "Synchronous average aggregation" } "Min" = @{ Pattern = "\.Min\s*\(" Replacement = 'await .MinAsync(' Severity = "LOW" Description = "Synchronous min aggregation" } "Max" = @{ Pattern = "\.Max\s*\(" Replacement = 'await .MaxAsync(' Severity = "LOW" Description = "Synchronous max aggregation" } "Load" = @{ Pattern = "\.Load\s*\(\s*\)" Replacement = 'await .LoadAsync(cancellationToken)' Severity = "MEDIUM" Description = "Synchronous navigation property load" } } # Exclude patterns (already async or intentionally sync) $excludePatterns = @( "Async\s*\(", "// ASYNC", "\.ConfigureAwait", "AsyncEnumerable" ) # Get all .cs files, excluding test files and migrations initially $files = Get-ChildItem -Path $Path -Recurse -Filter "*.cs" | Where-Object { $_.FullName -notmatch "\\bin\\" -and $_.FullName -notmatch "\\obj\\" -and $_.FullName -notmatch "\\Migrations\\" -and $_.FullName -notmatch "\.Designer\.cs$" } $results = @() $summary = @{} foreach ($file in $files) { $content = Get-Content -Path $file.FullName -Raw foreach ($patternName in $patterns.Keys) { $patternInfo = $patterns[$patternName] $pattern = $patternInfo.Pattern # Find matches $matches = [regex]::Matches($content, $pattern) foreach ($match in $matches) { # Check if this line is already async or should be excluded $lineStart = $content.Substring(0, $match.Index).LastIndexOf("`n") if ($lineStart -lt 0) { $lineStart = 0 } $lineEnd = $content.IndexOf("`n", $match.Index) if ($lineEnd -lt 0) { $lineEnd = $content.Length } $line = $content.Substring($lineStart, $lineEnd - $lineStart).Trim() # Skip if already async or excluded $shouldExclude = $false foreach ($excludePattern in $excludePatterns) { if ($line -match $excludePattern) { $shouldExclude = $true break } } if (-not $shouldExclude) { # Calculate line number $lineNumber = ($content.Substring(0, $match.Index) -split "`n").Count $result = [PSCustomObject]@{ File = $file.FullName.Replace($Path, "").TrimStart('\') LineNumber = $lineNumber Pattern = $patternName Severity = $patternInfo.Severity Description = $patternInfo.Description MatchedText = $match.Value Line = $line Replacement = $patternInfo.Replacement } $results += $result # Update summary if (-not $summary.ContainsKey($patternName)) { $summary[$patternName] = 0 } $summary[$patternName]++ } } } } # Display results Write-Host "📊 Analysis Complete!" -ForegroundColor Green Write-Host "" Write-Host "=" * 80 -ForegroundColor DarkGray Write-Host "SUMMARY" -ForegroundColor Yellow Write-Host "=" * 80 -ForegroundColor DarkGray Write-Host "" $totalIssues = $results.Count $highSeverity = ($results | Where-Object { $_.Severity -eq "HIGH" }).Count $mediumSeverity = ($results | Where-Object { $_.Severity -eq "MEDIUM" }).Count $lowSeverity = ($results | Where-Object { $_.Severity -eq "LOW" }).Count Write-Host "Total synchronous operations found: " -NoNewline Write-Host $totalIssues -ForegroundColor Red Write-Host "" Write-Host " 🔴 HIGH severity: " -NoNewline Write-Host $highSeverity -ForegroundColor Red Write-Host " 🟡 MEDIUM severity: " -NoNewline Write-Host $mediumSeverity -ForegroundColor Yellow Write-Host " 🟢 LOW severity: " -NoNewline Write-Host $lowSeverity -ForegroundColor Green Write-Host "" Write-Host "Breakdown by operation type:" -ForegroundColor Cyan Write-Host "" $summary.GetEnumerator() | Sort-Object Value -Descending | ForEach-Object { $severity = $patterns[$_.Key].Severity $color = switch ($severity) { "HIGH" { "Red" } "MEDIUM" { "Yellow" } "LOW" { "Green" } } Write-Host " $($_.Key): " -NoNewline Write-Host $_.Value -ForegroundColor $color -NoNewline Write-Host " ($severity)" } Write-Host "" Write-Host "=" * 80 -ForegroundColor DarkGray if ($DetailedReport) { Write-Host "" Write-Host "DETAILED FINDINGS" -ForegroundColor Yellow Write-Host "=" * 80 -ForegroundColor DarkGray Write-Host "" # Group by file $resultsByFile = $results | Group-Object -Property File foreach ($fileGroup in $resultsByFile | Sort-Object Name) { Write-Host "" Write-Host "📄 $($fileGroup.Name)" -ForegroundColor Cyan Write-Host "-" * 80 -ForegroundColor DarkGray foreach ($item in $fileGroup.Group | Sort-Object LineNumber) { $severityColor = switch ($item.Severity) { "HIGH" { "Red" } "MEDIUM" { "Yellow" } "LOW" { "Green" } } Write-Host " Line $($item.LineNumber): " -NoNewline Write-Host "$($item.Pattern) " -NoNewline -ForegroundColor $severityColor Write-Host "- $($item.Description)" Write-Host " Current: " -NoNewline Write-Host $item.MatchedText -ForegroundColor Gray Write-Host " Replacement: " -NoNewline Write-Host $item.Replacement -ForegroundColor Green } } } if ($ExportCsv) { $csvPath = Join-Path $Path "async_migration_report.csv" $results | Export-Csv -Path $csvPath -NoTypeInformation Write-Host "" Write-Host "📁 Report exported to: " -NoNewline Write-Host $csvPath -ForegroundColor Cyan } Write-Host "" Write-Host "💡 TIP: Run with -DetailedReport for line-by-line analysis" -ForegroundColor DarkGray Write-Host "💡 TIP: Run with -ExportCsv to export findings to CSV" -ForegroundColor DarkGray Write-Host "" # Return results for further processing return $results