Skip to content

feat: add Docker Cache cleaning category (#1)#36

Open
MukundaKatta wants to merge 1 commit intomomenbasel:mainfrom
MukundaKatta:feat/docker-cache-category
Open

feat: add Docker Cache cleaning category (#1)#36
MukundaKatta wants to merge 1 commit intomomenbasel:mainfrom
MukundaKatta:feat/docker-cache-category

Conversation

@MukundaKatta
Copy link
Copy Markdown
Contributor

Summary

Closes #1.

Adds a new `dockerCache` `CleaningCategory` so users with Docker Desktop installed can recover space without leaving PureMac.

What it scans

  • On-disk caches under the Docker Desktop sandbox:
    • `~/Library/Containers/com.docker.docker/Data/{cache,log,tmp}`
    • `~/Library/Group Containers/group.com.docker/Caches`
    • `~/.docker/cli-plugins/.cache`
    • `~/.docker/buildx/cache`
  • Reclaimable space via the `docker` CLI:
    • Runs `docker system df --format '{{.Reclaimable}}'` if `docker` is on PATH (Homebrew or `/usr/local/bin`).
    • Surfaces the parsed total as an unselected virtual entry. The user runs `docker system prune -af` themselves — we just display how much they could recover.

Pattern

Mirrors the existing `scanBrewCache()` implementation:

  • Probe well-known paths first, fall through cleanly if Docker isn't installed.
  • Never touch anything outside the user's container sandbox.
  • Default to `isSelected: true` for cache directories that PureMac can safely delete itself; default to `false` for the CLI-managed reclaimable virtual entry.

Localization

Added entries for both `en.lproj` and `zh-Hans.lproj`:

  • "Docker Cache"
  • "Docker images, containers, and build cache"
  • "Reclaimable (run `docker system prune -af`)"

Test plan

  • `xcodebuild -project PureMac.xcodeproj -scheme PureMac -configuration Debug build` — succeeds.
  • All existing `CleaningCategory` switches updated (Models, ScanEngine).
  • No new dependencies.

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.

@momenbasel
Copy link
Copy Markdown
Owner

@copilot resolve the merge conflicts in this pull request

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 dockerCache to CleaningCategory with icon/color/description.
  • Add Docker cache scanning to ScanEngine (well-known on-disk paths + optional docker system df reclaimable parsing).
  • Add localization keys for Docker strings in en and zh-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.

Comment on lines +383 to +385
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 {
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
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`)",
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
name: "Reclaimable (run `docker system prune -af`)",
name: String(localized: "Reclaimable (run `docker system prune -af`)"),

Copilot uses AI. Check for mistakes.
Comment on lines +383 to +393
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
))
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +385 to +393
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
))
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +402 to +409
/// 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()
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +386 to +393
items.append(CleanableItem(
name: "Reclaimable (run `docker system prune -af`)",
path: dockerBin,
size: reclaimable,
category: .dockerCache,
isSelected: false,
lastModified: nil
))
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +123
/* 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`)";
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
/* Docker Cache */
"Docker Cache" = "Docker 缓存";
"Docker images, containers, and build cache" = "Docker 镜像、容器和构建缓存";
"Reclaimable (run `docker system prune -af`)" = "可回收空间(运行 `docker system prune -af`)";
Copy link

Copilot AI Apr 18, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
"Reclaimable (run `docker system prune -af`)" = "可回收空间(运行 `docker system prune -af`)";
"Reclaimable (run docker system prune -af)" = "可回收空间(运行 docker system prune -af)";

Copilot uses AI. Check for mistakes.
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
@MukundaKatta MukundaKatta force-pushed the feat/docker-cache-category branch from f21b095 to eddda08 Compare April 18, 2026 09:24
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.

Add Docker cache cleaning

3 participants