Skip to content

fix: replace Zip dependency with native ZipService#526

Open
pwltr wants to merge 2 commits intomasterfrom
fix/zip-service
Open

fix: replace Zip dependency with native ZipService#526
pwltr wants to merge 2 commits intomasterfrom
fix/zip-service

Conversation

@pwltr
Copy link
Copy Markdown
Contributor

@pwltr pwltr commented Apr 21, 2026

Description

Switch log zipping to an internal NSFileCoordinator-based service, add safer filename/overwrite/temp-file handling, and cover key zip edge cases with tests.

QA Notes

Zipping logs works as before with same compression level.

@claude

This comment has been minimized.

Comment thread CHANGELOG.md
## [2.2.0] - 2026-04-07

### Fixed
- Replace third-party Zip dependency with native ZipService using NSFileCoordinator #526
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changelog entry was added under ## [2.2.0] - 2026-04-07 (a released version section) instead of ## [Unreleased].

Per CLAUDE.md:

ALWAYS add exactly ONE entry per PR under ## [Unreleased]
NEVER modify released version sections — only edit ## [Unreleased]

Move this entry into the ### Fixed category under ## [Unreleased] and remove it from the [2.2.0] section.

Comment on lines +142 to +149
try FileManager.default.createDirectory(at: directoryToZipURL, withIntermediateDirectories: true, attributes: [:])
var usedFilenames = Set<String>()
for fileToZip in filesToZip {
try fileToZip.prepareInDirectory(directoryURL: directoryToZipURL, usedFilenames: &usedFilenames)
}
defer {
try? FileManager.default.removeItem(at: directoryToZipURL)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defer cleanup is placed after the for loop that can throw. In Swift, defer only registers when execution reaches that statement — if prepareInDirectory throws on any iteration, the defer never runs and directoryToZipURL leaks.

See context at

try FileManager.default.createDirectory(at: directoryToZipURL, withIntermediateDirectories: true, attributes: [:])
var usedFilenames = Set<String>()
for fileToZip in filesToZip {
try fileToZip.prepareInDirectory(directoryURL: directoryToZipURL, usedFilenames: &usedFilenames)
}
defer {
try? FileManager.default.removeItem(at: directoryToZipURL)
}
return try createZipAtTmp(
zipFilename: zipFilename,

Move the defer to immediately after createDirectory succeeds so it always runs:

Suggested change
try FileManager.default.createDirectory(at: directoryToZipURL, withIntermediateDirectories: true, attributes: [:])
var usedFilenames = Set<String>()
for fileToZip in filesToZip {
try fileToZip.prepareInDirectory(directoryURL: directoryToZipURL, usedFilenames: &usedFilenames)
}
defer {
try? FileManager.default.removeItem(at: directoryToZipURL)
}
try FileManager.default.createDirectory(at: directoryToZipURL, withIntermediateDirectories: true, attributes: [:])
defer {
try? FileManager.default.removeItem(at: directoryToZipURL)
}
var usedFilenames = Set<String>()
for fileToZip in filesToZip {
try fileToZip.prepareInDirectory(directoryURL: directoryToZipURL, usedFilenames: &usedFilenames)
}

Comment on lines +139 to +141
let directoryToZipURL = FileManager.default.temporaryDirectory
.appending(path: UUID().uuidString)
.appending(path: zipFilename)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The staging directory is constructed two levels deep: tmp/<UUID>/<zipFilename>/. The defer cleanup calls removeItem(at: directoryToZipURL) which deletes only the inner <zipFilename> directory — the <UUID> parent is left behind as an empty orphan in tmp/ on every call.

Fix: capture the UUID directory URL separately and remove it in the defer instead:

let uuidDirURL = FileManager.default.temporaryDirectory.appending(path: UUID().uuidString)
let directoryToZipURL = uuidDirURL.appending(path: zipFilename)
// ...
defer { try? FileManager.default.removeItem(at: uuidDirURL) }

Comment on lines +28 to +32
extension FileToZip {
static func text(_ text: String, filename: String) -> FileToZip {
.data(text.data(using: .utf8) ?? Data(), filename: filename)
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileToZip.text(_:filename:) is not called anywhere in this PR (not in LogService.swift or ZipServiceTests.swift).\n\nPer CLAUDE.md:\n> Don't add features, refactor, or introduce abstractions beyond what the task requires.\n\nRemove the unused factory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant