mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
feat: added an option to use a HTTP method for connectivity check (#2971)
This commit is contained in:
@@ -208,7 +208,7 @@ public class Network: Module {
|
||||
self.settingsView.usageResetCallback = { [weak self] in
|
||||
self?.setUsageReset()
|
||||
}
|
||||
self.settingsView.ICMPHostCallback = { [weak self] isDisabled in
|
||||
self.settingsView.connectivityHostCallback = { [weak self] isDisabled in
|
||||
if isDisabled {
|
||||
self?.popupView.resetConnectivityView()
|
||||
self?.connectivityCallback(Network_Connectivity(status: false))
|
||||
|
||||
@@ -696,13 +696,26 @@ internal class ConnectivityReader: Reader<Network_Connectivity> {
|
||||
private let identifier = UInt16.random(in: 0..<UInt16.max)
|
||||
private var fingerprint: UUID = UUID()
|
||||
|
||||
private var host: String {
|
||||
private var ICMPHost: String {
|
||||
Store.shared.string(key: "Network_ICMPHost", defaultValue: "1.1.1.1")
|
||||
}
|
||||
private var HTTPHost: String {
|
||||
Store.shared.string(key: "Network_HTTPHost", defaultValue: "https://google.com")
|
||||
}
|
||||
|
||||
private var lastHost: String = ""
|
||||
private var addr: Data? = nil
|
||||
private let timeout: TimeInterval = 5
|
||||
|
||||
public enum ConnectivityMode: String {
|
||||
case icmp
|
||||
case http
|
||||
}
|
||||
|
||||
private var connectivityMode: ConnectivityMode {
|
||||
ConnectivityMode(rawValue: Store.shared.string(key: "Network_connectivityMode", defaultValue: "icmp")) ?? .icmp
|
||||
}
|
||||
|
||||
private var socket: CFSocket?
|
||||
private var socketSource: CFRunLoopSource?
|
||||
|
||||
@@ -786,18 +799,77 @@ internal class ConnectivityReader: Reader<Network_Connectivity> {
|
||||
}
|
||||
|
||||
override func read() {
|
||||
guard !self.host.isEmpty else {
|
||||
if self.socket != nil {
|
||||
self.closeConn()
|
||||
if self.connectivityMode == .http {
|
||||
self.httpCheck()
|
||||
} else {
|
||||
guard !self.ICMPHost.isEmpty else {
|
||||
if self.socket != nil {
|
||||
self.closeConn()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
self.icmpCheck()
|
||||
}
|
||||
|
||||
if let v = self.status {
|
||||
self.wrapper.status = v
|
||||
if let l = self.latency {
|
||||
self.wrapper.latency = l
|
||||
}
|
||||
if let j = self.jitter {
|
||||
self.wrapper.jitter = j
|
||||
}
|
||||
self.callback(self.wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
private func httpCheck() {
|
||||
guard !self.isPinging else { return }
|
||||
self.isPinging = true
|
||||
|
||||
let urlString = self.HTTPHost.hasPrefix("http://") || self.HTTPHost.hasPrefix("https://") ? self.HTTPHost : "https://\(self.HTTPHost)"
|
||||
guard let url = URL(string: urlString) else {
|
||||
self.status = false
|
||||
self.isPinging = false
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: self.timeout)
|
||||
request.httpMethod = "HEAD"
|
||||
|
||||
let startTime = DispatchTime.now()
|
||||
let task = URLSession.shared.dataTask(with: request) { _, response, error in
|
||||
let endTime = DispatchTime.now()
|
||||
let elapsed = Double(endTime.uptimeNanoseconds - startTime.uptimeNanoseconds) / 1_000_000
|
||||
|
||||
self.latency = elapsed
|
||||
if let prev = self.previousLatency {
|
||||
let d = abs(elapsed - prev)
|
||||
if self.jitter == nil {
|
||||
self.jitter = d
|
||||
} else {
|
||||
self.jitter! += (d - self.jitter!) / 16.0
|
||||
}
|
||||
}
|
||||
self.previousLatency = elapsed
|
||||
|
||||
if let http = response as? HTTPURLResponse {
|
||||
self.status = (200...399).contains(http.statusCode) && error == nil
|
||||
} else {
|
||||
self.status = false
|
||||
}
|
||||
self.isPinging = false
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
private func icmpCheck() {
|
||||
if self.socket == nil {
|
||||
self.prepare()
|
||||
}
|
||||
|
||||
if self.lastHost != self.host {
|
||||
if self.lastHost != self.ICMPHost {
|
||||
self.addr = self.resolve()
|
||||
}
|
||||
|
||||
@@ -813,17 +885,6 @@ internal class ConnectivityReader: Reader<Network_Connectivity> {
|
||||
if error != .success {
|
||||
self.socketCallback(data: nil, error: error)
|
||||
}
|
||||
|
||||
if let v = self.status {
|
||||
self.wrapper.status = v
|
||||
if let l = self.latency {
|
||||
self.wrapper.latency = l
|
||||
}
|
||||
if let j = self.jitter {
|
||||
self.wrapper.jitter = j
|
||||
}
|
||||
self.callback(self.wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func timeoutCallback() {
|
||||
@@ -966,9 +1027,9 @@ internal class ConnectivityReader: Reader<Network_Connectivity> {
|
||||
}
|
||||
|
||||
private func resolve() -> Data? {
|
||||
self.lastHost = self.host
|
||||
self.lastHost = self.ICMPHost
|
||||
var streamError = CFStreamError()
|
||||
let cfhost = CFHostCreateWithName(nil, self.host as CFString).takeRetainedValue()
|
||||
let cfhost = CFHostCreateWithName(nil, self.ICMPHost as CFString).takeRetainedValue()
|
||||
let status = CFHostStartInfoResolution(cfhost, .addresses, &streamError)
|
||||
guard status else { return nil }
|
||||
var success: DarwinBoolean = false
|
||||
|
||||
@@ -65,8 +65,10 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
private var widgetActivationThresholdState: Bool = false
|
||||
private var widgetActivationThreshold: Int = 0
|
||||
private var widgetActivationThresholdSize: SizeUnit = .MB
|
||||
private var ICMPHost: String = "1.1.1.1"
|
||||
private var updateICMPIntervalValue: Int = 1
|
||||
private var connectivityICMPHost: String = "1.1.1.1"
|
||||
private var connectivityHTTPHost: String = "https://google.com"
|
||||
private var updateConnectivityIntervalValue: Int = 1
|
||||
private var connectivityMode: ConnectivityReader.ConnectivityMode = .icmp
|
||||
private var publicIPState: Bool = true
|
||||
private var publicIPRefreshInterval: String = "never"
|
||||
private var baseValue: String = "byte"
|
||||
@@ -75,7 +77,7 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
public var callback: (() -> Void) = {}
|
||||
public var callbackWhenUpdateNumberOfProcesses: (() -> Void) = {}
|
||||
public var usageResetCallback: (() -> Void) = {}
|
||||
public var ICMPHostCallback: ((_ newState: Bool) -> Void) = { _ in }
|
||||
public var connectivityHostCallback: ((_ newState: Bool) -> Void) = { _ in }
|
||||
public var setInterval: ((_ value: Int) -> Void) = {_ in }
|
||||
public var publicIPRefreshIntervalCallback: (() -> Void) = {}
|
||||
|
||||
@@ -94,6 +96,8 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
return false
|
||||
}
|
||||
|
||||
private var connectivityHostField: NSTextField? = nil
|
||||
|
||||
public init(_ module: ModuleType) {
|
||||
self.title = module.stringValue
|
||||
self.numberOfProcesses = Store.shared.int(key: "\(self.title)_processes", defaultValue: self.numberOfProcesses)
|
||||
@@ -103,8 +107,10 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
self.widgetActivationThresholdState = Store.shared.bool(key: "\(self.title)_widgetActivationThresholdState", defaultValue: self.widgetActivationThresholdState)
|
||||
self.widgetActivationThreshold = Store.shared.int(key: "\(self.title)_widgetActivationThreshold", defaultValue: self.widgetActivationThreshold)
|
||||
self.widgetActivationThresholdSize = SizeUnit.fromString(Store.shared.string(key: "\(self.title)_widgetActivationThresholdSize", defaultValue: self.widgetActivationThresholdSize.key))
|
||||
self.ICMPHost = Store.shared.string(key: "\(self.title)_ICMPHost", defaultValue: self.ICMPHost)
|
||||
self.updateICMPIntervalValue = Store.shared.int(key: "\(self.title)_updateICMPInterval", defaultValue: self.updateICMPIntervalValue)
|
||||
self.connectivityICMPHost = Store.shared.string(key: "\(self.title)_ICMPHost", defaultValue: self.connectivityICMPHost)
|
||||
self.connectivityHTTPHost = Store.shared.string(key: "\(self.title)_HTTPHost", defaultValue: self.connectivityHTTPHost)
|
||||
self.updateConnectivityIntervalValue = Store.shared.int(key: "\(self.title)_updateICMPInterval", defaultValue: self.updateConnectivityIntervalValue)
|
||||
self.connectivityMode = ConnectivityReader.ConnectivityMode(rawValue: Store.shared.string(key: "\(self.title)_connectivityMode", defaultValue: "icmp")) ?? .icmp
|
||||
self.publicIPState = Store.shared.bool(key: "\(self.title)_publicIP", defaultValue: self.publicIPState)
|
||||
self.publicIPRefreshInterval = Store.shared.string(key: "\(self.title)_publicIPRefreshInterval", defaultValue: self.publicIPRefreshInterval)
|
||||
self.baseValue = Store.shared.string(key: "\(self.title)_base", defaultValue: self.baseValue)
|
||||
@@ -214,28 +220,29 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
self.addArrangedSubview(self.widgetThresholdSection!)
|
||||
self.widgetThresholdSection?.setRowVisibility(1, newState: self.widgetActivationThresholdState)
|
||||
|
||||
let valueField: NSTextField = NSTextField()
|
||||
valueField.widthAnchor.constraint(equalToConstant: 250).isActive = true
|
||||
valueField.font = NSFont.systemFont(ofSize: 12, weight: .regular)
|
||||
valueField.textColor = .textColor
|
||||
valueField.isEditable = true
|
||||
valueField.isSelectable = true
|
||||
valueField.usesSingleLineMode = true
|
||||
valueField.maximumNumberOfLines = 1
|
||||
valueField.focusRingType = .none
|
||||
valueField.stringValue = self.ICMPHost
|
||||
valueField.delegate = self
|
||||
valueField.placeholderString = localizedString("Leave empty to disable the check")
|
||||
var connectivityHost = self.connectivityICMPHost
|
||||
if self.connectivityMode == .http {
|
||||
connectivityHost = self.connectivityHTTPHost
|
||||
}
|
||||
|
||||
let ICMPField = self.inputField(id: "ICMP", value: self.ICMPHost, placeholder: localizedString("Leave empty to disable the check"))
|
||||
let ICMPField = self.inputField(id: "ICMP", value: connectivityHost, placeholder: localizedString("Leave empty to disable the check"))
|
||||
self.connectivityHostField = ICMPField
|
||||
self.addArrangedSubview(PreferencesSection([
|
||||
PreferencesRow(localizedString("Connectivity host (ICMP)"), component: ICMPField) {
|
||||
PreferencesRow(localizedString("Reader type"), component: selectView(
|
||||
action: #selector(self.changeConnectivityMode),
|
||||
items: [
|
||||
KeyValue_t(key: "icmp", value: "ICMP"),
|
||||
KeyValue_t(key: "http", value: "HTTP")
|
||||
],
|
||||
selected: self.connectivityMode.rawValue
|
||||
)),
|
||||
PreferencesRow(localizedString("Connectivity host"), component: ICMPField) {
|
||||
NSWorkspace.shared.open(URL(string: "https://en.wikipedia.org/wiki/Internet_Control_Message_Protocol")!)
|
||||
},
|
||||
PreferencesRow(localizedString("Update interval"), component: selectView(
|
||||
action: #selector(self.changeICMPUpdateInterval),
|
||||
items: ReaderUpdateIntervals,
|
||||
selected: "\(self.updateICMPIntervalValue)"
|
||||
selected: "\(self.updateConnectivityIntervalValue)"
|
||||
))
|
||||
]))
|
||||
|
||||
@@ -249,7 +256,7 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func inputField(id: String, value: String, placeholder: String) -> NSView {
|
||||
private func inputField(id: String, value: String, placeholder: String) -> NSTextField {
|
||||
let field: NSTextField = NSTextField()
|
||||
field.identifier = NSUserInterfaceItemIdentifier(id)
|
||||
field.widthAnchor.constraint(equalToConstant: 250).isActive = true
|
||||
@@ -322,9 +329,15 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
func controlTextDidChange(_ notification: Notification) {
|
||||
if let field = notification.object as? NSTextField {
|
||||
if field.identifier == NSUserInterfaceItemIdentifier("ICMP") {
|
||||
self.ICMPHost = field.stringValue
|
||||
Store.shared.set(key: "\(self.title)_ICMPHost", value: self.ICMPHost)
|
||||
self.ICMPHostCallback(self.ICMPHost.isEmpty)
|
||||
if self.connectivityMode == .http {
|
||||
self.connectivityHTTPHost = field.stringValue
|
||||
Store.shared.set(key: "\(self.title)_HTTPHost", value: self.connectivityHTTPHost)
|
||||
self.connectivityHostCallback(self.connectivityHTTPHost.isEmpty)
|
||||
} else {
|
||||
self.connectivityICMPHost = field.stringValue
|
||||
Store.shared.set(key: "\(self.title)_ICMPHost", value: self.connectivityICMPHost)
|
||||
self.connectivityHostCallback(self.connectivityICMPHost.isEmpty)
|
||||
}
|
||||
} else if field.identifier == NSUserInterfaceItemIdentifier("text") {
|
||||
self.textValue = field.stringValue
|
||||
Store.shared.set(key: "\(self.title)_textWidgetValue", value: self.textValue)
|
||||
@@ -333,7 +346,7 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
}
|
||||
@objc private func changeICMPUpdateInterval(_ sender: NSMenuItem) {
|
||||
guard let key = sender.representedObject as? String, let value = Int(key) else { return }
|
||||
self.updateICMPIntervalValue = value
|
||||
self.updateConnectivityIntervalValue = value
|
||||
Store.shared.set(key: "\(self.title)_updateICMPInterval", value: value)
|
||||
self.setInterval(value)
|
||||
}
|
||||
@@ -354,4 +367,13 @@ internal class Settings: NSStackView, Settings_v, NSTextFieldDelegate {
|
||||
self.baseValue = key
|
||||
Store.shared.set(key: "\(self.title)_base", value: self.baseValue)
|
||||
}
|
||||
@objc private func changeConnectivityMode(_ sender: NSMenuItem) {
|
||||
guard let key = sender.representedObject as? String else { return }
|
||||
self.connectivityMode = ConnectivityReader.ConnectivityMode(rawValue: key) ?? .icmp
|
||||
Store.shared.set(key: "\(self.title)_connectivityMode", value: self.connectivityMode.rawValue)
|
||||
self.connectivityHostField?.stringValue = self.connectivityICMPHost
|
||||
if self.connectivityMode == .http {
|
||||
self.connectivityHostField?.stringValue = self.connectivityHTTPHost
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user