You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
3.9 KiB
116 lines
3.9 KiB
import Foundation
|
|
import AppKit
|
|
|
|
public enum PingQuality {
|
|
case good(ms: Int) // < 450 ms -> Emerald Green
|
|
case medium(ms: Int) // 450 - 750 ms -> Warm Yellow/Lime
|
|
case high(ms: Int) // 750 - 1500 ms -> Amber Orange
|
|
case disconnected(ms: Int?) // > 1500 ms or Timeout/Error -> Crimson Red
|
|
|
|
public var labelText: String {
|
|
switch self {
|
|
case .good(let ms): return "\(ms)ms"
|
|
case .medium(let ms): return "\(ms)ms"
|
|
case .high(let ms): return "\(ms)ms"
|
|
case .disconnected(let ms):
|
|
if let ms = ms {
|
|
return "\(ms)ms"
|
|
} else {
|
|
return "OFF"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public final class PingMonitor {
|
|
public static let shared = PingMonitor()
|
|
|
|
private let targetURL = URL(string: "https://translate.compare.soniox.com/")!
|
|
private var session: URLSession!
|
|
private var timer: Timer?
|
|
private var isRunning = false
|
|
private var isMeasuring = false
|
|
|
|
public var onPingUpdate: ((PingQuality) -> Void)?
|
|
public private(set) var currentQuality: PingQuality = .medium(ms: 380)
|
|
|
|
private init() {
|
|
let config = URLSessionConfiguration.default
|
|
config.timeoutIntervalForRequest = 2.5
|
|
config.timeoutIntervalForResource = 2.5
|
|
config.waitsForConnectivity = false
|
|
config.requestCachePolicy = .reloadIgnoringLocalCacheData
|
|
self.session = URLSession(configuration: config)
|
|
|
|
setupSleepWakeObservers()
|
|
}
|
|
|
|
private func setupSleepWakeObservers() {
|
|
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, object: nil, queue: .main) { [weak self] _ in
|
|
print("PingMonitor: Mac entering sleep. Pausing ping measurements.")
|
|
self?.stop()
|
|
}
|
|
|
|
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: .main) { [weak self] _ in
|
|
print("PingMonitor: Mac woke from sleep. Resuming ping in 2.5s after network settles...")
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 2.5) {
|
|
self?.start()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func start() {
|
|
guard !isRunning else { return }
|
|
isRunning = true
|
|
measurePing()
|
|
DispatchQueue.main.async { [weak self] in
|
|
self?.timer?.invalidate()
|
|
self?.timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
|
self?.measurePing()
|
|
}
|
|
}
|
|
}
|
|
|
|
public func stop() {
|
|
isRunning = false
|
|
isMeasuring = false
|
|
timer?.invalidate()
|
|
timer = nil
|
|
}
|
|
|
|
private func measurePing() {
|
|
guard isRunning, !isMeasuring else { return }
|
|
isMeasuring = true
|
|
|
|
var req = URLRequest(url: targetURL)
|
|
req.httpMethod = "HEAD"
|
|
let t0 = Date()
|
|
|
|
let task = session.dataTask(with: req) { [weak self] _, resp, _ in
|
|
defer { self?.isMeasuring = false }
|
|
let elapsed = Date().timeIntervalSince(t0) * 1000.0
|
|
let ms = Int(elapsed)
|
|
|
|
let quality: PingQuality
|
|
if let http = resp as? HTTPURLResponse, (200...399).contains(http.statusCode) {
|
|
if ms < 450 {
|
|
quality = .good(ms: ms)
|
|
} else if ms <= 750 {
|
|
quality = .medium(ms: ms)
|
|
} else if ms <= 1500 {
|
|
quality = .high(ms: ms)
|
|
} else {
|
|
quality = .disconnected(ms: ms)
|
|
}
|
|
} else {
|
|
quality = .disconnected(ms: nil)
|
|
}
|
|
|
|
DispatchQueue.main.async {
|
|
self?.currentQuality = quality
|
|
self?.onPingUpdate?(quality)
|
|
}
|
|
}
|
|
task.resume()
|
|
}
|
|
}
|