diff --git a/Kit/plugins/Reachability.swift b/Kit/plugins/Reachability.swift new file mode 100644 index 00000000..f20ca45c --- /dev/null +++ b/Kit/plugins/Reachability.swift @@ -0,0 +1,118 @@ +// +// Reachability.swift +// Kit +// +// Created by Serhiy Mytrovtsiy on 15/10/2021. +// Using Swift 5.0. +// Running on macOS 10.15. +// +// Copyright © 2021 Serhiy Mytrovtsiy. All rights reserved. +// Inspired by https://gist.github.com/saeed-rz/d9827b312915e0dc145497532e514470 and https://github.com/ashleymills/Reachability.swift + +import Foundation +import SystemConfiguration + +public class Reachability { + public var isReachable: Bool = false + + public var reachable: () -> Void = {} + public var unreachable: () -> Void = {} + + private var isRunning = false + private var reachability: SCNetworkReachability? + private let reachabilitySerialQueue = DispatchQueue(label: "eu.exelban.ReachabilityQueue") + private let log: NextLog = NextLog.shared.copy(category: "Reachability") + + public init(start: Bool = false) { + var zeroAddress = sockaddr() + zeroAddress.sa_len = UInt8(MemoryLayout.size) + zeroAddress.sa_family = sa_family_t(AF_INET) + + guard let ref = SCNetworkReachabilityCreateWithAddress(nil, &zeroAddress) else { + error("SCNetworkReachability create with address") + return + } + + self.reachability = ref + + if start { + self.start() + } + } + + public func start() { + guard let reachability = self.reachability, !self.isRunning else { + error("reachability is nil or already started") + return + } + + let callback: SCNetworkReachabilityCallBack = { (_, flags, info) in + guard let info = info else { return } + Unmanaged.fromOpaque(info).takeUnretainedValue().setFlags(flags) + } + + var context = SCNetworkReachabilityContext( + version: 0, + info: Unmanaged.passUnretained(self).toOpaque(), + retain: { (info: UnsafeRawPointer) -> UnsafeRawPointer in + let unmanagedReachability = Unmanaged.fromOpaque(info) + _ = unmanagedReachability.retain() + return UnsafeRawPointer(unmanagedReachability.toOpaque()) + }, + release: { (info: UnsafeRawPointer) -> Void in + Unmanaged.fromOpaque(info).release() + }, + copyDescription: nil + ) + + guard SCNetworkReachabilitySetCallback(reachability, callback, &context) else { + error("SCNetworkReachability set dispatch callback") + self.stop() + return + } + guard SCNetworkReachabilitySetDispatchQueue(reachability, reachabilitySerialQueue) else { + error("SCNetworkReachability set dispatch queue") + self.stop() + return + } + + self.reachabilitySerialQueue.sync { [unowned self] in + guard let reachability = self.reachability else { + error("reachability is nil") + return + } + + var flags = SCNetworkReachabilityFlags() + if !SCNetworkReachabilityGetFlags(reachability, &flags) { + error("SCNetworkReachability get flags") + self.stop() + return + } + + self.setFlags(flags) + } + + self.isRunning = true + } + + public func stop() { + defer { self.isRunning = false } + guard let reachability = self.reachability, self.isRunning else { + error("reachability is nil or already stopped") + return + } + + SCNetworkReachabilitySetCallback(reachability, nil, nil) + SCNetworkReachabilitySetDispatchQueue(reachability, nil) + } + + private func setFlags(_ flags: SCNetworkReachabilityFlags) { + self.isReachable = flags.contains(.reachable) + + if self.isReachable { + self.reachable() + } else { + self.unreachable() + } + } +} diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift index 60367924..d1101283 100644 --- a/Modules/Net/readers.swift +++ b/Modules/Net/readers.swift @@ -12,7 +12,6 @@ import Cocoa import Kit import SystemConfiguration -import Reachability import CoreWLAN struct ipResponse: Decodable { @@ -22,7 +21,7 @@ struct ipResponse: Decodable { } internal class UsageReader: Reader { - private var reachability: Reachability? = nil + private var reachability: Reachability = Reachability(start: true) private var usage: Network_Usage = Network_Usage() private var primaryInterface: String { @@ -50,19 +49,12 @@ internal class UsageReader: Reader { } public override func setup() { - do { - self.reachability = try Reachability() - try self.reachability!.startNotifier() - } catch let err { - error("initialize Reachability error \(err)", log: self.log) - } - - self.reachability!.whenReachable = { _ in + self.reachability.reachable = { if self.active { self.getDetails() } } - self.reachability!.whenUnreachable = { _ in + self.reachability.unreachable = { if self.active { self.usage.reset() self.callback(self.usage) @@ -73,6 +65,10 @@ internal class UsageReader: Reader { NotificationCenter.default.addObserver(self, selector: #selector(resetTotalNetworkUsage), name: .resetTotalNetworkUsage, object: nil) } + public override func terminate() { + self.reachability.stop() + } + public override func read() { let current: Bandwidth = self.reader == "interface" ? self.readInterfaceBandwidth() : self.readProcessBandwidth() @@ -90,7 +86,7 @@ internal class UsageReader: Reader { self.usage.total.upload += self.usage.bandwidth.upload self.usage.total.download += self.usage.bandwidth.download - self.usage.status = self.reachability?.connection != Optional.none + self.usage.status = self.reachability.isReachable self.callback(self.usage) diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 2ce2f6d3..4273cd83 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -14,7 +14,6 @@ 9A11AB26266FD828000C1C05 /* config.plist in Resources */ = {isa = PBXBuildFile; fileRef = 9A11AB25266FD828000C1C05 /* config.plist */; }; 9A11AB36266FD9F4000C1C05 /* readers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A11AB35266FD9F4000C1C05 /* readers.swift */; }; 9A11AB67266FDB69000C1C05 /* Kit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; }; - 9A27D5352538A456001BB651 /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 9A27D5342538A456001BB651 /* Reachability */; }; 9A2846FE2666A9CC00EC1F6D /* Kit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; }; 9A2846FF2666A9CC00EC1F6D /* Kit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A2846F72666A9CC00EC1F6D /* Kit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9A28475F2666AA2700EC1F6D /* LineChart.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A2847552666AA2700EC1F6D /* LineChart.swift */; }; @@ -69,6 +68,7 @@ 9A58DE9E24B363D800716A9F /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58DE9D24B363D800716A9F /* popup.swift */; }; 9A58DEA024B363F300716A9F /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58DE9F24B363F300716A9F /* settings.swift */; }; 9A58DEA424B3647600716A9F /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A58DEA324B3647600716A9F /* settings.swift */; }; + 9A5A8447271895B700BC40A4 /* Reachability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5A8446271895B700BC40A4 /* Reachability.swift */; }; 9A5AF11B2469CE9B00684737 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A5AF11A2469CE9B00684737 /* popup.swift */; }; 9A65654A253F20EF0096B607 /* settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A656549253F20EF0096B607 /* settings.swift */; }; 9A656562253F788A0096B607 /* popup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A656561253F788A0096B607 /* popup.swift */; }; @@ -393,6 +393,7 @@ 9A58DE9D24B363D800716A9F /* popup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; 9A58DE9F24B363F300716A9F /* settings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; 9A58DEA324B3647600716A9F /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; + 9A5A8446271895B700BC40A4 /* Reachability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = ""; }; 9A5AF11A2469CE9B00684737 /* popup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = popup.swift; sourceTree = ""; }; 9A5F0503256A9135002FF75F /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Localizable.strings; sourceTree = ""; }; 9A656549253F20EF0096B607 /* settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = settings.swift; sourceTree = ""; }; @@ -516,7 +517,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9A27D5352538A456001BB651 /* Reachability in Frameworks */, 9A2847D62666AA9C00EC1F6D /* Kit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -826,6 +826,7 @@ 9A2848072666AB3000EC1F6D /* Updater.swift */, 9A6EEBBD2685259500897371 /* Logger.swift */, 9A8AE0A226921A2A00B13054 /* Server.swift */, + 9A5A8446271895B700BC40A4 /* Reachability.swift */, ); path = plugins; sourceTree = ""; @@ -1080,7 +1081,6 @@ ); name = Net; packageProductDependencies = ( - 9A27D5342538A456001BB651 /* Reachability */, ); productName = Net; productReference = 9A3E17CC247A94AF00449CD1 /* Net.framework */; @@ -1340,7 +1340,6 @@ ); mainGroup = 9A1410EC229E721100D29793; packageReferences = ( - 9A27D4D42538A37D001BB651 /* XCRemoteSwiftPackageReference "Reachability" */, 9A27D4D72538A38A001BB651 /* XCRemoteSwiftPackageReference "Repeat" */, ); productRefGroup = 9A1410F6229E721100D29793 /* Products */; @@ -1528,6 +1527,7 @@ 9A28477A2666AA5000EC1F6D /* settings.swift in Sources */, 9A28475F2666AA2700EC1F6D /* LineChart.swift in Sources */, 9A28480E2666AB3000EC1F6D /* Updater.swift in Sources */, + 9A5A8447271895B700BC40A4 /* Reachability.swift in Sources */, 9A2847622666AA2700EC1F6D /* Label.swift in Sources */, 9A28477C2666AA5000EC1F6D /* reader.swift in Sources */, 9A9B8C9D27149A3700218374 /* Tachometer.swift in Sources */, @@ -2836,14 +2836,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 9A27D4D42538A37D001BB651 /* XCRemoteSwiftPackageReference "Reachability" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ashleymills/Reachability.swift"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.1.0; - }; - }; 9A27D4D72538A38A001BB651 /* XCRemoteSwiftPackageReference "Repeat" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/malcommac/Repeat"; @@ -2855,11 +2847,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 9A27D5342538A456001BB651 /* Reachability */ = { - isa = XCSwiftPackageProductDependency; - package = 9A27D4D42538A37D001BB651 /* XCRemoteSwiftPackageReference "Reachability" */; - productName = Reachability; - }; 9A2847AA2666AA7B00EC1F6D /* Repeat */ = { isa = XCSwiftPackageProductDependency; package = 9A27D4D72538A38A001BB651 /* XCRemoteSwiftPackageReference "Repeat" */; diff --git a/Stats.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Stats.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 607ebc9d..cab20955 100644 --- a/Stats.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Stats.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,15 +1,6 @@ { "object": { "pins": [ - { - "package": "Reachability", - "repositoryURL": "https://github.com/ashleymills/Reachability.swift", - "state": { - "branch": null, - "revision": "c01bbdf2d633cf049ae1ed1a68a2020a8bda32e2", - "version": "5.1.0" - } - }, { "package": "Repeat", "repositoryURL": "https://github.com/malcommac/Repeat", diff --git a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme index f3ef4f41..6a0271c2 100644 --- a/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme +++ b/Stats.xcodeproj/xcshareddata/xcschemes/Stats.xcscheme @@ -89,13 +89,6 @@ isEnabled = "NO"> - - - -