diff --git a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme index 8642ae0..b0a4d86 100644 --- a/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme +++ b/PHP Monitor.xcodeproj/xcshareddata/xcschemes/PHP Monitor DEV.xcscheme @@ -91,7 +91,7 @@ + isEnabled = "YES"> Self { + public static func dummy(named service: String, enabled: Bool) -> HomebrewService { return HomebrewService( name: service, service_name: service, @@ -41,4 +63,10 @@ struct HomebrewService: Decodable, Equatable, Hashable { hasher.combine(service_name) hasher.combine(pid) } + + static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool { + return lhs.name == rhs.name + && lhs.pid == rhs.pid + && lhs.status == rhs.status + } } diff --git a/phpmon/Common/Testables/TestableConfiguration.swift b/phpmon/Common/Testables/TestableConfiguration.swift index 4c05182..beaf040 100644 --- a/phpmon/Common/Testables/TestableConfiguration.swift +++ b/phpmon/Common/Testables/TestableConfiguration.swift @@ -26,6 +26,9 @@ public struct TestableConfiguration: Codable { ActiveCommand.useTestable(commandOutput) Log.info("Applying fake scanner...") ValetScanner.useFake() + Log.info("Applying fake services manager...") + Homebrew.fake = true + ServicesManager.useFake() Log.info("Applying fake Valet domain interactor...") ValetInteractor.useFake() } diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 53ae7d2..0601028 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -21,13 +21,14 @@ class FakeServicesManager: ServicesManager { super.init() Log.warn("A fake services manager is being used, so Homebrew formula resolver is set to act in fake mode.") - Log.warn("If you do not want this behaviour, never instantiate FakeServicesManager!") + Log.warn("If you do not want this behaviour, do not make use of a `FakeServicesManager`!") self.fixedFormulae = formulae self.fixedStatus = status - self.services = self.formulae.map { + self.serviceWrappers = self.formulae.map { let wrapper = ServiceWrapper(formula: $0) + wrapper.isBusy = (status == .loading) wrapper.service = HomebrewService.dummy(named: $0.name, enabled: true) return wrapper } @@ -38,4 +39,16 @@ class FakeServicesManager: ServicesManager { return HomebrewFormula.init(formula, elevated: false) } } + + override func updateServices() async { + await delay(seconds: 0.3) + + for formula in self.serviceWrappers { + formula.service?.running = true + formula.isBusy = false + } + + print("Sending the update!") + broadcastServicesUpdated() + } } diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index da2fad4..b8d124b 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -13,20 +13,31 @@ class ServicesManager: ObservableObject { @ObservedObject static var shared: ServicesManager = ValetServicesManager() - @Published var services = [ServiceWrapper]() + @Published var serviceWrappers = [ServiceWrapper]() + public static func useFake() { + ServicesManager.shared = FakeServicesManager.init( + formulae: ["php", "nginx", "dnsmasq"], + status: .loading + ) + } + + /** + The order of services is important, so easy access is accomplished + without much fanfare through subscripting. + */ subscript(name: String) -> ServiceWrapper? { - return self.services.first { wrapper in + return self.serviceWrappers.first { wrapper in wrapper.name == name } } public var statusMessage: String { - if self.services.isEmpty { + if self.serviceWrappers.isEmpty { return "Loading..." } - let statuses = self.services[0...2].map { $0.status } + let statuses = self.serviceWrappers[0...2].map { $0.status } if statuses.contains(.loading) { return "Determining Valet status..." } @@ -41,11 +52,11 @@ class ServicesManager: ObservableObject { } public var statusColor: Color { - if self.services.isEmpty { + if self.serviceWrappers.isEmpty { return .yellow } - let statuses = self.services[0...2].map { $0.status } + let statuses = self.serviceWrappers[0...2].map { $0.status } if statuses.contains(.loading) { return .orange } @@ -61,14 +72,29 @@ class ServicesManager: ObservableObject { @available(*, deprecated, message: "Use a more specific method instead") static func loadHomebrewServices() { - print(self.shared) + // print(self.shared) print("This method must be updated") } - public func updateServices() { + public func updateServices() async { fatalError("Must be implemented in child class") } + public func broadcastServicesUpdated() { + Task { @MainActor in + self.serviceWrappers.forEach { wrapper in + guard let service = wrapper.service else { + return + } + + Log.perf("\(service.name): \(wrapper.status)") + wrapper.objectWillChange.send() + } + + self.objectWillChange.send() + } + } + var formulae: [HomebrewFormula] { var formulae = [ Homebrew.Formulae.php, @@ -88,7 +114,7 @@ class ServicesManager: ObservableObject { init() { Log.info("The services manager will determine which Valet services exist on this system.") - services = formulae.map { + serviceWrappers = formulae.map { ServiceWrapper(formula: $0) } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 6319169..9ffff96 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -13,10 +13,10 @@ class ValetServicesManager: ServicesManager { super.init() // Load the initial services state - self.updateServices() + Task { await self.updateServices() } } - override func updateServices() { + override func updateServices() async { // TODO } } diff --git a/phpmon/Domain/Menu/MainMenu+Async.swift b/phpmon/Domain/Menu/MainMenu+Async.swift index 261f25d..ca9b766 100644 --- a/phpmon/Domain/Menu/MainMenu+Async.swift +++ b/phpmon/Domain/Menu/MainMenu+Async.swift @@ -74,7 +74,7 @@ extension MainMenu { } if behaviours.contains(.broadcastServicesUpdate) { - Task { await ServicesManager.loadHomebrewServices() } + Task { await ServicesManager.shared.updateServices() } } if error != nil { diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 2698049..c5a9571 100644 --- a/phpmon/Domain/Menu/MainMenu+Startup.swift +++ b/phpmon/Domain/Menu/MainMenu+Startup.swift @@ -96,7 +96,7 @@ extension MainMenu { Valet.notifyAboutUnsupportedTLD() // Find out which services are active - Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.") + Log.info("The services manager knows about \(ServicesManager.shared.serviceWrappers.count) services.") // Start the background refresh timer startSharedTimer() diff --git a/phpmon/Domain/Menu/MainMenu.swift b/phpmon/Domain/Menu/MainMenu.swift index 8ec944e..727e54a 100644 --- a/phpmon/Domain/Menu/MainMenu.swift +++ b/phpmon/Domain/Menu/MainMenu.swift @@ -94,7 +94,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate self.refreshActiveInstallation() self.refreshIcon() self.rebuild() - await ServicesManager.loadHomebrewServices() + await ServicesManager.shared.updateServices() Log.perf("The menu has been reloaded!") } } @@ -184,7 +184,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate // Make sure the shortcut key does not trigger this when the menu is open App.shared.shortcutHotkey?.isPaused = true Task { // Reload Homebrew services information asynchronously - await ServicesManager.loadHomebrewServices() + await ServicesManager.shared.updateServices() } } diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 4fd7c23..b1b1320 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -41,14 +41,14 @@ struct ServicesView: View { init(manager: ServicesManager, perRow: Int, height: CGFloat? = nil) { self.manager = manager self.perRow = perRow - self.chunkCount = manager.services.chunked(by: perRow).count + self.chunkCount = manager.serviceWrappers.chunked(by: perRow).count self.height = CGFloat((50 * chunkCount) + (5 * perRow)) } var body: some View { VStack { VStack(alignment: .leading) { - ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in + ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in HStack { ForEach(chunk) { service in ServiceView(service: service).frame(minWidth: 70) @@ -150,9 +150,16 @@ struct ServicesView_Previews: PreviewProvider { static var previews: some View { ServicesView(manager: FakeServicesManager( formulae: ["php", "nginx", "dnsmasq"], - status: .active + status: .loading ), perRow: 4) .frame(width: 330.0) .previewDisplayName("Loading") + + ServicesView(manager: FakeServicesManager( + formulae: ["php", "nginx", "dnsmasq"], + status: .active + ), perRow: 4) + .frame(width: 330.0) + .previewDisplayName("Active") } }