feat: add Docker Cache cleaning category (#1)#36
feat: add Docker Cache cleaning category (#1)#36MukundaKatta wants to merge 1 commit intomomenbasel:mainfrom
Conversation
|
@copilot resolve the merge conflicts in this pull request |
There was a problem hiding this comment.
Pull request overview
Adds a new “Docker Cache” cleaning category to PureMac, intended to surface Docker Desktop cache directories plus an informational “reclaimable space” entry derived from the docker CLI.
Changes:
- Add
dockerCachetoCleaningCategorywith icon/color/description. - Add Docker cache scanning to
ScanEngine(well-known on-disk paths + optionaldocker system dfreclaimable parsing). - Add localization keys for Docker strings in
enandzh-Hans.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| PureMac/Services/ScanEngine.swift | Adds scanDockerCache(), plus CLI-based reclaimable-space parsing helpers. |
| PureMac/Models/Models.swift | Introduces .dockerCache enum case + associated UI metadata (icon/description/color). |
| PureMac/en.lproj/Localizable.strings | Adds English localization keys for Docker Cache strings. |
| PureMac/zh-Hans.lproj/Localizable.strings | Adds Simplified Chinese localization keys for Docker Cache strings. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let dockerBinPaths = ["/usr/local/bin/docker", "/opt/homebrew/bin/docker"] | ||
| for dockerBin in dockerBinPaths where fileManager.fileExists(atPath: dockerBin) { | ||
| if let reclaimable = reclaimableDockerSpace(dockerBin: dockerBin), reclaimable > 0 { |
There was a problem hiding this comment.
PR description says the CLI check runs when docker is “on PATH”, but the implementation only checks two hard-coded locations (/usr/local/bin/docker, /opt/homebrew/bin/docker). If Docker is installed elsewhere (e.g., symlinked into a different directory), reclaimable space won’t be surfaced. Consider resolving via which docker//usr/bin/env docker (while still keeping the allowlist if desired).
| for dockerBin in dockerBinPaths where fileManager.fileExists(atPath: dockerBin) { | ||
| if let reclaimable = reclaimableDockerSpace(dockerBin: dockerBin), reclaimable > 0 { | ||
| items.append(CleanableItem( | ||
| name: "Reclaimable (run `docker system prune -af`)", |
There was a problem hiding this comment.
This label is hard-coded English even though corresponding keys were added to Localizable.strings. Since this value is passed around as a String (not a LocalizedStringKey), it won’t be localized at render time—prefer String(localized: "Reclaimable (run …)") (or NSLocalizedString) here so the new translations are actually used.
| name: "Reclaimable (run `docker system prune -af`)", | |
| name: String(localized: "Reclaimable (run `docker system prune -af`)"), |
| let dockerBinPaths = ["/usr/local/bin/docker", "/opt/homebrew/bin/docker"] | ||
| for dockerBin in dockerBinPaths where fileManager.fileExists(atPath: dockerBin) { | ||
| if let reclaimable = reclaimableDockerSpace(dockerBin: dockerBin), reclaimable > 0 { | ||
| items.append(CleanableItem( | ||
| name: "Reclaimable (run `docker system prune -af`)", | ||
| path: dockerBin, | ||
| size: reclaimable, | ||
| category: .dockerCache, | ||
| isSelected: false, | ||
| lastModified: nil | ||
| )) |
There was a problem hiding this comment.
The “Reclaimable …” virtual item uses the docker executable path as its path. In the current cleaning flow, selected items are deleted by item.path (with a safety allowlist), so if this row ends up selected it will attempt to delete /usr/local/bin/docker or /opt/homebrew/bin/docker and then surface a “Skipped … unsafe path” error. This should be modeled as a non-deletable/virtual item (e.g., add an isVirtual/action field and have the UI/cleaner skip it), and avoid using an actual filesystem path that implies it can be removed.
| if let reclaimable = reclaimableDockerSpace(dockerBin: dockerBin), reclaimable > 0 { | ||
| items.append(CleanableItem( | ||
| name: "Reclaimable (run `docker system prune -af`)", | ||
| path: dockerBin, | ||
| size: reclaimable, | ||
| category: .dockerCache, | ||
| isSelected: false, | ||
| lastModified: nil | ||
| )) |
There was a problem hiding this comment.
isSelected: false on the reclaimable-space row won’t have the intended effect with the current selection model: AppState defaults to “selected unless in deselectedItems” and clears deselectedItems on every scan, without initializing it from CleanableItem.isSelected. As a result this new virtual row will appear selected by default (and may be included in “Select All” flows). Either wire CleanableItem.isSelected into deselectedItems initialization when storing scan results, or remove isSelected from scan outputs and manage selection consistently in one place.
| /// Sum the reclaimable bytes reported by `docker system df --format json`. | ||
| /// Returns nil when Docker isn't running or the command fails — callers | ||
| /// should treat that as "no reclaimable info available", not as an error. | ||
| private func reclaimableDockerSpace(dockerBin: String) -> Int64? { | ||
| let task = Process() | ||
| task.executableURL = URL(fileURLWithPath: dockerBin) | ||
| task.arguments = ["system", "df", "--format", "{{.Reclaimable}}"] | ||
| let stdoutPipe = Pipe() |
There was a problem hiding this comment.
Doc comment mismatch: this function says it parses output from docker system df --format json, but the code runs docker system df --format "{{.Reclaimable}}". Update the comment (or the command) so future maintainers don’t assume JSON parsing here.
| items.append(CleanableItem( | ||
| name: "Reclaimable (run `docker system prune -af`)", | ||
| path: dockerBin, | ||
| size: reclaimable, | ||
| category: .dockerCache, | ||
| isSelected: false, | ||
| lastModified: nil | ||
| )) |
There was a problem hiding this comment.
The item name includes backticks (`docker system prune -af`). SwiftUI Text doesn’t render Markdown, so these backticks will show up literally in the UI. Consider removing the backticks (or presenting the command in a separate UI affordance like a copy button) to avoid confusing display text.
| /* Docker Cache */ | ||
| "Docker Cache" = "Docker Cache"; | ||
| "Docker images, containers, and build cache" = "Docker images, containers, and build cache"; | ||
| "Reclaimable (run `docker system prune -af`)" = "Reclaimable (run `docker system prune -af`)"; |
There was a problem hiding this comment.
These new Docker strings are only added for en and zh-Hans, but the repo also ships zh-Hant.lproj/Localizable.strings. Without adding equivalent keys there, users in that locale will see fallback/English for the new Docker UI strings. Consider adding the same three keys to zh-Hant (and any other supported locales) to keep localization coverage consistent.
| /* Docker Cache */ | ||
| "Docker Cache" = "Docker 缓存"; | ||
| "Docker images, containers, and build cache" = "Docker 镜像、容器和构建缓存"; | ||
| "Reclaimable (run `docker system prune -af`)" = "可回收空间(运行 `docker system prune -af`)"; |
There was a problem hiding this comment.
The string includes backticks (`docker system prune -af`). If this is shown in a normal label, backticks will appear literally (no code formatting). Consider removing the backticks or adjusting the presentation so the command is shown in a clearer way.
| "Reclaimable (run `docker system prune -af`)" = "可回收空间(运行 `docker system prune -af`)"; | |
| "Reclaimable (run docker system prune -af)" = "可回收空间(运行 docker system prune -af)"; |
Adds a new `dockerCache` CleaningCategory that scans the Docker
Desktop on macOS install for recoverable space:
- Scans on-disk caches under
`~/Library/Containers/com.docker.docker/Data/{cache,log,tmp}`,
`~/Library/Group Containers/group.com.docker/Caches`, and the
`~/.docker/{cli-plugins/.cache,buildx/cache}` user-tree caches.
- If the `docker` CLI is on PATH (Homebrew or /usr/local), invokes
`docker system df --format '{{.Reclaimable}}'` and surfaces the
total reclaimable bytes as an *unselected* virtual entry — the
user's expected to run `docker system prune -af` themselves; we
just display the magnitude.
The same pattern as the existing brewCache scanner: probe well-known
paths, fall back gracefully if Docker isn't installed, never hit
anything outside the user's container sandbox.
Localized strings added for both en and zh-Hans.
Closes momenbasel#1
f21b095 to
eddda08
Compare
Summary
Closes #1.
Adds a new `dockerCache` `CleaningCategory` so users with Docker Desktop installed can recover space without leaving PureMac.
What it scans
Pattern
Mirrors the existing `scanBrewCache()` implementation:
Localization
Added entries for both `en.lproj` and `zh-Hans.lproj`:
Test plan
AI-assisted disclosure
Drafted with Claude Code. I read the existing brew/xcode scanners to match the pattern, verified the Docker Desktop on macOS path layout, and built the project locally to confirm the new switch case compiles. I understand what the code does.