Compare commits
8 Commits
db2ee821fb
...
5b409d771b
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b409d771b | |||
|
|
169b4f656d | ||
|
|
a506c09a49 | ||
|
|
83b5e62266 | ||
|
|
124ad9e3b6 | ||
|
|
dfc113ea38 | ||
|
|
5c5e22cfb7 | ||
|
|
cef84c68b9 |
19204
.specstory/history/2025-11-10_16-59Z-change-compression-method-to-7z.md
Normal file
19204
.specstory/history/2025-11-10_16-59Z-change-compression-method-to-7z.md
Normal file
File diff suppressed because one or more lines are too long
227
ConfigLoader.ps1
227
ConfigLoader.ps1
@@ -85,14 +85,84 @@ function Get-ProjectsRoot {
|
||||
return $candidate
|
||||
}
|
||||
|
||||
function Get-ProjectPathFromUser {
|
||||
param(
|
||||
[string]$Prompt = "Paste the project path to deploy in"
|
||||
)
|
||||
|
||||
do {
|
||||
$inputPath = Read-Host -Prompt $Prompt
|
||||
if ([string]::IsNullOrWhiteSpace($inputPath)) {
|
||||
Write-Warning "Path cannot be empty. Please try again."
|
||||
continue
|
||||
}
|
||||
|
||||
# Remove quotes if present
|
||||
$inputPath = $inputPath.Trim('"', "'")
|
||||
|
||||
# Try to resolve the path
|
||||
try {
|
||||
if (Test-Path -LiteralPath $inputPath -PathType Container) {
|
||||
$resolved = Resolve-Path -LiteralPath $inputPath -ErrorAction Stop
|
||||
return $resolved.Path
|
||||
}
|
||||
else {
|
||||
Write-Warning "Path does not exist or is not a directory: $inputPath"
|
||||
$retry = Read-Host "Try again? (Y/N)"
|
||||
if ($retry -notmatch '^[Yy]') {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Invalid path: $($_.Exception.Message)"
|
||||
$retry = Read-Host "Try again? (Y/N)"
|
||||
if ($retry -notmatch '^[Yy]') {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
} while ($true)
|
||||
}
|
||||
|
||||
function Use-IsoDailyFormat {
|
||||
$dailyFormat = Get-ConfigValue -Name 'dailyFormat' -Default $true
|
||||
return [bool]$dailyFormat
|
||||
|
||||
# Handle backward compatibility with boolean values
|
||||
if ($dailyFormat -is [bool]) {
|
||||
return $dailyFormat
|
||||
}
|
||||
|
||||
# Handle string values
|
||||
if ($dailyFormat -is [string]) {
|
||||
$formatStr = $dailyFormat.Trim()
|
||||
# ISO format contains YYYY-MM-DD pattern
|
||||
if ($formatStr -match 'YYYY-MM-DD' -or $formatStr -match '\d{4}-\d{2}-\d{2}') {
|
||||
return $true
|
||||
}
|
||||
# daily_YYMMDD or other formats are not ISO
|
||||
return $false
|
||||
}
|
||||
|
||||
# Default to false (not ISO format) for unknown types
|
||||
return $false
|
||||
}
|
||||
|
||||
function Use-7Zip {
|
||||
$zipper = Get-ConfigValue -Name 'zipper' -Default $true
|
||||
return [bool]$zipper
|
||||
|
||||
# Handle backward compatibility with boolean values
|
||||
if ($zipper -is [bool]) {
|
||||
return $zipper
|
||||
}
|
||||
|
||||
# Handle string values
|
||||
if ($zipper -is [string]) {
|
||||
$zipperStr = $zipper.Trim().ToLower()
|
||||
return ($zipperStr -eq '7z')
|
||||
}
|
||||
|
||||
# Default to false (use zip) for unknown types
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-ZipCompressionLevel {
|
||||
@@ -111,3 +181,156 @@ function Get-ZipCompressionLevel {
|
||||
return [Math]::Min(9, [Math]::Max(0, $value))
|
||||
}
|
||||
|
||||
# If script is run directly (not dot-sourced), prompt for project path and deploy
|
||||
# When dot-sourced, InvocationName is '.'; when run directly, it's the script path or name
|
||||
if ($MyInvocation.InvocationName -ne '.') {
|
||||
$projectPath = Get-ProjectPathFromUser
|
||||
if ($null -ne $projectPath) {
|
||||
# Deploy batch files and config to the project
|
||||
$structDir = Get-StructDirectory
|
||||
if (-not (Test-Path -LiteralPath $structDir -PathType Container)) {
|
||||
Write-Error "Configured structDir not found: $structDir"
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$resolvedProject = (Resolve-Path -LiteralPath $projectPath -ErrorAction Stop).Path
|
||||
}
|
||||
catch {
|
||||
Write-Error "Unable to resolve project directory: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $resolvedProject -PathType Container)) {
|
||||
Write-Error "Project path is not a directory: $resolvedProject"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "`nDeploying to: $resolvedProject" -ForegroundColor Cyan
|
||||
Write-Host "Struct directory: $structDir" -ForegroundColor Cyan
|
||||
|
||||
$specs = @(
|
||||
@{ Name = 'UpdateSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateSequences.bat' },
|
||||
@{ Name = 'UpdateAllSequences.bat'; Source = Join-Path -Path $structDir -ChildPath 'UpdateAllSequences.bat' },
|
||||
@{ Name = 'ZipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'ZipSeqArchv.bat' },
|
||||
@{ Name = 'UnzipSeqArchv.bat'; Source = Join-Path -Path $structDir -ChildPath 'UnzipSeqArchv.bat' },
|
||||
@{ Name = 'NewDaily.bat'; Source = Join-Path -Path $structDir -ChildPath 'NewDaily.bat' }
|
||||
)
|
||||
|
||||
# Config files to deploy to projectroot\.config\
|
||||
$configAssets = @(
|
||||
@{ Name = 'config.json'; Source = Join-Path -Path $structDir -ChildPath 'config.json' },
|
||||
@{ Name = 'GetStructDir.ps1'; Source = Join-Path -Path $structDir -ChildPath 'GetStructDir.ps1' }
|
||||
)
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
if (-not (Test-Path -LiteralPath $spec.Source -PathType Leaf)) {
|
||||
Write-Error "Source file not found: $($spec.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($asset in $configAssets) {
|
||||
if (-not (Test-Path -LiteralPath $asset.Source -PathType Leaf)) {
|
||||
Write-Error "Config asset not found: $($asset.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure .config directory exists in project root and is hidden
|
||||
$projectConfigDir = Join-Path -Path $resolvedProject -ChildPath '.config'
|
||||
if (-not (Test-Path -LiteralPath $projectConfigDir -PathType Container)) {
|
||||
New-Item -Path $projectConfigDir -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created .config directory: $projectConfigDir" -ForegroundColor Cyan
|
||||
}
|
||||
# Set hidden attribute on .config directory
|
||||
$folder = Get-Item -LiteralPath $projectConfigDir -Force
|
||||
$folder.Attributes = $folder.Attributes -bor [System.IO.FileAttributes]::Hidden
|
||||
|
||||
$touchedDirs = @{}
|
||||
$summary = @()
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
Write-Host "`n=== Updating $($spec.Name) ===" -ForegroundColor Magenta
|
||||
|
||||
$targets = Get-ChildItem -LiteralPath $resolvedProject -Recurse -Filter $spec.Name -File -ErrorAction SilentlyContinue
|
||||
$targets = $targets | Where-Object { $_.FullName -ne $spec.Source }
|
||||
|
||||
if (-not $targets) {
|
||||
Write-Host "No targets found." -ForegroundColor Yellow
|
||||
$summary += [pscustomobject]@{
|
||||
Name = $spec.Name
|
||||
Updated = 0
|
||||
Failed = 0
|
||||
Skipped = 0
|
||||
Total = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
$updated = 0
|
||||
$failed = 0
|
||||
|
||||
foreach ($target in $targets) {
|
||||
try {
|
||||
Copy-Item -Path $spec.Source -Destination $target.FullName -Force
|
||||
Write-Host "[OK] $($target.FullName)" -ForegroundColor Green
|
||||
$updated++
|
||||
|
||||
$targetDir = $target.Directory.FullName
|
||||
if (-not $touchedDirs.ContainsKey($targetDir)) {
|
||||
$touchedDirs[$targetDir] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $($target.FullName)" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
$failed++
|
||||
}
|
||||
}
|
||||
|
||||
$summary += [pscustomobject]@{
|
||||
Name = $spec.Name
|
||||
Updated = $updated
|
||||
Failed = $failed
|
||||
Skipped = 0
|
||||
Total = @($targets).Count
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`n=== Summary ===" -ForegroundColor Cyan
|
||||
foreach ($item in $summary) {
|
||||
Write-Host ("{0,-22} Updated: {1,3} Failed: {2,3} Total: {3,3}" -f $item.Name, $item.Updated, $item.Failed, $item.Total)
|
||||
}
|
||||
|
||||
if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {
|
||||
Write-Host "Completed with errors." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "All batch files refreshed successfully." -ForegroundColor Green
|
||||
|
||||
# Deploy config files to projectroot\.config\
|
||||
Write-Host "`n=== Deploying config files to .config\ ===" -ForegroundColor Magenta
|
||||
foreach ($asset in $configAssets) {
|
||||
$targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name
|
||||
try {
|
||||
Copy-Item -Path $asset.Source -Destination $targetPath -Force
|
||||
Write-Host "[OK] $targetPath" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $targetPath" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "`nConfig files deployed successfully." -ForegroundColor Green
|
||||
Write-Host "Deployment completed successfully." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
else {
|
||||
Write-Host "No project path provided." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
68
GetStructDir.ps1
Normal file
68
GetStructDir.ps1
Normal file
@@ -0,0 +1,68 @@
|
||||
# Simple helper script to get structDir from project config.json
|
||||
# Reads config.json from .config folder in project root
|
||||
|
||||
param(
|
||||
[string]$ProjectRoot
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ProjectRoot)) {
|
||||
# Try to determine project root from script location
|
||||
if ($PSScriptRoot) {
|
||||
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||
}
|
||||
elseif ($MyInvocation.MyCommand.Path) {
|
||||
$ProjectRoot = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
|
||||
}
|
||||
else {
|
||||
Write-Error "Unable to determine project root. Please provide -ProjectRoot parameter."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$configPath = Join-Path -Path $ProjectRoot -ChildPath '.config\config.json'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $configPath -PathType Leaf)) {
|
||||
Write-Error "config.json not found at: $configPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$config = Get-Content -LiteralPath $configPath -Raw -ErrorAction Stop | ConvertFrom-Json
|
||||
|
||||
if ($config.PSObject.Properties.Name -contains 'structDir') {
|
||||
$structDir = $config.structDir
|
||||
if ($null -ne $structDir -and ($structDir -isnot [string] -or $structDir.Trim().Length -gt 0)) {
|
||||
# If it's an absolute path, resolve it
|
||||
if ([System.IO.Path]::IsPathRooted($structDir)) {
|
||||
$resolved = Resolve-Path -LiteralPath $structDir -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolved) {
|
||||
Write-Output $resolved.Path
|
||||
exit 0
|
||||
}
|
||||
Write-Output $structDir
|
||||
exit 0
|
||||
}
|
||||
# Relative path - resolve relative to config location
|
||||
$candidate = Join-Path -Path (Split-Path -Parent $configPath) -ChildPath $structDir
|
||||
$resolvedCandidate = Resolve-Path -LiteralPath $candidate -ErrorAction SilentlyContinue
|
||||
if ($null -ne $resolvedCandidate) {
|
||||
Write-Output $resolvedCandidate.Path
|
||||
exit 0
|
||||
}
|
||||
Write-Output $candidate
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# Default: return the directory containing config.json (project root)
|
||||
Write-Output $ProjectRoot
|
||||
exit 0
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to read or parse config.json: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
|
||||
set "REN_DIR=%~dp0"
|
||||
for %%I in ("%REN_DIR%..") do set "PROJ_ROOT=%%~fI"
|
||||
|
||||
set "CONFIG_LOADER=%REN_DIR%ConfigLoader.ps1"
|
||||
set "CONFIG_PATH=%REN_DIR%config.json"
|
||||
set "CONFIG_DIR=%PROJ_ROOT%\.config"
|
||||
set "CONFIG_PATH=%CONFIG_DIR%\config.json"
|
||||
set "GET_STRUCT_DIR=%CONFIG_DIR%\GetStructDir.ps1"
|
||||
|
||||
if not exist "%CONFIG_LOADER%" (
|
||||
echo [ERROR] ConfigLoader.ps1 not found next to UnzipSeqArchv.bat.
|
||||
echo Please run UpdateProjectBatches.ps1 to refresh helper files.
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found at %CONFIG_PATH%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found next to UnzipSeqArchv.bat.
|
||||
echo Please run UpdateProjectBatches.ps1 to refresh helper files.
|
||||
if not exist "%GET_STRUCT_DIR%" (
|
||||
echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; $pyPath = Join-Path (Get-StructDirectory) 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \"zip_sequences.py not found at $pyPath\" }; Write-Output $pyPath"`) do set "PY_SCRIPT=%%I"
|
||||
"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \"Failed to get structDir from GetStructDir.ps1\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \"zip_sequences.py not found at $pyPath\" }; Write-Output $pyPath"`) do set "PY_SCRIPT=%%I"
|
||||
|
||||
if not defined PY_SCRIPT (
|
||||
echo [ERROR] Unable to resolve zip_sequences.py path from config.
|
||||
@@ -30,12 +31,12 @@ if not defined PY_SCRIPT (
|
||||
pushd "%PROJ_ROOT%" >nul 2>&1
|
||||
|
||||
python "%PY_SCRIPT%" --mode expand --verbose %*
|
||||
set "ERR=%ERRORLEVEL%"
|
||||
set "ERR=!ERRORLEVEL!"
|
||||
|
||||
if not "%ERR%"=="0" (
|
||||
echo Failed to expand render sequence archives (exit code %ERR%).
|
||||
if not "!ERR!"=="0" (
|
||||
echo Failed to expand render sequence archives (exit code !ERR!).
|
||||
)
|
||||
|
||||
popd >nul 2>&1
|
||||
exit /b %ERR%
|
||||
exit /b !ERR!
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Refresh helper batch scripts within a specific project directory.
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
param(
|
||||
[string]$ProjectPath
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
if (-not $PSScriptRoot) {
|
||||
$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
@@ -55,9 +55,10 @@ $specs = @(
|
||||
@{ Name = 'NewDaily.bat'; Source = Join-Path -Path $structDir -ChildPath 'NewDaily.bat' }
|
||||
)
|
||||
|
||||
$sharedAssets = @(
|
||||
@{ Name = 'ConfigLoader.ps1'; Source = Join-Path -Path $structDir -ChildPath 'ConfigLoader.ps1' },
|
||||
@{ Name = 'config.json'; Source = Join-Path -Path $structDir -ChildPath 'config.json' }
|
||||
# Config files to deploy to projectroot\.config\
|
||||
$configAssets = @(
|
||||
@{ Name = 'config.json'; Source = Join-Path -Path $structDir -ChildPath 'config.json' },
|
||||
@{ Name = 'GetStructDir.ps1'; Source = Join-Path -Path $structDir -ChildPath 'GetStructDir.ps1' }
|
||||
)
|
||||
|
||||
foreach ($spec in $specs) {
|
||||
@@ -67,13 +68,20 @@ foreach ($spec in $specs) {
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($asset in $sharedAssets) {
|
||||
foreach ($asset in $configAssets) {
|
||||
if (-not (Test-Path -LiteralPath $asset.Source -PathType Leaf)) {
|
||||
Write-Error "Shared asset not found: $($asset.Source)"
|
||||
Write-Error "Config asset not found: $($asset.Source)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure .config directory exists in project root
|
||||
$projectConfigDir = Join-Path -Path $resolvedProject -ChildPath '.config'
|
||||
if (-not (Test-Path -LiteralPath $projectConfigDir -PathType Container)) {
|
||||
New-Item -Path $projectConfigDir -ItemType Directory -Force | Out-Null
|
||||
Write-Host "Created .config directory: $projectConfigDir" -ForegroundColor Cyan
|
||||
}
|
||||
|
||||
$touchedDirs = @{}
|
||||
$summary = @()
|
||||
|
||||
@@ -101,24 +109,16 @@ foreach ($spec in $specs) {
|
||||
foreach ($target in $targets) {
|
||||
try {
|
||||
Copy-Item -Path $spec.Source -Destination $target.FullName -Force
|
||||
Write-Host "✓ $($target.FullName)" -ForegroundColor Green
|
||||
Write-Host "[OK] $($target.FullName)" -ForegroundColor Green
|
||||
$updated++
|
||||
|
||||
$targetDir = $target.Directory.FullName
|
||||
if (-not $touchedDirs.ContainsKey($targetDir)) {
|
||||
foreach ($asset in $sharedAssets) {
|
||||
try {
|
||||
Copy-Item -Path $asset.Source -Destination (Join-Path -Path $targetDir -ChildPath $asset.Name) -Force
|
||||
}
|
||||
catch {
|
||||
Write-Host " ✗ Failed to copy $($asset.Name) into $targetDir: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
$touchedDirs[$targetDir] = $true
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "✗ $($target.FullName)" -ForegroundColor Red
|
||||
Write-Host "[FAIL] $($target.FullName)" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
$failed++
|
||||
}
|
||||
@@ -129,7 +129,7 @@ foreach ($spec in $specs) {
|
||||
Updated = $updated
|
||||
Failed = $failed
|
||||
Skipped = 0
|
||||
Total = $targets.Count
|
||||
Total = @($targets).Count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,3 +145,20 @@ if (($summary | Measure-Object -Property Failed -Sum).Sum -gt 0) {
|
||||
|
||||
Write-Host "All batch files refreshed successfully." -ForegroundColor Green
|
||||
|
||||
# Deploy config files to projectroot\.config\
|
||||
Write-Host "`n=== Deploying config files to .config\ ===" -ForegroundColor Magenta
|
||||
foreach ($asset in $configAssets) {
|
||||
$targetPath = Join-Path -Path $projectConfigDir -ChildPath $asset.Name
|
||||
try {
|
||||
Copy-Item -Path $asset.Source -Destination $targetPath -Force
|
||||
Write-Host "[OK] $targetPath" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
Write-Host "[FAIL] $targetPath" -ForegroundColor Red
|
||||
Write-Host " $($_.Exception.Message)" -ForegroundColor DarkRed
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Config files deployed successfully." -ForegroundColor Green
|
||||
|
||||
|
||||
@@ -2,23 +2,26 @@
|
||||
setlocal EnableExtensions
|
||||
|
||||
set "script_dir=%~dp0"
|
||||
set "config_loader=%script_dir%ConfigLoader.ps1"
|
||||
set "config_path=%script_dir%config.json"
|
||||
for %%I in ("%script_dir%..\..") do set "PROJ_ROOT=%%~fI"
|
||||
|
||||
if not exist "%config_loader%" (
|
||||
echo [ERROR] ConfigLoader.ps1 not found next to UpdateSequences.bat.
|
||||
echo Please run UpdateProjectBatches.ps1 to refresh helper files.
|
||||
set "CONFIG_DIR=%PROJ_ROOT%\.config"
|
||||
set "CONFIG_PATH=%CONFIG_DIR%\config.json"
|
||||
set "GET_STRUCT_DIR=%CONFIG_DIR%\GetStructDir.ps1"
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found at %CONFIG_PATH%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%config_path%" (
|
||||
echo [ERROR] config.json not found next to UpdateSequences.bat.
|
||||
echo Please run UpdateProjectBatches.ps1 to refresh helper files.
|
||||
if not exist "%GET_STRUCT_DIR%" (
|
||||
echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%config_loader%' -ErrorAction Stop; . $loader.Path; $ps1Path = Join-Path (Get-StructDirectory) 'UpdateSequences.ps1'; if (-not (Test-Path -LiteralPath $ps1Path)) { throw \"UpdateSequences.ps1 not found at $ps1Path\" }; Write-Output $ps1Path"`) do set "ps1=%%I"
|
||||
"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \"Failed to get structDir from GetStructDir.ps1\" }; $ps1Path = Join-Path $structDir 'UpdateSequences.ps1'; if (-not (Test-Path -LiteralPath $ps1Path)) { throw \"UpdateSequences.ps1 not found at $ps1Path\" }; Write-Output $ps1Path"`) do set "ps1=%%I"
|
||||
|
||||
if not defined ps1 (
|
||||
echo [ERROR] Unable to resolve UpdateSequences.ps1 path from config.
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
@echo off
|
||||
setlocal EnableExtensions
|
||||
setlocal EnableExtensions EnableDelayedExpansion
|
||||
|
||||
set "REN_DIR=%~dp0"
|
||||
for %%I in ("%REN_DIR%..") do set "PROJ_ROOT=%%~fI"
|
||||
|
||||
set "CONFIG_LOADER=%REN_DIR%ConfigLoader.ps1"
|
||||
set "CONFIG_PATH=%REN_DIR%config.json"
|
||||
set "CONFIG_DIR=%PROJ_ROOT%\.config"
|
||||
set "CONFIG_PATH=%CONFIG_DIR%\config.json"
|
||||
set "GET_STRUCT_DIR=%CONFIG_DIR%\GetStructDir.ps1"
|
||||
|
||||
if not exist "%CONFIG_LOADER%" (
|
||||
echo [ERROR] ConfigLoader.ps1 not found next to ZipSeqArchv.bat.
|
||||
echo Please run UpdateProjectBatches.ps1 to refresh helper files.
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found at %CONFIG_PATH%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%CONFIG_PATH%" (
|
||||
echo [ERROR] config.json not found next to ZipSeqArchv.bat.
|
||||
echo Please run UpdateProjectBatches.ps1 to refresh helper files.
|
||||
if not exist "%GET_STRUCT_DIR%" (
|
||||
echo [ERROR] GetStructDir.ps1 not found at %GET_STRUCT_DIR%
|
||||
echo Please run ConfigLoader.ps1 to deploy helper files.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command ^
|
||||
"Set-StrictMode -Version Latest; $loader = Resolve-Path -LiteralPath '%CONFIG_LOADER%' -ErrorAction Stop; . $loader.Path; $pyPath = Join-Path (Get-StructDirectory) 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \"zip_sequences.py not found at $pyPath\" }; Write-Output $pyPath"`) do set "PY_SCRIPT=%%I"
|
||||
"Set-StrictMode -Version Latest; $structDir = & '%GET_STRUCT_DIR%' -ProjectRoot '%PROJ_ROOT%'; if (-not $structDir) { throw \"Failed to get structDir from GetStructDir.ps1\" }; $pyPath = Join-Path $structDir 'zip_sequences.py'; if (-not (Test-Path -LiteralPath $pyPath)) { throw \"zip_sequences.py not found at $pyPath\" }; Write-Output $pyPath"`) do set "PY_SCRIPT=%%I"
|
||||
|
||||
if not defined PY_SCRIPT (
|
||||
echo [ERROR] Unable to resolve zip_sequences.py path from config.
|
||||
@@ -30,12 +31,12 @@ if not defined PY_SCRIPT (
|
||||
pushd "%PROJ_ROOT%" >nul 2>&1
|
||||
|
||||
python "%PY_SCRIPT%" --verbose %*
|
||||
set "ERR=%ERRORLEVEL%"
|
||||
set "ERR=!ERRORLEVEL!"
|
||||
|
||||
if not "%ERR%"=="0" (
|
||||
echo Failed to update render sequence archives (exit code %ERR%).
|
||||
if not "!ERR!"=="0" (
|
||||
echo Failed to update render sequence archives (exit code !ERR!).
|
||||
)
|
||||
|
||||
popd >nul 2>&1
|
||||
exit /b %ERR%
|
||||
exit /b !ERR!
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"dailyFormat": true,
|
||||
"structDir": "D:\\ProjectStructure",
|
||||
"zipper": true,
|
||||
"dailyFormat": "daily_YYMMDD",
|
||||
"structDir": "A:\\1 Amazon_Active_Projects\\3 ProjectStructure",
|
||||
"zipper": "7z",
|
||||
"compression": 9
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
new script named UpgradeToGitProj. this is to be ran in a project structure that was pre-git.
|
||||
|
||||
1. appends gitignore and gitattributes, initializes git, and installs git lfs
|
||||
- If already initialized, will this just error but continue?
|
||||
2. Manages Renders folder:
|
||||
a. creates folder, adding NewDaily and UpdateSeq scripts
|
||||
b. scans the structure within blends\animations and creates a folder for each submodule, e.g. Horizontal, Shorts, Vertical, etc. If there are no submodules, it just grabs the daily_* folders.
|
||||
c. For each daily_* folder, it copies the contents of each daily_*\seq\ folder into the Renders\submodule\ folder. If it's taking daily_* folders from the root, it just copies the contents of the daily_*\seq\ folder into the Renders\ folder.
|
||||
312
zip_sequences.py
312
zip_sequences.py
@@ -11,10 +11,12 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Sequence
|
||||
@@ -31,36 +33,52 @@ SEQUENCE_EXTENSIONS = {
|
||||
".exr",
|
||||
}
|
||||
STATE_SUFFIX = ".meta.json"
|
||||
CONFIG_PATH = Path(__file__).resolve().with_name("config.json")
|
||||
DEFAULT_CONFIG = {
|
||||
"zipper": True,
|
||||
"zipper": "7z",
|
||||
"compression": 9,
|
||||
"dailyFormat": "daily_YYMMDD",
|
||||
}
|
||||
|
||||
|
||||
def load_config() -> dict:
|
||||
try:
|
||||
text = CONFIG_PATH.read_text(encoding="utf-8")
|
||||
except FileNotFoundError:
|
||||
return DEFAULT_CONFIG.copy()
|
||||
except OSError:
|
||||
return DEFAULT_CONFIG.copy()
|
||||
|
||||
try:
|
||||
data = json.loads(text)
|
||||
except json.JSONDecodeError:
|
||||
return DEFAULT_CONFIG.copy()
|
||||
|
||||
if not isinstance(data, dict):
|
||||
return DEFAULT_CONFIG.copy()
|
||||
|
||||
merged = DEFAULT_CONFIG.copy()
|
||||
merged.update(data)
|
||||
return merged
|
||||
# First try to load from project's .config folder (current working directory)
|
||||
# Then fall back to ProjectStructure repo config (next to zip_sequences.py)
|
||||
cwd = Path.cwd()
|
||||
project_config = cwd / ".config" / "config.json"
|
||||
repo_config = Path(__file__).resolve().with_name("config.json")
|
||||
|
||||
config_paths = [
|
||||
("project", project_config),
|
||||
("repo", repo_config),
|
||||
]
|
||||
|
||||
for source, config_path in config_paths:
|
||||
try:
|
||||
if config_path.exists():
|
||||
text = config_path.read_text(encoding="utf-8")
|
||||
try:
|
||||
data = json.loads(text)
|
||||
if isinstance(data, dict):
|
||||
merged = DEFAULT_CONFIG.copy()
|
||||
merged.update(data)
|
||||
return merged
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
# If no config found, return defaults
|
||||
return DEFAULT_CONFIG.copy()
|
||||
|
||||
|
||||
CONFIG = load_config()
|
||||
USE_7Z = bool(CONFIG.get("zipper", True))
|
||||
zipper_val = CONFIG.get("zipper", "7z")
|
||||
# Handle both old boolean format and new string format
|
||||
if isinstance(zipper_val, bool):
|
||||
ZIPPER_TYPE = "7z" if zipper_val else "zip"
|
||||
else:
|
||||
ZIPPER_TYPE = str(zipper_val).lower()
|
||||
|
||||
COMPRESSION_LEVEL = CONFIG.get("compression", 9)
|
||||
if isinstance(COMPRESSION_LEVEL, str):
|
||||
try:
|
||||
@@ -72,11 +90,8 @@ if not isinstance(COMPRESSION_LEVEL, int):
|
||||
COMPRESSION_LEVEL = max(0, min(9, COMPRESSION_LEVEL))
|
||||
|
||||
SEVEN_Z_EXE: str | None = None
|
||||
if USE_7Z:
|
||||
if ZIPPER_TYPE == "7z":
|
||||
SEVEN_Z_EXE = shutil.which("7z") or shutil.which("7za")
|
||||
if SEVEN_Z_EXE is None:
|
||||
print("[zip] Requested 7z compression but no 7z executable was found; falling back to zipfile.", file=sys.stderr)
|
||||
USE_7Z = False
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
@@ -172,7 +187,8 @@ def state_changed(seq_state: dict, stored_state: dict | None) -> bool:
|
||||
|
||||
def archive_path_for(seq_dir: Path) -> Path:
|
||||
rel = seq_dir.relative_to(RENDER_ROOT)
|
||||
return (ARCHIVE_ROOT / rel).with_suffix(".zip")
|
||||
suffix = ".7z" if ZIPPER_TYPE == "7z" else ".zip"
|
||||
return (ARCHIVE_ROOT / rel).with_suffix(suffix)
|
||||
|
||||
|
||||
def sequence_dir_for(zip_path: Path) -> Path:
|
||||
@@ -185,33 +201,130 @@ def state_path_for(zip_path: Path) -> Path:
|
||||
|
||||
|
||||
def zip_sequence(seq_dir: Path, zip_path: Path) -> None:
|
||||
if USE_7Z and SEVEN_Z_EXE:
|
||||
if ZIPPER_TYPE == "7z":
|
||||
if SEVEN_Z_EXE is None:
|
||||
raise RuntimeError(
|
||||
"7z compression requested but 7z executable not found in PATH. "
|
||||
"Please install 7z (e.g., via Chocolatey: choco install 7zip) "
|
||||
"or set zipper to 'zip' in config.json"
|
||||
)
|
||||
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cmd = [
|
||||
SEVEN_Z_EXE,
|
||||
"a",
|
||||
"-y",
|
||||
f"-mx={COMPRESSION_LEVEL}",
|
||||
"-tzip",
|
||||
str(zip_path),
|
||||
".\\*",
|
||||
]
|
||||
subprocess.run(cmd, cwd=seq_dir, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
# If creating a .7z file, remove any existing .zip file for the same sequence
|
||||
if zip_path.suffix == ".7z":
|
||||
old_zip_path = zip_path.with_suffix(".zip")
|
||||
if old_zip_path.exists():
|
||||
old_zip_path.unlink(missing_ok=True)
|
||||
old_state_path = state_path_for(old_zip_path)
|
||||
if old_state_path.exists():
|
||||
old_state_path.unlink(missing_ok=True)
|
||||
|
||||
# Build list of files to archive with relative paths
|
||||
file_list = []
|
||||
for file_path in iter_sequence_files(seq_dir):
|
||||
rel_path = file_path.relative_to(seq_dir).as_posix()
|
||||
file_list.append(rel_path)
|
||||
|
||||
if not file_list:
|
||||
raise RuntimeError(f"No files found to archive in {seq_dir}")
|
||||
|
||||
# Create zip in temporary location first to avoid issues with corrupted existing files
|
||||
temp_zip = None
|
||||
list_file_path = None
|
||||
try:
|
||||
# Create temporary archive file path (but don't create the file - let 7z create it)
|
||||
temp_zip_path = tempfile.mktemp(suffix=".7z", dir=zip_path.parent)
|
||||
temp_zip = Path(temp_zip_path)
|
||||
|
||||
# Create list file with absolute path
|
||||
fd, temp_path = tempfile.mkstemp(suffix=".lst", text=True)
|
||||
list_file_path = Path(temp_path)
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as list_file:
|
||||
for rel_path in file_list:
|
||||
list_file.write(rel_path + "\n")
|
||||
list_file.flush()
|
||||
os.fsync(list_file.fileno()) # Ensure data is written to disk
|
||||
# File is closed here by context manager, small delay to ensure OS releases handle
|
||||
time.sleep(0.1)
|
||||
|
||||
# Use absolute paths for both list file and temp zip
|
||||
list_file_abs = list_file_path.resolve()
|
||||
temp_zip_abs = temp_zip.resolve()
|
||||
# Create archive in temp location first (7z will create it fresh)
|
||||
cmd = [
|
||||
SEVEN_Z_EXE,
|
||||
"a",
|
||||
"-y",
|
||||
"-bb0", # Suppress progress output
|
||||
f"-mx={COMPRESSION_LEVEL}",
|
||||
"-t7z", # Use 7z format, not zip
|
||||
str(temp_zip_abs),
|
||||
f"@{list_file_abs}",
|
||||
]
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=seq_dir,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
||||
if result.stdout:
|
||||
error_msg += f"\nstdout: {result.stdout.strip()}"
|
||||
raise RuntimeError(f"7z compression failed: {error_msg}")
|
||||
|
||||
# Move temp zip to final location, replacing any existing file
|
||||
if zip_path.exists():
|
||||
zip_path.unlink()
|
||||
temp_zip.replace(zip_path)
|
||||
temp_zip = None # Mark as moved so we don't delete it
|
||||
finally:
|
||||
# Clean up temp zip if it wasn't moved
|
||||
if temp_zip and temp_zip.exists():
|
||||
try:
|
||||
temp_zip.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
pass
|
||||
# Clean up list file, with retry in case 7z still has it open
|
||||
if list_file_path and list_file_path.exists():
|
||||
for attempt in range(3):
|
||||
try:
|
||||
list_file_path.unlink(missing_ok=True)
|
||||
break
|
||||
except PermissionError:
|
||||
if attempt < 2:
|
||||
time.sleep(0.1) # Wait 100ms before retry
|
||||
else:
|
||||
# Last attempt failed, just log and continue
|
||||
# The temp file will be cleaned up by the OS eventually
|
||||
pass
|
||||
return
|
||||
|
||||
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
||||
# Use zipfile (only if ZIPPER_TYPE == "zip")
|
||||
if ZIPPER_TYPE == "zip":
|
||||
from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile
|
||||
|
||||
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if COMPRESSION_LEVEL <= 0:
|
||||
compression = ZIP_STORED
|
||||
zip_kwargs = {}
|
||||
else:
|
||||
compression = ZIP_DEFLATED
|
||||
zip_kwargs = {"compresslevel": COMPRESSION_LEVEL}
|
||||
zip_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if COMPRESSION_LEVEL <= 0:
|
||||
compression = ZIP_STORED
|
||||
zip_kwargs = {}
|
||||
else:
|
||||
compression = ZIP_DEFLATED
|
||||
zip_kwargs = {"compresslevel": COMPRESSION_LEVEL}
|
||||
|
||||
with ZipFile(zip_path, "w", compression=compression, **zip_kwargs) as archive:
|
||||
for file_path in iter_sequence_files(seq_dir):
|
||||
archive.write(file_path, arcname=file_path.relative_to(seq_dir).as_posix())
|
||||
with ZipFile(zip_path, "w", compression=compression, **zip_kwargs) as archive:
|
||||
for file_path in iter_sequence_files(seq_dir):
|
||||
archive.write(file_path, arcname=file_path.relative_to(seq_dir).as_posix())
|
||||
return
|
||||
|
||||
# Unknown ZIPPER_TYPE - fail with clear error
|
||||
raise RuntimeError(
|
||||
f"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. "
|
||||
f"Expected '7z' or 'zip'. "
|
||||
f"Config zipper value: {CONFIG.get('zipper', 'not set')!r}"
|
||||
)
|
||||
|
||||
|
||||
def expand_sequence(zip_path: Path, seq_state: dict) -> None:
|
||||
@@ -220,7 +333,12 @@ def expand_sequence(zip_path: Path, seq_state: dict) -> None:
|
||||
shutil.rmtree(target_dir)
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if USE_7Z and SEVEN_Z_EXE:
|
||||
if ZIPPER_TYPE == "7z":
|
||||
if SEVEN_Z_EXE is None:
|
||||
raise RuntimeError(
|
||||
"7z extraction requested but 7z executable not found in PATH. "
|
||||
"Please install 7z or set zipper to 'zip' in config.json"
|
||||
)
|
||||
cmd = [
|
||||
SEVEN_Z_EXE,
|
||||
"x",
|
||||
@@ -228,12 +346,29 @@ def expand_sequence(zip_path: Path, seq_state: dict) -> None:
|
||||
str(zip_path),
|
||||
f"-o{target_dir}",
|
||||
]
|
||||
subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
else:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
check=False,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = result.stderr.strip() if result.stderr else "Unknown error"
|
||||
if result.stdout:
|
||||
error_msg += f"\nstdout: {result.stdout.strip()}"
|
||||
raise RuntimeError(f"7z extraction failed: {error_msg}")
|
||||
elif ZIPPER_TYPE == "zip":
|
||||
from zipfile import ZipFile
|
||||
|
||||
with ZipFile(zip_path, "r") as archive:
|
||||
archive.extractall(target_dir)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Unsupported ZIPPER_TYPE: {ZIPPER_TYPE!r}. "
|
||||
f"Expected '7z' or 'zip'. "
|
||||
f"Config zipper value: {CONFIG.get('zipper', 'not set')!r}"
|
||||
)
|
||||
|
||||
for entry in seq_state.get("files", []):
|
||||
file_path = target_dir / entry["path"]
|
||||
@@ -262,11 +397,34 @@ def run_zip(worker_count: int, *, verbose: bool) -> int:
|
||||
if not seq_state["files"]:
|
||||
continue
|
||||
|
||||
# Get the target archive path (will be .7z if ZIPPER_TYPE is "7z")
|
||||
zip_path = archive_path_for(seq_dir)
|
||||
state_path = state_path_for(zip_path)
|
||||
|
||||
# Check if we need to upgrade from .zip to .7z
|
||||
old_zip_path = None
|
||||
if ZIPPER_TYPE == "7z":
|
||||
# Check if an old .zip file exists
|
||||
old_zip_path = zip_path.with_suffix(".zip")
|
||||
if old_zip_path.exists():
|
||||
# Check if the old .zip's metadata matches current state
|
||||
old_state_path = state_path_for(old_zip_path)
|
||||
old_stored_state = load_state(old_state_path)
|
||||
if not state_changed(seq_state, old_stored_state):
|
||||
# Old .zip is up to date, skip conversion
|
||||
continue
|
||||
# Old .zip is out of date, will be replaced with .7z
|
||||
|
||||
# Check if the target archive (e.g., .7z) already exists and is up to date
|
||||
stored_state = load_state(state_path)
|
||||
|
||||
if not state_changed(seq_state, stored_state):
|
||||
# Target archive is up to date, but we might still need to clean up old .zip
|
||||
if old_zip_path and old_zip_path.exists():
|
||||
# Old .zip exists but we have a newer .7z, remove the old one
|
||||
old_zip_path.unlink(missing_ok=True)
|
||||
old_state_path = state_path_for(old_zip_path)
|
||||
if old_state_path.exists():
|
||||
old_state_path.unlink(missing_ok=True)
|
||||
continue
|
||||
|
||||
work_items.append((seq_dir, zip_path, state_path, seq_state))
|
||||
@@ -320,18 +478,21 @@ def run_expand(worker_count: int, *, verbose: bool) -> int:
|
||||
|
||||
work_items: list[tuple[Path, dict]] = []
|
||||
|
||||
for zip_path in ARCHIVE_ROOT.rglob("*.zip"):
|
||||
state_path = state_path_for(zip_path)
|
||||
seq_state = load_state(state_path)
|
||||
if seq_state is None:
|
||||
log("expand", f"Skipping {zip_path} (missing metadata)")
|
||||
continue
|
||||
# Look for both .zip and .7z archives
|
||||
archive_patterns = ["*.zip", "*.7z"]
|
||||
for pattern in archive_patterns:
|
||||
for zip_path in ARCHIVE_ROOT.rglob(pattern):
|
||||
state_path = state_path_for(zip_path)
|
||||
seq_state = load_state(state_path)
|
||||
if seq_state is None:
|
||||
log("expand", f"Skipping {zip_path} (missing metadata)")
|
||||
continue
|
||||
|
||||
target_dir = sequence_dir_for(zip_path)
|
||||
if current_state(target_dir) == seq_state:
|
||||
continue
|
||||
target_dir = sequence_dir_for(zip_path)
|
||||
if current_state(target_dir) == seq_state:
|
||||
continue
|
||||
|
||||
work_items.append((zip_path, seq_state))
|
||||
work_items.append((zip_path, seq_state))
|
||||
|
||||
if not work_items:
|
||||
log("expand", "Working folders already match archives; nothing to expand.")
|
||||
@@ -363,19 +524,22 @@ def cleanup_orphan_archives(*, verbose: bool) -> int:
|
||||
|
||||
removed: list[Path] = []
|
||||
|
||||
for zip_path in ARCHIVE_ROOT.rglob("*.zip"):
|
||||
seq_dir = sequence_dir_for(zip_path)
|
||||
if seq_dir.exists():
|
||||
continue
|
||||
# Look for both .zip and .7z archives
|
||||
archive_patterns = ["*.zip", "*.7z"]
|
||||
for pattern in archive_patterns:
|
||||
for zip_path in ARCHIVE_ROOT.rglob(pattern):
|
||||
seq_dir = sequence_dir_for(zip_path)
|
||||
if seq_dir.exists():
|
||||
continue
|
||||
|
||||
rel = zip_path.relative_to(ARCHIVE_ROOT)
|
||||
log("zip", f"Removing orphan archive {rel}", verbose_only=True, verbose=verbose)
|
||||
rel = zip_path.relative_to(ARCHIVE_ROOT)
|
||||
log("zip", f"Removing orphan archive {rel}", verbose_only=True, verbose=verbose)
|
||||
|
||||
zip_path.unlink(missing_ok=True)
|
||||
state_path = state_path_for(zip_path)
|
||||
if state_path.exists():
|
||||
state_path.unlink()
|
||||
removed.append(zip_path)
|
||||
zip_path.unlink(missing_ok=True)
|
||||
state_path = state_path_for(zip_path)
|
||||
if state_path.exists():
|
||||
state_path.unlink()
|
||||
removed.append(zip_path)
|
||||
|
||||
if not removed:
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user