1
0
mirror of https://github.com/nicoverbruggen/phpmon.git synced 2025-08-07 20:10:08 +02:00

🏗 WIP: Use objectWillChange.send()

This commit is contained in:
2023-01-03 16:11:55 +01:00
parent 296bc486c4
commit 3bcf52bf0a
11 changed files with 109 additions and 32 deletions

View File

@ -91,7 +91,7 @@
</CommandLineArgument> </CommandLineArgument>
<CommandLineArgument <CommandLineArgument
argument = "--configuration:~/.phpmon_fconf_working.json" argument = "--configuration:~/.phpmon_fconf_working.json"
isEnabled = "NO"> isEnabled = "YES">
</CommandLineArgument> </CommandLineArgument>
<CommandLineArgument <CommandLineArgument
argument = "--configuration:~/.phpmon_fconf_broken.json" argument = "--configuration:~/.phpmon_fconf_broken.json"

View File

@ -71,7 +71,7 @@ class Actions {
"services stop \(name)", "services stop \(name)",
sudo: ServicesManager.shared[name]?.formula.elevated ?? false sudo: ServicesManager.shared[name]?.formula.elevated ?? false
) )
await ServicesManager.loadHomebrewServices() await ServicesManager.shared.updateServices()
} }
public static func startService(name: String) async { public static func startService(name: String) async {
@ -79,7 +79,7 @@ class Actions {
"services start \(name)", "services start \(name)",
sudo: ServicesManager.shared[name]?.formula.elevated ?? false sudo: ServicesManager.shared[name]?.formula.elevated ?? false
) )
await ServicesManager.loadHomebrewServices() await ServicesManager.shared.updateServices()
} }
// MARK: - Finding Config Files // MARK: - Finding Config Files

View File

