# Welche Quelldateien beim Uebernehmen KEINEN eigenen Platz bekommen haben. # # Beim Import der Item-Bilder kamen 3669 Dateien an, aber nur 3576 Eintraege # heraus. Die Differenz sind KEINE Fehler -- es sind Namen, die nach der # Zaehmung (safeFilename: Kleinbuchstaben, Fremdes wird zum Strich) auf # denselben Pfad fallen: # # WEAPON_SMG.png -> weapon_smg.png # weapon_smg.png -> weapon_smg.png <- derselbe Platz # # Der Import laedt mit overwrite=false, hat also NICHTS ueberschrieben. Die # zweite Datei blieb schlicht liegen. # # DIESES SKRIPT AENDERT NICHTS. Es liest die Quelle, rechnet dieselbe Zaehmung # nach und zeigt, welche Namen zusammenfallen -- damit die Entscheidung darueber # auf Zahlen steht und nicht auf einer Vermutung. # # pwsh -File tools\kollisionen.ps1 -Von 'CDN/items' # pwsh -File tools\kollisionen.ps1 -Von 'CDN/items' -Csv befund.csv param( [Parameter(Mandatory)][string]$Von, [string]$Csv = '' ) $ErrorActionPreference = 'Stop' # ------------------------------------------------- Zugang aus dem Dashboard # # Dieselbe Quelle wie Sicherung und Import: die Werte stehen im Panel unter # Einstellungen und damit in media.db -- so verlaesst das Passwort den Rechner # nicht. function hol($schluessel) { docker exec d4rk-media node -e @" const db = require('better-sqlite3')('/data/media.db', { readonly: true }) const z = db.prepare('SELECT value FROM settings WHERE key = ?').get('$schluessel') process.stdout.write(z ? z.value : '') "@ } $nc = @{ basis = hol 'sicherung.basis' benutzer = hol 'sicherung.benutzer' passwort = hol 'sicherung.passwort' } if (-not ($nc.basis -and $nc.benutzer -and $nc.passwort)) { Write-Host 'Die Nextcloud-Werte fehlen. Dashboard -> Einstellungen -> Sicherung.' -ForegroundColor Red exit 1 } $auth = "$($nc.benutzer):$($nc.passwort)" $wurzel = "$($nc.basis.TrimEnd('/'))/remote.php/dav/files/$($nc.benutzer)" # ------------------------------------------------------------ Quelle lesen Write-Host "Lese $Von ..." -ForegroundColor Cyan $antwort = curl.exe -s -u $auth -X PROPFIND -H 'Depth: infinity' "$wurzel/$Von/" # -join, NICHT direkt auf das Zeilen-Array: curl.exe gibt unter PowerShell ein # Array zurueck, und -match darauf FILTERT, statt zu pruefen. Zweimal darauf # hereingefallen, deshalb steht es hier. $roh = $antwort -join "`n" if ($roh -notmatch '([^<]+)') | ForEach-Object { [uri]::UnescapeDataString($_.Groups[1].Value) } | Where-Object { $_ -notmatch '/$' } | ForEach-Object { $_ -replace [regex]::Escape($praefix), '' } Write-Host " $($dateien.Count) Dateien" -ForegroundColor DarkGray # --------------------------------------------- Dieselbe Zaehmung nachrechnen # # Wort fuer Wort aus server/src/storage.ts (safeFilename). Weicht das je # auseinander, zeigt dieses Skript etwas anderes an als der Dienst tut -- und # das waere schlimmer als kein Skript. function Zaehmen([string]$s) { $s = $s.ToLowerInvariant() $s = $s -replace 'ä', 'ae' -replace 'ö', 'oe' -replace 'ü', 'ue' -replace 'ß', 'ss' $s = $s -replace '[^a-z0-9._-]+', '-' $s = $s -replace '-{2,}', '-' $s = $s -replace '^[^a-z0-9]+', '' $s = $s -replace '[-.]+$', '' return $s } function SichererName([string]$name) { $punkt = $name.LastIndexOf('.') $stamm = if ($punkt -gt 0) { $name.Substring(0, $punkt) } else { $name } $endung = if ($punkt -gt 0) { $name.Substring($punkt + 1) } else { '' } $basis = Zaehmen $stamm $ende = Zaehmen $endung if (-not $basis) { $basis = 'datei' } if ($ende) { return "$basis.$ende" } else { return $basis } } # ------------------------------------------------------------- Gruppieren $gruppen = @{} foreach ($d in $dateien) { # Unterordner bleiben erhalten, jedes Stueck einzeln gezaehmt -- genau wie # beim Import. $ziel = ($d -split '/' | ForEach-Object { SichererName $_ }) -join '/' if (-not $gruppen.ContainsKey($ziel)) { $gruppen[$ziel] = @() } $gruppen[$ziel] += $d } $kollisionen = $gruppen.GetEnumerator() | Where-Object { $_.Value.Count -gt 1 } | Sort-Object Name $verloren = ($kollisionen | ForEach-Object { $_.Value.Count - 1 } | Measure-Object -Sum).Sum if (-not $verloren) { $verloren = 0 } Write-Host '' Write-Host "Quelldateien : $($dateien.Count)" Write-Host "Verschiedene Ziele: $($gruppen.Count)" Write-Host "Kollisionsgruppen : $($kollisionen.Count)" Write-Host "Ohne eigenen Platz: $verloren" -ForegroundColor $(if ($verloren) { 'Yellow' } else { 'Green' }) Write-Host '' if ($kollisionen.Count -eq 0) { Write-Host 'Nichts faellt zusammen.' -ForegroundColor Green exit 0 } # ------------------------------------------------------------------- Zeigen # # GLEICHER INHALT ODER NICHT -- das ist die Frage, an der die Entscheidung # haengt. Deshalb steht die Groesse dabei: gleiche Groesse heisst mit hoher # Wahrscheinlichkeit dieselbe Datei unter anderer Schreibweise, und dann fehlt # nichts. $groessen = @{} foreach ($m in [regex]::Matches($roh, '(?s).*?')) { $t = $m.Value if ($t -match '([^<]+)' ) { $href = [uri]::UnescapeDataString($matches[1]) if ($t -match '(\d+)') { $groessen[($href -replace [regex]::Escape($praefix), '')] = [int]$matches[1] } } } $zeilen = foreach ($k in $kollisionen) { $gleich = ($k.Value | ForEach-Object { $groessen[$_] } | Select-Object -Unique).Count -eq 1 foreach ($q in $k.Value) { [pscustomobject]@{ Ziel = $k.Name Quelle = $q Bytes = $groessen[$q] GleicheGroesse = $gleich } } } $zeilen | Format-Table -AutoSize | Out-String -Width 200 | Write-Host $gleicheGruppen = ($kollisionen | Where-Object { ($_.Value | ForEach-Object { $groessen[$_] } | Select-Object -Unique).Count -eq 1 }).Count Write-Host '' Write-Host "Davon Gruppen mit DURCHWEG GLEICHER Groesse: $gleicheGruppen von $($kollisionen.Count)" Write-Host 'Gleiche Groesse heisst fast immer: dieselbe Datei, andere Schreibweise. Dann fehlt nichts.' if ($Csv) { $zeilen | Export-Csv -Path $Csv -NoTypeInformation -Encoding utf8 -Delimiter ';' Write-Host '' Write-Host "Geschrieben nach $Csv" -ForegroundColor Green }