c3734780b18a

land in-flight hardening: status-item removal, temp-file cleanup, defensive pictures directory (v0.6.0)

time
2026-07-16 22:07 UTC · 2 months ago
machine
koh2
agent
minimax-m3
session
resolve remaining Haven findings
parent
f5f88eb6cf9c
2 files Sources/Haven/AppDelegate.swift+11 −0Sources/Haven/UI/AppState.swift+11 −1
Sources/Haven/AppDelegate.swift+11 −0
@@ -9,7 +9,15 @@99   // delegate.1010   private var statusItem: NSStatusItem?1111   private var menuController: MenuController?12+  private let removeStatusItem: (NSStatusItem) -> Void1213 14+  init(removeStatusItem: @escaping (NSStatusItem) -> Void = {15+    NSStatusBar.system.removeStatusItem($0)16+  }) {17+    self.removeStatusItem = removeStatusItem18+    super.init()19+  }20+1321   func applicationDidFinishLaunching(_ notification: Notification) {1422     // Create the status item in the menu bar1523     let item = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)@@ -40,6 +48,9 @@4048   /// that want to recreate the delegate cleanly and for any future code4149   /// path that needs to dispose of the menu bar item explicitly.4250   func invalidate() {51+    if let item = statusItem {52+      removeStatusItem(item)53+    }4354     statusItem = nil4455     menuController = nil4556   }

view file

Sources/Haven/UI/AppState.swift+11 −1
@@ -90,6 +90,7 @@9090   private let autostartManager: AutostartManaging9191   private let chooseFolder: @MainActor (URL, @escaping (URL?) -> Void) -> Void9292   private let terminateApplication: @MainActor () -> Void93+  private let removeTempFile: @MainActor (URL) throws -> Void9394   private var cancellables = Set<AnyCancellable>()9495   // Monotonic token incremented on every reset; only the search started with9596   // the latest token may commit results. This keeps a slow older request from@@ -103,6 +104,12 @@103104     autostartManager: AutostartManaging? = nil,104105     chooseFolder: (@MainActor (URL, @escaping (URL?) -> Void) -> Void)? = nil,105106     terminateApplication: (@MainActor () -> Void)? = nil,107+    removeTempFile: @escaping @MainActor (URL) throws -> Void = {108+      try FileManager.default.removeItem(at: $0)109+    },110+    picturesDirectoryProvider: @escaping () -> URL? = {111+      FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first112+    },106113     observeChanges: Bool = true107114   ) {108115     let api = api ?? WallhavenAPI()@@ -113,13 +120,14 @@113120     self.autostartManager = autostartManager114121     self.chooseFolder = chooseFolder ?? AppState.presentFolderPicker115122     self.terminateApplication = terminateApplication ?? { NSApp.terminate(nil) }123+    self.removeTempFile = removeTempFile116124     self.launchAtLogin = autostartManager.isEnabled117125     // Load settings118126     self.saveToFolderEnabled = UserDefaults.standard.bool(forKey: "saveToFolderEnabled")119127     if let path = UserDefaults.standard.string(forKey: "saveFolderPath") {120128       self.saveFolderPath = path121129     } else {122-      let pictures = FileManager.default.urls(for: .picturesDirectory, in: .userDomainMask).first!130+      let pictures = picturesDirectoryProvider() ?? FileManager.default.temporaryDirectory123131       self.saveFolderPath = pictures.appendingPathComponent("Haven").path124132     }125133 @@ -288,6 +296,7 @@288296     }289297     do {290298       let localURL = try await wallpaperManager.download(from: remoteURL)299+      defer { try? removeTempFile(localURL) }291300 292301       // AppState is @MainActor isolated; the prior MainActor.run hop was293302       // redundant because the call site already runs on the main actor.@@ -339,6 +348,7 @@339348     }340349     do {341350       let localURL = try await wallpaperManager.download(from: remoteURL)351+      defer { try? removeTempFile(localURL) }342352 343353       // Always save to folder if download requested, or ensure save folder is used344354       let destFolder = URL(fileURLWithPath: saveFolderPath)

view file