@ -8,21 +8,43 @@
import Foundation import Foundation
struct HomebrewService: Decodable, Equatable, Hashable { class HomebrewService: Decodable, Equatable, Hashable {
let name: String let name: String
let service_name: String let service_name: String
let running: Bool var running: Bool
let loaded: Bool var loaded: Bool
let pid: Int? var pid: Int?
let user: String? var user: String?
let status: String? var status: String?
let log_path: String? var log_path: String?
let error_log_path: String? var error_log_path: String?
init(
name: String,
service_name: String,
running: Bool,
loaded: Bool,
pid: Int? = nil,
user: String? = nil,
status: String? = nil,
log_path: String? = nil,
error_log_path: String? = nil
) {
self.name = name
self.service_name = service_name
self.running = running
self.loaded = loaded
self.pid = pid
self.user = user
self.status = status
self.log_path = log_path
self.error_log_path = error_log_path
}
/** /**
Dummy data for preview purposes. Dummy data for preview purposes.
*/ */
public static func dummy(named service: String, enabled: Bool) -> Self { public static func dummy(named service: String, enabled: Bool) -> HomebrewService {
return HomebrewService( return HomebrewService(
name: service, name: service,
service_name: service, service_name: service,
@ -41,4 +63,10 @@ struct HomebrewService: Decodable, Equatable, Hashable {
hasher.combine(service_name) hasher.combine(service_name)
hasher.combine(pid) hasher.combine(pid)
} }
static func == (lhs: HomebrewService, rhs: HomebrewService) -> Bool {
return lhs.name == rhs.name
&& lhs.pid == rhs.pid
&& lhs.status == rhs.status
}
} }

View File

@ -26,6 +26,9 @@ public struct TestableConfiguration: Codable {
ActiveCommand.useTestable(commandOutput) ActiveCommand.useTestable(commandOutput)
Log.info("Applying fake scanner...") Log.info("Applying fake scanner...")
ValetScanner.useFake() ValetScanner.useFake()
Log.info("Applying fake services manager...")
Homebrew.fake = true
ServicesManager.useFake()
Log.info("Applying fake Valet domain interactor...") Log.info("Applying fake Valet domain interactor...")
ValetInteractor.useFake() ValetInteractor.useFake()
} }

View File

@ -21,13 +21,14 @@ class FakeServicesManager: ServicesManager {
super.init() super.init()
Log.warn("A fake services manager is being used, so Homebrew formula resolver is set to act in fake mode.") 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.fixedFormulae = formulae
self.fixedStatus = status self.fixedStatus = status
self.services = self.formulae.map { self.serviceWrappers = self.formulae.map {
let wrapper = ServiceWrapper(formula: $0) let wrapper = ServiceWrapper(formula: $0)
wrapper.isBusy = (status == .loading)
wrapper.service = HomebrewService.dummy(named: $0.name, enabled: true) wrapper.service = HomebrewService.dummy(named: $0.name, enabled: true)
return wrapper return wrapper
} }
@ -38,4 +39,16 @@ class FakeServicesManager: ServicesManager {
return HomebrewFormula.init(formula, elevated: false) 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()
}
} }

View File

@ -13,20 +13,31 @@ class ServicesManager: ObservableObject {
@ObservedObject static var shared: ServicesManager = ValetServicesManager() @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? { subscript(name: String) -> ServiceWrapper? {
return self.services.first { wrapper in return self.serviceWrappers.first { wrapper in
wrapper.name == name wrapper.name == name
} }
} }
public var statusMessage: String { public var statusMessage: String {
if self.services.isEmpty { if self.serviceWrappers.isEmpty {
return "Loading..." return "Loading..."
} }
let statuses = self.services[0...2].map { $0.status } let statuses = self.serviceWrappers[0...2].map { $0.status }
if statuses.contains(.loading) { if statuses.contains(.loading) {
return "Determining Valet status..." return "Determining Valet status..."
} }
@ -41,11 +52,11 @@ class ServicesManager: ObservableObject {
} }
public var statusColor: Color { public var statusColor: Color {
if self.services.isEmpty { if self.serviceWrappers.isEmpty {
return .yellow return .yellow
} }
let statuses = self.services[0...2].map { $0.status } let statuses = self.serviceWrappers[0...2].map { $0.status }
if statuses.contains(.loading) { if statuses.contains(.loading) {
return .orange return .orange
} }
@ -61,14 +72,29 @@ class ServicesManager: ObservableObject {
@available(*, deprecated, message: "Use a more specific method instead") @available(*, deprecated, message: "Use a more specific method instead")
static func loadHomebrewServices() { static func loadHomebrewServices() {
print(self.shared) // print(self.shared)
print("This method must be updated") print("This method must be updated")
} }
public func updateServices() { public func updateServices() async {
fatalError("Must be implemented in child class") 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: [HomebrewFormula] {
var formulae = [ var formulae = [
Homebrew.Formulae.php, Homebrew.Formulae.php,
@ -88,7 +114,7 @@ class ServicesManager: ObservableObject {
init() { init() {
Log.info("The services manager will determine which Valet services exist on this system.") Log.info("The services manager will determine which Valet services exist on this system.")
services = formulae.map { serviceWrappers = formulae.map {
ServiceWrapper(formula: $0) ServiceWrapper(formula: $0)
} }
} }

View File

@ -13,10 +13,10 @@ class ValetServicesManager: ServicesManager {
super.init() super.init()
// Load the initial services state // Load the initial services state
self.updateServices() Task { await self.updateServices() }
} }
override func updateServices() { override func updateServices() async {
// TODO // TODO
} }
} }

View File

@ -74,7 +74,7 @@ extension MainMenu {
} }
if behaviours.contains(.broadcastServicesUpdate) { if behaviours.contains(.broadcastServicesUpdate) {
Task { await ServicesManager.loadHomebrewServices() } Task { await ServicesManager.shared.updateServices() }
} }
if error != nil { if error != nil {

View File

@ -96,7 +96,7 @@ extension MainMenu {
Valet.notifyAboutUnsupportedTLD() Valet.notifyAboutUnsupportedTLD()
// Find out which services are active // 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 // Start the background refresh timer
startSharedTimer() startSharedTimer()

View File

@ -94,7 +94,7 @@ class MainMenu: NSObject, NSWindowDelegate, NSMenuDelegate, PhpSwitcherDelegate
self.refreshActiveInstallation() self.refreshActiveInstallation()
self.refreshIcon() self.refreshIcon()
self.rebuild() self.rebuild()
await ServicesManager.loadHomebrewServices() await ServicesManager.shared.updateServices()
Log.perf("The menu has been reloaded!") 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 // Make sure the shortcut key does not trigger this when the menu is open
App.shared.shortcutHotkey?.isPaused = true App.shared.shortcutHotkey?.isPaused = true
Task { // Reload Homebrew services information asynchronously Task { // Reload Homebrew services information asynchronously
await ServicesManager.loadHomebrewServices() await ServicesManager.shared.updateServices()
} }
} }

View File

@ -41,14 +41,14 @@ struct ServicesView: View {
init(manager: ServicesManager, perRow: Int, height: CGFloat? = nil) { init(manager: ServicesManager, perRow: Int, height: CGFloat? = nil) {
self.manager = manager self.manager = manager
self.perRow = perRow 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)) self.height = CGFloat((50 * chunkCount) + (5 * perRow))
} }
var body: some View { var body: some View {
VStack { VStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
ForEach(manager.services.chunked(by: perRow), id: \.self) { chunk in ForEach(manager.serviceWrappers.chunked(by: perRow), id: \.self) { chunk in
HStack { HStack {
ForEach(chunk) { service in ForEach(chunk) { service in
ServiceView(service: service).frame(minWidth: 70) ServiceView(service: service).frame(minWidth: 70)
@ -150,9 +150,16 @@ struct ServicesView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ServicesView(manager: FakeServicesManager( ServicesView(manager: FakeServicesManager(
formulae: ["php", "nginx", "dnsmasq"], formulae: ["php", "nginx", "dnsmasq"],
status: .active status: .loading
), perRow: 4) ), perRow: 4)
.frame(width: 330.0) .frame(width: 330.0)
.previewDisplayName("Loading") .previewDisplayName("Loading")
ServicesView(manager: FakeServicesManager(
formulae: ["php", "nginx", "dnsmasq"],
status: .active
), perRow: 4)
.frame(width: 330.0)
.previewDisplayName("Active")
} }
} }