diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9221fca..8b3622e 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -79,6 +79,10 @@ C417DC74277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C417DC75277614690015E6EE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C417DC73277614690015E6EE /* Helpers.swift */; }; C4181F1128FAF9330042EA28 /* UITestCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4181F1028FAF9330042EA28 /* UITestCase.swift */; }; + C41ADCE82970CCC700120423 /* FSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41ADCE72970CCC700120423 /* FSNotifier.swift */; }; + C41ADCE92970CCC700120423 /* FSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41ADCE72970CCC700120423 /* FSNotifier.swift */; }; + C41ADCEA2970CCC700120423 /* FSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41ADCE72970CCC700120423 /* FSNotifier.swift */; }; + C41ADCEB2970CCC700120423 /* FSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41ADCE72970CCC700120423 /* FSNotifier.swift */; }; C41C02A927E61A65009F26CB /* FakeValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* FakeValetSite.swift */; }; C41C02AB27E61CB3009F26CB /* FakeValetSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C02A827E61A65009F26CB /* FakeValetSite.swift */; }; C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */; }; @@ -760,6 +764,7 @@ C4168F4427ADB4A3003B6C39 /* DEVELOPER.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = DEVELOPER.md; sourceTree = ""; }; C417DC73277614690015E6EE /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; C4181F1028FAF9330042EA28 /* UITestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestCase.swift; sourceTree = ""; }; + C41ADCE72970CCC700120423 /* FSNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSNotifier.swift; sourceTree = ""; }; C41C02A827E61A65009F26CB /* FakeValetSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeValetSite.swift; sourceTree = ""; }; C41C1B3322B0097F00E7CF16 /* PHP Monitor.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "PHP Monitor.app"; sourceTree = BUILT_PRODUCTS_DIR; }; C41C1B3622B0097F00E7CF16 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -1403,6 +1408,7 @@ C4B5635D276AB09000F12CCB /* VersionExtractor.swift */, C4D3660A29113F20006BD146 /* System.swift */, C4D36614291160A1006BD146 /* WIP.swift */, + C41ADCE72970CCC700120423 /* FSNotifier.swift */, ); path = Helpers; sourceTree = ""; @@ -1969,6 +1975,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C41ADCE82970CCC700120423 /* FSNotifier.swift in Sources */, C47699EF28A2F2A30060FEB8 /* WarningManager.swift in Sources */, C4ACA38F25C754C100060C66 /* PhpExtension.swift in Sources */, C4D3661A291173EA006BD146 /* DictionaryExtension.swift in Sources */, @@ -2230,6 +2237,7 @@ C471E88928F9BB650021E251 /* SwiftUIHelper.swift in Sources */, C471E88B28F9BB650021E251 /* HotKey.swift in Sources */, C471E88C28F9BB650021E251 /* HotKeysController.swift in Sources */, + C41ADCEA2970CCC700120423 /* FSNotifier.swift in Sources */, C471E88D28F9BB650021E251 /* Key.swift in Sources */, C471E88E28F9BB650021E251 /* KeyCombo.swift in Sources */, C471E88F28F9BB650021E251 /* ModifierFlagsExtension.swift in Sources */, @@ -2357,6 +2365,7 @@ C471E8C728F9BB8F0021E251 /* Warning.swift in Sources */, C471E8C828F9BB8F0021E251 /* WarningManager.swift in Sources */, C471E8C928F9BB8F0021E251 /* WarningsWindowController.swift in Sources */, + C41ADCEB2970CCC700120423 /* FSNotifier.swift in Sources */, C471E8CA28F9BB8F0021E251 /* OnboardingWindowController.swift in Sources */, C471E8CB28F9BB8F0021E251 /* PreferencesWindowController.swift in Sources */, C471E8CC28F9BB8F0021E251 /* PreferencesWindowController+Hotkey.swift in Sources */, @@ -2495,6 +2504,7 @@ C4D5CFCB27E0F9CD00035329 /* NginxConfigurationFile.swift in Sources */, C4068CA827B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C4F780CE25D80B75000DBC97 /* LocalNotification.swift in Sources */, + C41ADCE92970CCC700120423 /* FSNotifier.swift in Sources */, C40C7F2927721FF600DDDCDC /* ActivePhpInstallation+Checks.swift in Sources */, C485707A28BF457800539B36 /* WarningListView.swift in Sources */, C4C0E8E827F88B41002D32A9 /* DomainScanner.swift in Sources */, diff --git a/phpmon/Common/Helpers/FSNotifier.swift b/phpmon/Common/Helpers/FSNotifier.swift new file mode 100644 index 0000000..16294cc --- /dev/null +++ b/phpmon/Common/Helpers/FSNotifier.swift @@ -0,0 +1,90 @@ +// +// FSNotifier.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 13/01/2023. +// Copyright © 2023 Nico Verbruggen. All rights reserved. +// + +import Cocoa + +class FSNotifier { + enum Kind { + case homebrewLocks, homebrewBinaries + } + + public static var shared: FSNotifier! = nil + + let queue = DispatchQueue(label: "FSWatch2Queue", attributes: .concurrent) + + var lastUpdate: TimeInterval? + var linked: Bool + + private var fileDescriptor: CInt = -1 + private var dispatchSource: DispatchSourceFileSystemObject? + + internal let url: URL + + init(for url: URL, eventMask: DispatchSource.FileSystemEvent, onChange: @escaping () -> Void) { + self.url = url + + self.linked = FileSystem.fileExists(Paths.php) + print("Initial PHP linked state: \(linked)") + + fileDescriptor = open(url.path, O_EVTONLY) + + dispatchSource = DispatchSource.makeFileSystemObjectSource( + fileDescriptor: fileDescriptor, + eventMask: eventMask, + queue: self.queue + ) + + dispatchSource?.setEventHandler(handler: { + let distance = self.lastUpdate?.distance(to: Date().timeIntervalSince1970) + + if distance == nil || distance != nil && distance! > 1.00 { + print("FS event fired, checking in 1s, no duplicate FS events will be acted upon") + + self.lastUpdate = Date().timeIntervalSince1970 + + Task { + await delay(seconds: 1) + + let newLinked = FileSystem.fileExists(Paths.php) + + if newLinked != self.linked { + self.linked = newLinked + + Log.info("The status of the PHP binary has changed!") + + if newLinked { + Log.info("php is linked") + } else { + Log.info("php is not linked") + } + } + + onChange() + } + } + }) + + dispatchSource?.setCancelHandler(handler: { [weak self] in + guard let self = self else { return } + + close(self.fileDescriptor) + self.fileDescriptor = -1 + self.dispatchSource = nil + }) + + dispatchSource?.resume() + } + + func terminate() { + dispatchSource?.cancel() + } + + deinit { + Log.perf("FSNotifier for \(self.url) will be deinitialized.") + } +} diff --git a/phpmon/Domain/App/AppDelegate.swift b/phpmon/Domain/App/AppDelegate.swift index ebeed23..da8a5f9 100644 --- a/phpmon/Domain/App/AppDelegate.swift +++ b/phpmon/Domain/App/AppDelegate.swift @@ -50,6 +50,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele */ var logger = Log.shared + /** + + */ + var watchers: [FSNotifier.Kind: FSNotifier] = [:] + // MARK: - Initializer /** @@ -99,96 +104,27 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele startup procedure. */ func applicationDidFinishLaunching(_ aNotification: Notification) { - self.watchHomebrewBinFolder() - /* // Make sure notifications will work setupNotifications() + // Make sure the watchers are set up + // TODO: Move to after startup + self.watchHomebrewBinFolder() + Task { // Make sure the menu performs its initial checks await paths.loadUser() await menu.startup() } - */ } func watchHomebrewBinFolder() { - Log.info("Watching Homebrew's bin folder") - FSWatch2.shared = FSWatch2( + self.watchers[.homebrewLocks] = FSNotifier( for: URL(fileURLWithPath: Paths.binPath), eventMask: .all, onChange: { - print("Something has changed") + // Removing requires termination and then removing reference + // self.watchers[.homebrewLocks]?.terminate() + // self.watchers[.homebrewLocks] = nil } ) } } - -class FSWatch2 { - public static var shared: FSWatch2! = nil - - let queue = DispatchQueue(label: "FSWatch2Queue", attributes: .concurrent) - - var lastUpdate: TimeInterval? - var linked: Bool - - private var fileDescriptor: CInt = -1 - private var dispatchSource: DispatchSourceFileSystemObject? - - internal let url: URL - - init(for url: URL, eventMask: DispatchSource.FileSystemEvent, onChange: () -> Void) { - self.url = url - - self.linked = FileSystem.fileExists(Paths.php) - print("Initial PHP linked state: \(linked)") - - fileDescriptor = open(url.path, O_EVTONLY) - - dispatchSource = DispatchSource.makeFileSystemObjectSource( - fileDescriptor: fileDescriptor, - eventMask: eventMask, - queue: self.queue - ) - - dispatchSource?.setEventHandler(handler: { - let distance = self.lastUpdate?.distance(to: Date().timeIntervalSince1970) - - if distance == nil || distance != nil && distance! > 1.00 { - print("FS event fired, checking in 1s, no duplicate FS events will be acted upon") - - self.lastUpdate = Date().timeIntervalSince1970 - - Task { - await delay(seconds: 1) - - let newLinked = FileSystem.fileExists(Paths.php) - - if newLinked != self.linked { - self.linked = newLinked - - Log.info("The status of the PHP binary has changed!") - - if newLinked { - Log.info("php is linked") - } else { - Log.info("php is not linked") - } - } - } - } - }) - - dispatchSource?.setCancelHandler(handler: { [weak self] in - guard let self = self else { return } - - close(self.fileDescriptor) - self.fileDescriptor = -1 - self.dispatchSource = nil - }) - - dispatchSource?.resume() - } - - deinit { - print("deallocing watcher") - } -}