mirror of
https://github.com/bitsycore/Allofit.git
synced 2026-10-05 12:27:26 +00:00
Memory / relaunch - FSEvents replay after a relaunch no longer copies the whole index per changed file: allRecords is no longer @Published (each element mutation emitted the full array and a buffering subscriber kept every copy alive, reaching 10+ GB). Changes are coalesced, stat'ed off-main and merged once. - Save the cache when the history replay finishes and on quit, and save the event id actually merged, so the next launch doesn't replay days again. - Views (App scene, ContentView, PreviewPane) no longer observe AppModel, so index updates don't re-render the table or reload the Quick Look preview. - Break retain cycles in the FSEvents watcher callbacks. Search (Everything syntax) - Space = AND, | = OR, ! = NOT, quotes, ext:, file:, folder:. - Terms with / match the full path; path wildcards are folder-aware (* within a folder name, ** across folders), e.g. "some/folder/**/path" IMG_????.heic - Byte-level matching (memmem / native glob) instead of NSRegularExpression, parallel filter, top-N selection instead of a full sort, total match count in the status bar, near-instant typing debounce. Index correctness - New IndexState shared by the GUI and the service: stable path-derived ids, O(1) swap removal, folder removal drops its subtree (no ghost entries after moving a folder to the Trash), folders moved/renamed in are rescanned, parent-must-be-indexed rule matching the walker (hidden/package content). - Unicode-normalized path ids (no duplicates for accented names). - Reconcile on load and on settings change: prune removed roots, exclusions and orphans; scan new roots; rescan when the saved event id is unusable. - Service resumes from its cache instead of rescanning everything on start. - Shared parent-path strings, cheaper exclusion checks, deduped roots. Tests - Swift Testing target for the search syntax, index rules, top-N selection and cache round trip; run in CI.
40 lines
1.4 KiB
Swift
40 lines
1.4 KiB
Swift
import Foundation
|
|
|
|
// ExclusionMatcher tests filesystem paths against a configured exclusion list.
|
|
// A path is excluded when it matches an exclusion exactly or sits beneath one
|
|
// of the configured prefix directories.
|
|
//
|
|
// Called for every entry during a walk and for every record during a prune,
|
|
// so the check is a plain UTF-8 prefix compare: the paths it receives come
|
|
// from URL.path / FSEvents and are already standardized.
|
|
struct ExclusionMatcher: Sendable {
|
|
|
|
// normalized exclusions (no trailing slash, tilde expanded)
|
|
private let paths: [String]
|
|
// the same exclusions with a trailing slash, as UTF-8 bytes
|
|
private let prefixes: [[UInt8]]
|
|
|
|
// builds a matcher from raw user-entered exclusion strings
|
|
init(inExclusions: [String]) {
|
|
paths = inExclusions.map { vRaw in
|
|
let vExpanded = (vRaw as NSString).expandingTildeInPath
|
|
let vStandard = (vExpanded as NSString).standardizingPath
|
|
if vStandard.hasSuffix("/") && vStandard.count > 1 {
|
|
return String(vStandard.dropLast())
|
|
}
|
|
return vStandard
|
|
}
|
|
prefixes = paths.map { Array(($0 == "/" ? "/" : $0 + "/").utf8) }
|
|
}
|
|
|
|
// returns true when inPath matches an exclusion exactly or is below one
|
|
func isExcluded(inPath: String) -> Bool {
|
|
if paths.isEmpty { return false }
|
|
for (vI, vEx) in paths.enumerated() {
|
|
if inPath == vEx { return true }
|
|
if inPath.utf8.starts(with: prefixes[vI]) { return true }
|
|
}
|
|
return false
|
|
}
|
|
}
|