diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index d0f44a2..5f43860 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -143,10 +143,10 @@ C4570C3B28FC355300D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C4570C3C28FC355400D18420 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C459B4BD27F6093700E9B4B4 /* nginx-proxy.test in Resources */ = {isa = PBXBuildFile; fileRef = C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */; }; - C45B9149295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; - C45B914A295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; - C45B914B295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; - C45B914C295607F400F4EC78 /* ServiceWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* ServiceWrapper.swift */; }; + C45B9149295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; + C45B914A295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; + C45B914B295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; + C45B914C295607F400F4EC78 /* Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B9148295607F400F4EC78 /* Service.swift */; }; C45B914E295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; C45B914F295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; C45B9150295608E300F4EC78 /* ValetServicesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45B914D295608E300F4EC78 /* ValetServicesManager.swift */; }; @@ -802,7 +802,7 @@ C44F868D2835BD8D005C353A /* phpmon-config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "phpmon-config.json"; sourceTree = ""; }; C450C8C528C919EC002A2B4B /* PreferenceName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceName.swift; sourceTree = ""; }; C459B4BC27F6093700E9B4B4 /* nginx-proxy.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "nginx-proxy.test"; sourceTree = ""; }; - C45B9148295607F400F4EC78 /* ServiceWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceWrapper.swift; sourceTree = ""; }; + C45B9148295607F400F4EC78 /* Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Service.swift; sourceTree = ""; }; C45B914D295608E300F4EC78 /* ValetServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetServicesManager.swift; sourceTree = ""; }; C45B91522956123A00F4EC78 /* FakeServicesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeServicesManager.swift; sourceTree = ""; }; C45E2A76291992DA005C7CFD /* FeatureTestCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTestCase.swift; sourceTree = ""; }; @@ -1298,7 +1298,7 @@ C45B9147295607E200F4EC78 /* Services */ = { isa = PBXGroup; children = ( - C45B9148295607F400F4EC78 /* ServiceWrapper.swift */, + C45B9148295607F400F4EC78 /* Service.swift */, C45E76132854A65300B4FE0C /* ServicesManager.swift */, C45B914D295608E300F4EC78 /* ValetServicesManager.swift */, C45B91522956123A00F4EC78 /* FakeServicesManager.swift */, @@ -1978,7 +1978,7 @@ 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */, C4C8900328F0E28800CE5E97 /* FileSystemProtocol.swift in Sources */, C43603A0275E67610028EFC6 /* AppDelegate+Notifications.swift in Sources */, - C45B9149295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B9149295607F400F4EC78 /* Service.swift in Sources */, 5489625828312FAD004F647A /* CreatedFromFile.swift in Sources */, C4068CA727B07A1300544CD5 /* SelectPreferenceView.swift in Sources */, C45B91532956123A00F4EC78 /* FakeServicesManager.swift in Sources */, @@ -2256,7 +2256,7 @@ C471E7F228F9BAC70021E251 /* PhpEnv.swift in Sources */, C471E7E628F9BAC20021E251 /* Process.swift in Sources */, C471E81928F9BAE80021E251 /* NSMenuItemExtension.swift in Sources */, - C45B914B295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B914B295607F400F4EC78 /* Service.swift in Sources */, C471E7D928F9BA8F0021E251 /* TestableShell.swift in Sources */, C471E81428F9BAE80021E251 /* NSWindowExtension.swift in Sources */, C471E7D328F9BA8F0021E251 /* ActiveShell.swift in Sources */, @@ -2309,7 +2309,7 @@ C471E8A528F9BB8F0021E251 /* AppDelegate+InterApp.swift in Sources */, C471E8A628F9BB8F0021E251 /* App.swift in Sources */, C471E8A728F9BB8F0021E251 /* App+ActivationPolicy.swift in Sources */, - C45B914C295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B914C295607F400F4EC78 /* Service.swift in Sources */, C471E8A828F9BB8F0021E251 /* App+GlobalHotkey.swift in Sources */, C471E8A928F9BB8F0021E251 /* InterAppHandler.swift in Sources */, C471E8AA28F9BB8F0021E251 /* Startup.swift in Sources */, @@ -2501,7 +2501,7 @@ C4AF9F7B2754499000D44ED0 /* Valet.swift in Sources */, C4C1019C27C65C6F001FACC2 /* Process.swift in Sources */, C4F780C025D80B6E000DBC97 /* Startup.swift in Sources */, - C45B914A295607F400F4EC78 /* ServiceWrapper.swift in Sources */, + C45B914A295607F400F4EC78 /* Service.swift in Sources */, C4C0E8E327F88B13002D32A9 /* ValetDomainScanner.swift in Sources */, C4CCBA6D275C567B008C7055 /* PMWindowController.swift in Sources */, C4B5635F276AB09000F12CCB /* VersionExtractor.swift in Sources */, diff --git a/phpmon/Common/PHP/Homebrew/HomebrewService.swift b/phpmon/Common/PHP/Homebrew/HomebrewService.swift index da287d5..028dfe0 100644 --- a/phpmon/Common/PHP/Homebrew/HomebrewService.swift +++ b/phpmon/Common/PHP/Homebrew/HomebrewService.swift @@ -8,16 +8,16 @@ import Foundation -class HomebrewService: Decodable, Equatable, Hashable { +final class HomebrewService: Sendable, Decodable { let name: String let service_name: String - var running: Bool - var loaded: Bool - var pid: Int? - var user: String? - var status: String? - var log_path: String? - var error_log_path: String? + let running: Bool + let loaded: Bool + let pid: Int? + let user: String? + let status: String? + let log_path: String? + let error_log_path: String? init( name: String, @@ -57,17 +57,4 @@ class HomebrewService: Decodable, Equatable, Hashable { error_log_path: nil ) } - - public func hash(into hasher: inout Hasher) { - hasher.combine(name) - hasher.combine(service_name) - hasher.combine(pid) - hasher.combine(status) - hasher.combine(running) - hasher.combine(user) - } - - static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool { - return lhs.hashValue == rhs.hashValue - } } diff --git a/phpmon/Domain/App/Services/FakeServicesManager.swift b/phpmon/Domain/App/Services/FakeServicesManager.swift index 4cb733f..486ce8c 100644 --- a/phpmon/Domain/App/Services/FakeServicesManager.swift +++ b/phpmon/Domain/App/Services/FakeServicesManager.swift @@ -26,13 +26,15 @@ class FakeServicesManager: ServicesManager { self.fixedFormulae = formulae self.fixedStatus = status - self.serviceWrappers = self.formulae.map { - let wrapper = ServiceWrapper( + self.services = self.formulae.map { + let wrapper = Service( formula: $0, service: HomebrewService.dummy(named: $0.name, enabled: true) ) return wrapper } + + self.firstRunComplete = true } override var formulae: [HomebrewFormula] { diff --git a/phpmon/Domain/App/Services/ServiceWrapper.swift b/phpmon/Domain/App/Services/Service.swift similarity index 74% rename from phpmon/Domain/App/Services/ServiceWrapper.swift rename to phpmon/Domain/App/Services/Service.swift index deafaf4..cf9b021 100644 --- a/phpmon/Domain/App/Services/ServiceWrapper.swift +++ b/phpmon/Domain/App/Services/Service.swift @@ -17,11 +17,7 @@ public enum ServiceStatus: String { case missing } -/** - Service wrapper, that contains the Homebrew JSON output (if determined) and the formula. - This helps the app determine whether a service should run as an administrator or not. - */ -public struct ServiceWrapper: Hashable { +public struct Service: Hashable { var formula: HomebrewFormula var status: ServiceStatus = .missing @@ -37,7 +33,7 @@ public struct ServiceWrapper: Hashable { } } - public static func == (lhs: ServiceWrapper, rhs: ServiceWrapper) -> Bool { + public static func == (lhs: Service, rhs: Service) -> Bool { return lhs.hashValue == rhs.hashValue } diff --git a/phpmon/Domain/App/Services/ServicesManager.swift b/phpmon/Domain/App/Services/ServicesManager.swift index c5f6f60..ae20f7c 100644 --- a/phpmon/Domain/App/Services/ServicesManager.swift +++ b/phpmon/Domain/App/Services/ServicesManager.swift @@ -13,7 +13,9 @@ class ServicesManager: ObservableObject { @ObservedObject static var shared: ServicesManager = ValetServicesManager() - @Published var serviceWrappers = [ServiceWrapper]() + @Published var services = [Service]() + + @Published var firstRunComplete: Bool = false public static func useFake() { ServicesManager.shared = FakeServicesManager.init( @@ -26,18 +28,18 @@ class ServicesManager: ObservableObject { The order of services is important, so easy access is accomplished without much fanfare through subscripting. */ - subscript(name: String) -> ServiceWrapper? { - return self.serviceWrappers.first { wrapper in + subscript(name: String) -> Service? { + return self.services.first { wrapper in wrapper.name == name } } public var statusMessage: String { - if self.serviceWrappers.isEmpty { + if self.services.isEmpty { return "Loading..." } - let statuses = self.serviceWrappers[0...2].map { $0.status } + let statuses = self.services[0...2].map { $0.status } if statuses.contains(.missing) { return "A key service is not installed." @@ -50,11 +52,11 @@ class ServicesManager: ObservableObject { } public var statusColor: Color { - if self.serviceWrappers.isEmpty { + if self.services.isEmpty { return .yellow } - let statuses = self.serviceWrappers[0...2].map { $0.status } + let statuses = self.services[0...2].map { $0.status } if statuses.contains(.missing) { return .red } @@ -109,8 +111,8 @@ class ServicesManager: ObservableObject { init() { Log.info("The services manager will determine which Valet services exist on this system.") - serviceWrappers = formulae.map { - ServiceWrapper(formula: $0) + services = formulae.map { + Service(formula: $0) } } } diff --git a/phpmon/Domain/App/Services/ValetServicesManager.swift b/phpmon/Domain/App/Services/ValetServicesManager.swift index 3fad3bf..01a7085 100644 --- a/phpmon/Domain/App/Services/ValetServicesManager.swift +++ b/phpmon/Domain/App/Services/ValetServicesManager.swift @@ -13,7 +13,10 @@ class ValetServicesManager: ServicesManager { super.init() // Load the initial services state - Task { await self.reloadServicesStatus() } + Task { + await self.reloadServicesStatus() + firstRunComplete = true + } } /** @@ -58,18 +61,22 @@ class ValetServicesManager: ServicesManager { .filter({ return userServiceNames.contains($0.name) }) } + // Ensure that Homebrew services' output is stored self.homebrewServices = [] - for await services in group { homebrewServices.append(contentsOf: services) } + // Dispatch the update of the new service wrappers Task { @MainActor in // Ensure both commands complete (but run concurrently) - serviceWrappers = formulae.map { formula in - ServiceWrapper(formula: formula, service: homebrewServices.first(where: { service in - service.name == formula.name - })) + services = formulae.map { formula in + Service( + formula: formula, + service: homebrewServices.first(where: { service in + service.name == formula.name + }) + ) } // Broadcast that all services have been updated diff --git a/phpmon/Domain/Menu/MainMenu+Startup.swift b/phpmon/Domain/Menu/MainMenu+Startup.swift index 4f54592..487d259 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.serviceWrappers.count) services.") + Log.info("The services manager knows about \(ServicesManager.shared.services.count) services.") // Start the background refresh timer startSharedTimer() diff --git a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift index 0a547a6..52170a4 100644 --- a/phpmon/Domain/SwiftUI/Menu/ServicesView.swift +++ b/phpmon/Domain/SwiftUI/Menu/ServicesView.swift @@ -44,7 +44,7 @@ struct ServicesView: View { init(manager: ServicesManager, perRow: Int) { self.manager = manager self.perRow = perRow - self.rowCount = manager.serviceWrappers.chunked(by: perRow).count + self.rowCount = manager.services.chunked(by: perRow).count self.height = CGFloat( (rowHeight * rowCount) + ((rowCount - 1) * rowSpacing) @@ -55,7 +55,7 @@ struct ServicesView: View { var body: some View { VStack(spacing: 0) { VStack(alignment: .leading, spacing: CGFloat(self.rowSpacing)) { - ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in + ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in HStack { ForEach(chunk, id: \.self) { service in ServiceView(service: service) @@ -86,7 +86,7 @@ struct ServicesView: View { } struct ServiceView: View { - var service: ServiceWrapper + var service: Service @State var isBusy: Bool = false var body: some View { @@ -117,7 +117,6 @@ struct ServiceView: View { Text("?") } .focusable(false) - // .buttonStyle(BlueButton()) .frame(minWidth: 70, alignment: .center) } if service.status == .active || service.status == .inactive { @@ -134,7 +133,9 @@ struct ServiceView: View { .resizable() .frame(width: 12.0, height: 12.0) .foregroundColor( - service.status == .active ? Color("IconColorGreen") : Color("IconColorRed") + service.status == .active + ? Color("IconColorGreen") + : Color("IconColorRed") ) }.frame(width: 25, height: 25) }