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
↓ haven-c3734780.tar.gzatlas at this save
files changed2 files
Sources/Haven/AppDelegate.swift+11 −0Sources/Haven/UI/AppState.swift+11 −1Sources/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 }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)