diff --git a/Backup.ps1 b/Backup.ps1 index 46b09b6..09a743a 100644 --- a/Backup.ps1 +++ b/Backup.ps1 @@ -182,37 +182,6 @@ if ($freeSpaceGB -ge 0) { } -function New-ItemRecord { - param([string]$BaseName, [string]$Source, [string]$ResolvedSource, [string]$Phase) - return [ordered]@{ - baseName = $BaseName - source = $Source - resolvedSource = $ResolvedSource - roots = @() - layouts = @() - catalog = $null - archive = $null - action = $null - reason = $null - phase = $Phase - attemptedAt = (Get-Date).ToString('o') - finishedAt = $null - durationSec = $null - exitCode = $null - verified = $false - warnings = $false - attemptWarnings = $false - encrypted = $false - security = $null - sourceFiles = $null - sourceBytes = $null - archiveBytes = $null - sha256 = $null - lastSuccessAt = $null - successCount = 0 - failCount = 0 - } -} function Save-ItemRecord { param($Record, [string]$Action, [string]$Reason, [bool]$ArchiveWarnings = $false) @@ -523,7 +492,7 @@ foreach ($line in $lines) { $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $catalogPath if (-not $resolved.BaseName) { - $record = New-ItemRecord -BaseName ('raw:' + $displayPath) -Source $displayPath -ResolvedSource $displayPath -Phase 'parse' + $record = New-BakNRetItemRecord -BaseName ('raw:' + $displayPath) -Source $displayPath -ResolvedSource $displayPath -Phase 'parse' Save-ItemRecord -Record $record -Action 'failed' -Reason '无法生成归档名' | Out-Null $failed++; $failures += $displayPath continue @@ -543,7 +512,7 @@ foreach ($line in $lines) { if ($seenBaseNames.ContainsKey($baseName)) { $reason = "归档名 '$baseName' 与清单中的 '$($seenBaseNames[$baseName])' 重复(由 '$displayPath' 生成),两者会互相覆盖" Write-BakNRetLog "失败: $displayPath,$reason" -Level ERROR - $record = New-ItemRecord -BaseName $baseName -Source $displayPath -ResolvedSource $sourcePath -Phase 'backup' + $record = New-BakNRetItemRecord -BaseName $baseName -Source $displayPath -ResolvedSource $sourcePath -Phase 'backup' Save-ItemRecord -Record $record -Action 'failed' -Reason $reason | Out-Null $failed++; $failures += $displayPath continue @@ -555,7 +524,7 @@ foreach ($line in $lines) { continue } - $record = New-ItemRecord -BaseName $baseName -Source $displayPath -ResolvedSource $sourcePath -Phase 'backup' + $record = New-BakNRetItemRecord -BaseName $baseName -Source $displayPath -ResolvedSource $sourcePath -Phase 'backup' $record.archive = $baseName + $tool.Extension if ($resolved.CatalogEntry) { $record.catalog = $resolved.CatalogEntry.Path } $finalPath = Join-Path $BackupDir $record.archive diff --git a/BakNRet/BakNRet.psd1 b/BakNRet/BakNRet.psd1 index d9e9ea1..eb96a5d 100644 --- a/BakNRet/BakNRet.psd1 +++ b/BakNRet/BakNRet.psd1 @@ -19,6 +19,7 @@ CompatiblePSEditions = @('Desktop', 'Core') FunctionsToExport = @( + 'New-BakNRetItemRecord', 'Find-BakNRet7zExecutable', 'Resolve-BakNRetRootedPath', 'Test-BakNRetItemSelected', diff --git a/BakNRet/BakNRet.psm1 b/BakNRet/BakNRet.psm1 index 03668c3..82415b4 100644 --- a/BakNRet/BakNRet.psm1 +++ b/BakNRet/BakNRet.psm1 @@ -138,7 +138,10 @@ . (Join-Path $PSScriptRoot 'Public\Find-BakNRet7zExecutable.ps1') +. (Join-Path $PSScriptRoot 'Public\New-BakNRetItemRecord.ps1') + Export-ModuleMember -Function @( + 'New-BakNRetItemRecord', 'Find-BakNRet7zExecutable', 'Resolve-BakNRetRootedPath', 'Test-BakNRetItemSelected', diff --git a/BakNRet/Public/New-BakNRetItemRecord.ps1 b/BakNRet/Public/New-BakNRetItemRecord.ps1 new file mode 100644 index 0000000..40bde25 --- /dev/null +++ b/BakNRet/Public/New-BakNRetItemRecord.ps1 @@ -0,0 +1,45 @@ +function New-BakNRetItemRecord { + <# + .SYNOPSIS + 造一条 manifest 条目的初始记录(未完成状态)。 + + .DESCRIPTION + 这个有序哈希就是 manifest 里每个条目的 schema:谁(baseName / source / resolvedSource)、 + 这次做了什么(phase / action / reason / exitCode)、结果如何(verified / warnings / sha256 / + archiveBytes)、以及累计计数(successCount / failCount / lastSuccessAt)。 + + 它是纯工厂:不读任何外部状态,所有输入都是参数,时间戳自己取。原先它躺在 Backup.ps1 里, + 搬进模块之后这个"manifest 条目长什么样"的事实就有了唯一的落点 —— 恢复端与工具脚本读的 + 也是同一份 schema,改字段时不会再漏掉某一侧。 + #> + param([string]$BaseName, [string]$Source, [string]$ResolvedSource, [string]$Phase) + + return [ordered]@{ + baseName = $BaseName + source = $Source + resolvedSource = $ResolvedSource + roots = @() + layouts = @() + catalog = $null + archive = $null + action = $null + reason = $null + phase = $Phase + attemptedAt = (Get-Date).ToString('o') + finishedAt = $null + durationSec = $null + exitCode = $null + verified = $false + warnings = $false + attemptWarnings = $false + encrypted = $false + security = $null + sourceFiles = $null + sourceBytes = $null + archiveBytes = $null + sha256 = $null + lastSuccessAt = $null + successCount = 0 + failCount = 0 + } +} diff --git a/tests/Run-Tests.ps1 b/tests/Run-Tests.ps1 index 932c750..c4bd481 100644 --- a/tests/Run-Tests.ps1 +++ b/tests/Run-Tests.ps1 @@ -1364,6 +1364,26 @@ Test-Case '模块可以合回单文件:加载器的顺序声明完整、导出 Assert-Equal 0 $noFile.Count ("导出名单里有名字没有对应文件:" + ($noFile -join '、')) } +Test-Case '条目记录的字段名与顺序是 manifest 的 schema(顺序变了 diff 就没法读)' { + # manifest.json 是给人看的:字段按固定顺序排列,两次运行之间只有值会变, + # 于是 git diff / 文本对比才读得下去。顺序被无意改掉时,这个断言会拦下来。 + $expected = @( + 'baseName', 'source', 'resolvedSource', 'roots', 'layouts', 'catalog', 'archive', + 'action', 'reason', 'phase', 'attemptedAt', 'finishedAt', 'durationSec', 'exitCode', + 'verified', 'warnings', 'attemptWarnings', 'encrypted', 'security', 'sourceFiles', + 'sourceBytes', 'archiveBytes', 'sha256', 'lastSuccessAt', 'successCount', 'failCount' + ) + $record = New-BakNRetItemRecord -BaseName 'x' -Source 's' -ResolvedSource 'rs' -Phase 'backup' + Assert-Equal $expected.Count @($record.Keys).Count '字段数不对' + for ($i = 0; $i -lt $expected.Count; $i++) { + Assert-Equal $expected[$i] @($record.Keys)[$i] "第 $($i + 1) 个字段应当是 $($expected[$i])" + } + Assert-True (@($record.attemptedAt) -match '^\d{4}-\d{2}-\d{2}T') 'attemptedAt 应当是 ISO 时间' + # 时间戳每次现取,不能被写成模块加载时的常量 + $again = New-BakNRetItemRecord -BaseName 'y' -Source 's' -ResolvedSource 'rs' -Phase 'backup' + Assert-True ($record.attemptedAt -ne $again.attemptedAt) '两条记录的 attemptedAt 不该相同' +} + # ============================================================================ Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $itemSandbox -Recurse -Force -ErrorAction SilentlyContinue