1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-06 19:40:08 +02:00

👌 Cleanup filesystem watcher code

This commit is contained in:
2023-10-24 18:24:18 +02:00
parent b9c7cdb3cc
commit 5594130ccd
9 changed files with 223 additions and 207 deletions

View File

@ -162,6 +162,10 @@
C44067F727E258410045BD4E /* DomainListPhpCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F627E258410045BD4E /* DomainListPhpCell.swift */; };
C44067F927E2585E0045BD4E /* DomainListTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */; };
C44067FB27E25FD70045BD4E /* DomainListTLSCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */; };
C441CC562AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C441CC582AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */; };
C44264BE2850B86C007400F1 /* SwiftUIHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */; };
C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */; };
C4463FCC29804BCB007B93D5 /* RCFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4463FCB29804BCB007B93D5 /* RCFile.swift */; };
@ -424,7 +428,7 @@
C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; };
C471E87928F9BB650021E251 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; };
C471E87B28F9BB650021E251 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C471E87C28F9BB650021E251 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
C471E87C28F9BB650021E251 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C471E87D28F9BB650021E251 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; };
C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; };
C471E87F28F9BB650021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
@ -512,7 +516,7 @@
C471E8DB28F9BB8F0021E251 /* TerminalProgressWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44C198C276E3A1C0072762D /* TerminalProgressWindowController.swift */; };
C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C44A874728905BB000498BC4 /* ProgressVC.swift */; };
C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C471E8DF28F9BB8F0021E251 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
C471E8DF28F9BB8F0021E251 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C471E8E028F9BB8F0021E251 /* Preset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40C5C9B2846A40600E28255 /* Preset.swift */; };
C471E8E128F9BB8F0021E251 /* PresetHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C463E37F284930EE00422731 /* PresetHelper.swift */; };
C471E8E228F9BB8F0021E251 /* WarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4297F7928970D59004C4630 /* WarningView.swift */; };
@ -678,8 +682,8 @@
C4C8900728F0E3EF00CE5E97 /* ActiveFileSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */; };
C4C8E818276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C4C8E819276F54D8003AC782 /* App+ConfigWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */; };
C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */; };
C4C8E81B276F54E5003AC782 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C4C8E81C276F54E5003AC782 /* ConfigWatchManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */; };
C4CB250529B28BB800CA4492 /* MainMenuTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB250429B28BB800CA4492 /* MainMenuTest.swift */; };
C4CB6E65292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; };
C4CB6E66292C362C002E9027 /* Homebrew.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4CB6E64292C362C002E9027 /* Homebrew.swift */; };
@ -946,6 +950,7 @@
C44067F627E258410045BD4E /* DomainListPhpCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainListPhpCell.swift; sourceTree = "<group>"; };
C44067F827E2585E0045BD4E /* DomainListTypeCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTypeCell.swift; sourceTree = "<group>"; };
C44067FA27E25FD70045BD4E /* DomainListTLSCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainListTLSCell.swift; sourceTree = "<group>"; };
C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigFSNotifier.swift; sourceTree = "<group>"; };
C44264BD2850B86C007400F1 /* SwiftUIHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHelper.swift; sourceTree = "<group>"; };
C44264BF2850BD2A007400F1 /* VersionPopoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionPopoverView.swift; sourceTree = "<group>"; };
C4463FCB29804BCB007B93D5 /* RCFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RCFile.swift; sourceTree = "<group>"; };
@ -1054,7 +1059,7 @@
C4C8900428F0E3D100CE5E97 /* RealFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealFileSystem.swift; sourceTree = "<group>"; };
C4C8900628F0E3EF00CE5E97 /* ActiveFileSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveFileSystem.swift; sourceTree = "<group>"; };
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "App+ConfigWatch.swift"; sourceTree = "<group>"; };
C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhpConfigWatcher.swift; sourceTree = "<group>"; };
C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigWatchManager.swift; sourceTree = "<group>"; };
C4CB250429B28BB800CA4492 /* MainMenuTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenuTest.swift; sourceTree = "<group>"; };
C4CB6E64292C362C002E9027 /* Homebrew.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Homebrew.swift; sourceTree = "<group>"; };
C4CCBA6B275C567B008C7055 /* PMWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PMWindowController.swift; sourceTree = "<group>"; };
@ -1745,7 +1750,6 @@
C4B5635D276AB09000F12CCB /* VersionExtractor.swift */,
C4D3660A29113F20006BD146 /* System.swift */,
C4D36614291160A1006BD146 /* WIP.swift */,
C41ADCE72970CCC700120423 /* FSNotifier.swift */,
C47DF1AE299D5A3B0007055D /* LoginItemManager.swift */,
C49EAA5129B12A5A00AB28FC /* Measurements.swift */,
);
@ -1964,9 +1968,11 @@
C4C8E81D276F5686003AC782 /* Watcher */ = {
isa = PBXGroup;
children = (
C4C8E81A276F54E5003AC782 /* ConfigWatchManager.swift */,
C441CC552AE8249400DDFACD /* ConfigFSNotifier.swift */,
C41ADCE72970CCC700120423 /* FSNotifier.swift */,
C49EAA5629B1689200AB28FC /* App+BrewWatch.swift */,
C4C8E817276F54D8003AC782 /* App+ConfigWatch.swift */,
C4C8E81A276F54E5003AC782 /* PhpConfigWatcher.swift */,
);
path = Watcher;
sourceTree = "<group>";
@ -2506,8 +2512,9 @@
C4D4CB3729C109CF00DB9F93 /* InternalSwitcher+Valet.swift in Sources */,
C46EBC4728DB9644007ACC74 /* RealShell.swift in Sources */,
C4068CAA27B0890D00544CD5 /* MenuBarIcons.swift in Sources */,
C441CC562AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C44264C02850BD2A007400F1 /* VersionPopoverView.swift in Sources */,
C4C8E81B276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
C4C8E81B276F54E5003AC782 /* ConfigWatchManager.swift in Sources */,
C417DC74277614690015E6EE /* Helpers.swift in Sources */,
C415D3E82770F692005EF286 /* AppDelegate+InterApp.swift in Sources */,
C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */,
@ -2683,7 +2690,7 @@
C471E87828F9BB650021E251 /* TerminalProgressWindowController.swift in Sources */,
C471E87928F9BB650021E251 /* ProgressVC.swift in Sources */,
C471E87B28F9BB650021E251 /* App+ConfigWatch.swift in Sources */,
C471E87C28F9BB650021E251 /* PhpConfigWatcher.swift in Sources */,
C471E87C28F9BB650021E251 /* ConfigWatchManager.swift in Sources */,
C471E87D28F9BB650021E251 /* Preset.swift in Sources */,
C471E87E28F9BB650021E251 /* PresetHelper.swift in Sources */,
C471E87F28F9BB650021E251 /* WarningView.swift in Sources */,
@ -2728,6 +2735,7 @@
C471E7E828F9BAC20021E251 /* Actions.swift in Sources */,
C40D72612A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C471E82528F9BB2E0021E251 /* ComposerWindow.swift in Sources */,
C441CC582AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C471E80828F9BAD40021E251 /* PhpExtension.swift in Sources */,
C471E7F928F9BACB0021E251 /* PhpSwitcher.swift in Sources */,
C471E82A28F9BB330021E251 /* ValetListable.swift in Sources */,
@ -2785,6 +2793,7 @@
C471E89228F9BB8F0021E251 /* Alert.swift in Sources */,
C471E89328F9BB8F0021E251 /* Application.swift in Sources */,
C471E89428F9BB8F0021E251 /* LocalNotification.swift in Sources */,
C441CC592AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C40934A5298EEB2C00D25014 /* CaskFile.swift in Sources */,
C471E89528F9BB8F0021E251 /* MenuBarImageGenerator.swift in Sources */,
C40D725D2A018ACC0054A067 /* PhpFormulaeStatus.swift in Sources */,
@ -2871,7 +2880,7 @@
C471E8DC28F9BB8F0021E251 /* ProgressVC.swift in Sources */,
C490E3BF29BCA376006D2DE6 /* Measurements.swift in Sources */,
C471E8DE28F9BB8F0021E251 /* App+ConfigWatch.swift in Sources */,
C471E8DF28F9BB8F0021E251 /* PhpConfigWatcher.swift in Sources */,
C471E8DF28F9BB8F0021E251 /* ConfigWatchManager.swift in Sources */,
C4CB250529B28BB800CA4492 /* MainMenuTest.swift in Sources */,
C40D72622A018AE30054A067 /* BrewFormula+UI.swift in Sources */,
C4B79ECE29CA475900A483EE /* RemovePhpVersionCommand.swift in Sources */,
@ -3018,7 +3027,7 @@
C43A8A2425D9D20D00591B77 /* HomebrewPackageTest.swift in Sources */,
C485707928BF456C00539B36 /* ArrayExtension.swift in Sources */,
C4F780CA25D80B75000DBC97 /* HomebrewDecodable.swift in Sources */,
C4C8E81C276F54E5003AC782 /* PhpConfigWatcher.swift in Sources */,
C4C8E81C276F54E5003AC782 /* ConfigWatchManager.swift in Sources */,
C4F319C927B034A500AFF46F /* Stats.swift in Sources */,
C4F30B04278E16BA00755FCE /* HomebrewService.swift in Sources */,
54D9E0B527E4F51E003B9AD9 /* Key.swift in Sources */,
@ -3034,6 +3043,7 @@
C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */,
C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */,
C463E381284930EE00422731 /* PresetHelper.swift in Sources */,
C441CC572AE8249400DDFACD /* ConfigFSNotifier.swift in Sources */,
C46FA98C2822F08F00D78807 /* PhpConfigurationFileTest.swift in Sources */,
C4D5576529C77CC5001A44CD /* PhpVersionManagerWindowController.swift in Sources */,
C4BF90C127C57C220054E78C /* MainMenu+FixMyValet.swift in Sources */,
@ -3492,7 +3502,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1335;
CURRENT_PROJECT_VERSION = 1336;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3523,7 +3533,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1335;
CURRENT_PROJECT_VERSION = 1336;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3763,7 +3773,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1335;
CURRENT_PROJECT_VERSION = 1336;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3879,7 +3889,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1335;
CURRENT_PROJECT_VERSION = 1336;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -3995,7 +4005,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1335;
CURRENT_PROJECT_VERSION = 1336;
DEAD_CODE_STRIPPING = YES;
DEBUG = YES;
DEVELOPMENT_TEAM = 8M54J5J787;
@ -4176,7 +4186,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1335;
CURRENT_PROJECT_VERSION = 1336;
DEAD_CODE_STRIPPING = YES;
DEBUG = NO;
DEVELOPMENT_TEAM = 8M54J5J787;

View File

@ -97,14 +97,14 @@ class PhpConfigurationFile: CreatedFromFile {
self.lines[item.lineIndex] = components.joined(separator: "=")
// Ensure the watchers aren't tripped up by config changes
PhpConfigWatcher.ignoresModificationsToConfigValues = true
ConfigWatchManager.ignoresModificationsToConfigValues = true
// Finally, join the string and save the file atomatically again
try self.lines.joined(separator: "\n")
.write(toFile: self.filePath, atomically: true, encoding: .utf8)
// Ensure watcher behaviour is reverted
PhpConfigWatcher.ignoresModificationsToConfigValues = false
ConfigWatchManager.ignoresModificationsToConfigValues = false
// Reload the original file
self.reload()

View File

@ -89,9 +89,6 @@ class App {
/** The warning manager, responsible for keeping track of warnings. */
var warnings = WarningManager.shared
/** The filesystem watchers, responsible for keeping track of changes to the PHP installation. */
var watchers: [FSNotifier.Kind: FSNotifier] = [:]
/** Timer that will periodically reload info about the user's PHP installation. */
var timer: Timer?
@ -120,8 +117,12 @@ class App {
// MARK: - App Watchers
/**
The `PhpConfigWatcher` is responsible for watching the `.ini` files and the `.conf.d` folder.
/** Individual filesystem watchers, which are, i.e. responsible for watching the Homebrew folders. */
var watchers: [String: FSNotifier] = [:]
/**
The `ConfigWatchManager` is responsible for watching the `.ini` files and the `.conf.d` folder.
This manager object can immediately start or stop all watchers (or pause them) all at once.
*/
var watcher: PhpConfigWatcher!
var watchManager: ConfigWatchManager!
}

View File

@ -17,13 +17,13 @@ extension App {
onChange: { Task { await self.onHomebrewPhpModification() } }
)
App.shared.watchers[.homebrewBinaries] = notifier
App.shared.watchers["homebrewBinaries"] = notifier
}
public func destroyHomebrewWatchers() {
// Removing requires termination and then removing reference
self.watchers[.homebrewBinaries]?.terminate()
self.watchers[.homebrewBinaries] = nil
self.watchers["homebrewBinaries"]?.terminate()
self.watchers["homebrewBinaries"] = nil
}
public func onHomebrewPhpModification() async {
@ -31,10 +31,13 @@ extension App {
Log.info("Something changed in the Homebrew binary directory...")
await PhpEnvironments.detectPhpVersions()
await MainMenu.shared.refreshActiveInstallation()
// let new = PhpEnvironments.shared.currentInstall?.version.text
// TODO:
// Check if the new and previous version are different
// if so, we can show a notification if needed
//
// TODO: PHP Guard 2.0
// Check if the new and previous version of PHP are different
// if so, we can show a notification if needed or alert the user
//
// let new = PhpEnvironments.shared.currentInstall?.version.text
//
}
}

View File

@ -10,52 +10,52 @@ import Foundation
extension App {
func startWatcher(_ url: URL) {
Log.perf("No watcher currently active...")
self.watcher = PhpConfigWatcher(for: url)
func startWatchManager(_ url: URL) {
Log.perf("Starting config watch manager...")
self.watchManager = ConfigWatchManager(for: url)
self.watcher.didChange = { url in
self.watchManager.didChange = { url in
Log.perf("Something has changed in: \(url)")
// Check if the watcher has last updated the menu less than 0.75s ago
let distance = self.watcher.lastUpdate?.distance(to: Date().timeIntervalSince1970)
let distance = self.watchManager.lastUpdate?.distance(to: Date().timeIntervalSince1970)
if distance == nil || distance != nil && distance! > 0.75 {
Log.perf("Refreshing menu...")
Task { @MainActor in MainMenu.shared.reloadPhpMonitorMenuInBackground() }
self.watcher.lastUpdate = Date().timeIntervalSince1970
self.watchManager.lastUpdate = Date().timeIntervalSince1970
}
}
}
func handlePhpConfigWatcher(forceReload: Bool = false) {
if ActiveFileSystem.shared is TestableFileSystem {
Log.warn("FS watcher is disabled when using testable filesystem.")
Log.warn("Config watch manager is disabled when using testable filesystem.")
return
}
guard let install = PhpEnvironments.phpInstall else {
Log.info("It appears as if no PHP installation is currently active.")
Log.info("The FS watcher will be disabled until a PHP install is active.")
Log.info("The config watch manager be disabled until a PHP install is active.")
return
}
let url = URL(fileURLWithPath: "\(Paths.etcPath)/php/\(install.version.short)")
// Check whether the watcher exists and schedule on the main thread
// Check whether the manager exists and schedule on the main thread
// if we don't consistently do this, the app will create duplicate watchers
// due to timing issues, which creates retain cycles.
// due to timing issues, which creates retain cycles
Task { @MainActor in
// Watcher needs to be created
if self.watcher == nil {
self.startWatcher(url)
if self.watchManager == nil {
self.startWatchManager(url)
}
// Watcher needs to be updated
if self.watcher.url != url || forceReload {
self.watcher.disable()
self.watcher = nil
if self.watchManager.url != url || forceReload {
self.watchManager.disable()
self.watchManager = nil
Log.perf("Watcher has stopped watching files. Starting new one...")
self.startWatcher(url)
self.startWatchManager(url)
}
}
}

View File

@ -0,0 +1,77 @@
//
// ConfigFSNotifier.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 24/10/2023.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class ConfigFSNotifier {
enum Behaviour {
case reloadsMenu
case reloadsWatchers
}
private var parent: ConfigWatchManager!
private var monitoredFolderFileDescriptor: CInt = -1
private var folderMonitorSource: DispatchSourceFileSystemObject?
let url: URL
init(
for url: URL,
eventMask: DispatchSource.FileSystemEvent,
parent: ConfigWatchManager,
behaviour: ConfigFSNotifier.Behaviour = .reloadsMenu
) {
self.url = url
self.parent = parent
self.startMonitoring(eventMask, behaviour: behaviour)
}
func startMonitoring(
_ eventMask: DispatchSource.FileSystemEvent,
behaviour: ConfigFSNotifier.Behaviour
) {
guard folderMonitorSource == nil && monitoredFolderFileDescriptor == -1 else {
return
}
monitoredFolderFileDescriptor = open(url.path, O_EVTONLY)
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: monitoredFolderFileDescriptor,
eventMask: eventMask,
queue: parent.folderMonitorQueue
)
folderMonitorSource?.setEventHandler { [weak self] in
if behaviour == .reloadsWatchers
&& !ConfigWatchManager.ignoresModificationsToConfigValues {
// Reload all configuration watchers
return App.shared.handlePhpConfigWatcher(forceReload: true)
}
self?.parent.didChange?(self!.url)
}
folderMonitorSource?.setCancelHandler { [weak self] in
guard let self = self else { return }
close(self.monitoredFolderFileDescriptor)
self.monitoredFolderFileDescriptor = -1
self.folderMonitorSource = nil
}
folderMonitorSource?.resume()
}
func stopMonitoring() {
folderMonitorSource?.cancel()
self.parent = nil
}
}

View File

@ -0,0 +1,82 @@
//
// ConfigWatchManager.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 30/03/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class ConfigWatchManager {
static var ignoresModificationsToConfigValues: Bool = false
let folderMonitorQueue = DispatchQueue(label: "FolderMonitorQueue", attributes: .concurrent)
let url: URL
var didChange: ((URL) -> Void)?
var lastUpdate: TimeInterval?
var watchers: [ConfigFSNotifier] = []
init(for url: URL) {
if FileSystem is TestableFileSystem {
fatalError("""
PhpConfigWatcher is not compatible with testable FS!"
You are not allowed to instantiate these while using a testable FS.
""")
}
self.url = url
// Add a watcher for php.ini
self.addWatcher(for: self.url.appendingPathComponent("php.ini"), eventMask: .write)
// Add a watcher for conf.d (in case a new file is added or a file is deleted)
// This watcher, when triggered, will restart all watchers
self.addWatcher(for: self.url.appendingPathComponent("conf.d"), eventMask: .all, behaviour: .reloadsWatchers)
// Scan the conf.d folder for .ini files, and add a watcher for each file
let filePaths = FileManager.default.enumerator(
atPath: self.url.appendingPathComponent("conf.d").path
)?.allObjects as! [String]
// Loop over the .ini files that we discovered
filePaths.filter { $0.contains(".ini") }.forEach { (file) in
// Add a watcher for each file we have discovered
self.addWatcher(for: self.url.appendingPathComponent("conf.d/\(file)"), eventMask: .write)
}
Log.perf("A watcher exists for the following config paths:")
Log.perf(self.watchers.map({ watcher in
return watcher.url.relativePath
}))
}
func addWatcher(
for url: URL,
eventMask: DispatchSource.FileSystemEvent,
behaviour: ConfigFSNotifier.Behaviour = .reloadsMenu
) {
if !FileSystem.anyExists(url.path) {
Log.warn("No watcher was created for \(url.path) because the requested file does not exist.")
return
}
let watcher = ConfigFSNotifier(for: url, eventMask: eventMask, parent: self, behaviour: behaviour)
self.watchers.append(watcher)
}
func disable() {
Log.perf("Turning off all individual existing watchers...")
self.watchers.forEach { (watcher) in
watcher.stopMonitoring()
}
}
deinit {
Log.perf("deinit: \(String(describing: self)).\(#function)")
}
}

View File

@ -6,12 +6,9 @@
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Cocoa
import Foundation
class FSNotifier {
enum Kind {
case homebrewLocks, homebrewBinaries
}
public static var shared: FSNotifier! = nil
@ -66,4 +63,5 @@ class FSNotifier {
deinit {
Log.perf("FSNotifier for \(self.url) will be deinitialized.")
}
}

View File

@ -1,155 +0,0 @@
//
// FolderWatcher.swift
// PHP Monitor
//
// Created by Nico Verbruggen on 30/03/2021.
// Copyright © 2023 Nico Verbruggen. All rights reserved.
//
import Foundation
class PhpConfigWatcher {
static var ignoresModificationsToConfigValues: Bool = false
let folderMonitorQueue = DispatchQueue(label: "FolderMonitorQueue", attributes: .concurrent)
let url: URL
var didChange: ((URL) -> Void)?
var lastUpdate: TimeInterval?
var watchers: [FSWatcher] = []
init(for url: URL) {
if FileSystem is TestableFileSystem {
fatalError("""
PhpConfigWatcher is not compatible with testable FS!"
You are not allowed to instantiate these while using a testable FS.
""")
}
self.url = url
// Add a watcher for php.ini
self.addWatcher(for: self.url.appendingPathComponent("php.ini"), eventMask: .write)
// Add a watcher for conf.d (in case a new file is added or a file is deleted)
// This watcher, when triggered, will restart all watchers
self.addWatcher(for: self.url.appendingPathComponent("conf.d"), eventMask: .all, behaviour: .reloadsWatchers)
// Scan the conf.d folder for .ini files, and add a watcher for each file
let enumerator = FileManager.default.enumerator(atPath: self.url.appendingPathComponent("conf.d").path)
let filePaths = enumerator?.allObjects as! [String]
// Loop over the .ini files that we discovered
filePaths.filter { $0.contains(".ini") }.forEach { (file) in
// Add a watcher for each file we have discovered
self.addWatcher(for: self.url.appendingPathComponent("conf.d/\(file)"), eventMask: .write)
}
Log.perf("A watcher exists for the following config paths:")
Log.perf(self.watchers.map({ watcher in
return watcher.url.relativePath
}))
}
func addWatcher(
for url: URL,
eventMask: DispatchSource.FileSystemEvent,
behaviour: FSWatcherBehaviour = .reloadsMenu
) {
if !FileSystem.anyExists(url.path) {
Log.warn("No watcher was created for \(url.path) because the requested file does not exist.")
return
}
let watcher = FSWatcher(for: url, eventMask: eventMask, parent: self, behaviour: behaviour)
self.watchers.append(watcher)
}
func disable() {
Log.perf("Turning off all individual existing watchers...")
self.watchers.forEach { (watcher) in
watcher.stopMonitoring()
}
}
deinit {
Log.perf("deinit: \(String(describing: self)).\(#function)")
}
}
enum FSWatcherBehaviour {
case reloadsMenu
case reloadsWatchers
}
class FSWatcher {
private var parent: PhpConfigWatcher!
private var monitoredFolderFileDescriptor: CInt = -1
private var folderMonitorSource: DispatchSourceFileSystemObject?
let url: URL
init(
for url: URL,
eventMask: DispatchSource.FileSystemEvent,
parent: PhpConfigWatcher,
behaviour: FSWatcherBehaviour = .reloadsMenu
) {
self.url = url
self.parent = parent
self.startMonitoring(eventMask, behaviour: behaviour)
}
func startMonitoring(
_ eventMask: DispatchSource.FileSystemEvent,
behaviour: FSWatcherBehaviour
) {
guard folderMonitorSource == nil && monitoredFolderFileDescriptor == -1 else {
return
}
// Open the file or folder referenced by URL for monitoring only.
monitoredFolderFileDescriptor = open(url.path, O_EVTONLY)
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: monitoredFolderFileDescriptor,
eventMask: eventMask,
queue: parent.folderMonitorQueue
)
// Define the block to call when a file change is detected.
folderMonitorSource?.setEventHandler { [weak self] in
if behaviour == .reloadsWatchers
&& !PhpConfigWatcher.ignoresModificationsToConfigValues {
// Reload all configuration watchers
return App.shared.handlePhpConfigWatcher(forceReload: true)
}
// The default behaviour is to reload the menu
self?.parent.didChange?(self!.url)
}
// Define a cancel handler to ensure the directory is closed when the source is cancelled.
folderMonitorSource?.setCancelHandler { [weak self] in
guard let self = self else { return }
close(self.monitoredFolderFileDescriptor)
self.monitoredFolderFileDescriptor = -1
self.folderMonitorSource = nil
}
// Start monitoring the directory via the source.
folderMonitorSource?.resume()
}
func stopMonitoring() {
folderMonitorSource?.cancel()
self.parent = nil
}
}