Files
Allofit/sources/core/VolumeManager.swift
Bitsy 547753cc4d Status bar stats, background throttling, Performance settings, drag fix
- Status bar: last search time, memory footprint, CPU use (highlighted when
  busy) and role, with a tooltip for peak memory, threads, index age, cache
  size and roots. Sampled every 2 s, redraws only the status bar; match count
  and search time live in a separate object so the table isn't re-rendered.
- Background throttling: file changes are collected longer and open results
  refresh less often when Allofit isn't focused or no window is visible;
  sampling pauses when hidden. Showing a window applies pending changes at
  once.
- Settings > Performance: the update delays (focused / another app focused /
  no window) and result refresh intervals, with an estimate of how soon a
  changed file appears and Restore Defaults. Defaults 2 s / 15 s / 1 min and
  1 s / 5 s.
- Drag rows as file URLs (like Finder) instead of a plain link, so browsers
  upload the file instead of navigating to it.
- Canonicalize roots with realpath: roots under /tmp, /var or /etc never
  matched their own entries because Foundation strips /private.
2026-10-02 10:20:45 +02:00

85 lines
3.1 KiB
Swift

import Foundation
// VolumeManager enumerates mounted volumes and derives the effective list of
// roots to index based on the user's preferences.
enum VolumeManager {
// a mounted volume identified by URL plus user-friendly metadata
struct Volume: Identifiable, Hashable {
let id: String
let url: URL
let name: String
let isLocal: Bool
let isInternal: Bool
// true when the volume is hosted on a remote file system
var isNetwork: Bool { !isLocal }
}
// returns every mounted volume except the boot volume (covered by /Users)
static func mountedVolumes() -> [Volume] {
let vKeys: [URLResourceKey] = [
.volumeNameKey,
.volumeIsLocalKey,
.volumeIsInternalKey,
.volumeIsRootFileSystemKey,
.volumeURLKey
]
guard let vUrls = FileManager.default.mountedVolumeURLs(
includingResourceValuesForKeys: vKeys,
options: []
) else { return [] }
var vResult: [Volume] = []
for vUrl in vUrls {
guard let vValues = try? vUrl.resourceValues(forKeys: Set(vKeys)) else { continue }
if vValues.volumeIsRootFileSystem == true { continue }
let vIsLocal = vValues.volumeIsLocal ?? false
let vIsInternal = vValues.volumeIsInternal ?? false
let vName = vValues.volumeName ?? vUrl.lastPathComponent
vResult.append(Volume(
id: vUrl.path,
url: vUrl,
name: vName,
isLocal: vIsLocal,
isInternal: vIsInternal
))
}
return vResult.sorted { $0.name.localizedStandardCompare($1.name) == .orderedAscending }
}
// returns the effective root URLs combining configured paths and mounted
// volumes that match the include-mounted / include-network preferences.
// Paths are canonicalized and nested roots dropped (a root inside another
// root would otherwise be walked, watched and indexed twice).
static func effectiveRoots(inPreferences: Preferences) -> [URL] {
var vPaths: [String] = inPreferences.rootPaths.map { canonicalPath(inPath: $0) }
if inPreferences.includeMountedVolumes || inPreferences.includeNetworkVolumes {
for vVol in mountedVolumes() {
if vVol.isNetwork && !inPreferences.includeNetworkVolumes { continue }
if !vVol.isNetwork && !inPreferences.includeMountedVolumes { continue }
vPaths.append(canonicalPath(inPath: vVol.url.path))
}
}
let vMinimal = Set(SubtreeMatcher.minimalRoots(inPaths: vPaths))
// keep the user's order, minus duplicates and nested entries
var vSeen = Set<String>()
return vPaths
.filter { vMinimal.contains($0) && vSeen.insert($0).inserted }
.map { URL(fileURLWithPath: $0) }
}
// the path the kernel reports for inPath (symlinks resolved, e.g.
// /tmp -> /private/tmp), which is what FSEvents and the walker produce.
// Foundation's standardizedFileURL / resolvingSymlinksInPath strip the
// /private prefix instead, so a root under /tmp, /var or /etc would never
// match its own entries. Falls back to the given path (minus a trailing
// slash) when it doesn't exist, e.g. an unmounted volume.
static func canonicalPath(inPath: String) -> String {
if let vResolved = realpath(inPath, nil) {
defer { free(vResolved) }
return String(cString: vResolved)
}
return URL(fileURLWithPath: inPath).path
}
}