mirror of
https://github.com/bitsycore/Allofit.git
synced 2026-10-05 12:27:26 +00:00
Fix - An open window with a path query (term containing "/") re-ran the search over the whole index on every FSEvents update, keeping several cores busy while files changed. Index-driven refilters are now throttled to one per second, skipped while the window is closed, minimized or covered, and replayed once when it becomes visible again. Typing stays instant. - Path terms no longer build and lowercase the full path of every record: the folded folder path is precomputed once per folder (interned like parentPath) and matched together with the folded name without allocating the joined string. Path-only queries are about 3x faster. Cleanup - Remove dead code: FSChange.isDir, FileWatcher.latestEventId, IndexState.record(atPath:), the never-set lastQuery preference, AccessManager.reset(), SearchField.onSubmit, an unreachable return. - Merge duplicates: the two Settings size formatters become Formatters.sizeOrDash, the shellQuote wrapper is replaced by AdminShell.quote. - Naming conventions: "in"-prefixed parameter names everywhere (delegate methods, binary helpers, initializers), camelCase impl helpers. - Comment every function and type; fix stale comments. - Debug and release builds are warning-free.
87 lines
2.5 KiB
Swift
87 lines
2.5 KiB
Swift
import Foundation
|
|
import Darwin
|
|
|
|
// IndexerLock enforces single-writer access to the on-disk index using a
|
|
// POSIX advisory lock (fcntl F_SETLK) on a sentinel file. Whoever grabs the
|
|
// lock is the canonical indexer; competing processes (a second GUI, an old
|
|
// service that didn't exit cleanly) fall back to reader mode and just watch
|
|
// the cache file for updates.
|
|
//
|
|
// The lock is released automatically when the process exits or when unlock()
|
|
// closes the file descriptor. POSIX file locks have a known footgun: closing
|
|
// ANY descriptor on the file releases all locks held by this process. The
|
|
// class owns the fd exclusively and never re-opens it.
|
|
final class IndexerLock {
|
|
|
|
// path of the lock sentinel file (one per cache location)
|
|
let path: String
|
|
// open descriptor, -1 when not locked
|
|
private var fileDescriptor: Int32 = -1
|
|
|
|
// creates an unlocked handle for the sentinel file at inPath
|
|
init(path inPath: String) {
|
|
self.path = inPath
|
|
}
|
|
|
|
// releases the lock together with the handle
|
|
deinit {
|
|
unlock()
|
|
}
|
|
|
|
// attempts to acquire the lock without blocking
|
|
// returns true on success, false if another process already holds it
|
|
func tryLock() -> Bool {
|
|
guard fileDescriptor < 0 else { return true }
|
|
|
|
let vDir = (path as NSString).deletingLastPathComponent
|
|
try? FileManager.default.createDirectory(
|
|
atPath: vDir,
|
|
withIntermediateDirectories: true
|
|
)
|
|
|
|
let vFd = open(path, O_CREAT | O_RDWR, 0o644)
|
|
if vFd < 0 { return false }
|
|
|
|
var vLock = flock(
|
|
l_start: 0,
|
|
l_len: 0,
|
|
l_pid: 0,
|
|
l_type: Int16(F_WRLCK),
|
|
l_whence: Int16(SEEK_SET)
|
|
)
|
|
let vResult = withUnsafeMutablePointer(to: &vLock) { vPtr in
|
|
fcntl(vFd, F_SETLK, vPtr)
|
|
}
|
|
if vResult < 0 {
|
|
close(vFd)
|
|
return false
|
|
}
|
|
|
|
// write pid for diagnostics (ftruncate first so the file stays small)
|
|
_ = ftruncate(vFd, 0)
|
|
let vPidString = "\(getpid())\n"
|
|
vPidString.withCString { vCstr in
|
|
_ = write(vFd, vCstr, strlen(vCstr))
|
|
}
|
|
fileDescriptor = vFd
|
|
return true
|
|
}
|
|
|
|
// releases the lock (also runs at deinit)
|
|
func unlock() {
|
|
if fileDescriptor >= 0 {
|
|
close(fileDescriptor)
|
|
fileDescriptor = -1
|
|
}
|
|
}
|
|
|
|
// reads the pid currently holding the lock, if any (best-effort)
|
|
static func readHolderPid(path inPath: String) -> Int32? {
|
|
guard let vData = try? Data(contentsOf: URL(fileURLWithPath: inPath)),
|
|
let vStr = String(data: vData, encoding: .utf8),
|
|
let vPid = Int32(vStr.trimmingCharacters(in: .whitespacesAndNewlines))
|
|
else { return nil }
|
|
return vPid
|
|
}
|
|
}
|