Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Check Swift version
run: swift --version
- name: Build with strict concurrency checks
run: swift build
run: swift build -Xswiftc -strict-concurrency=complete

lint:
runs-on: macos-26
Expand Down
36 changes: 8 additions & 28 deletions Kumo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,8 @@
94C185EB22D8E01100CD66DC /* ThrowingDataRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C185EA22D8E01100CD66DC /* ThrowingDataRepresentable.swift */; };
94C185ED22D8E13400CD66DC /* UIImage+DataRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C185EC22D8E13400CD66DC /* UIImage+DataRepresentable.swift */; };
94C185EF22D8E19400CD66DC /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C185EE22D8E19400CD66DC /* Errors.swift */; };
94C3BD95219B0F8100B4A3E2 /* Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94C3BD94219B0F8100B4A3E2 /* Progress.swift */; };
94F2CDCD222A3602006D9C36 /* Service+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F2CDC7222A3602006D9C36 /* Service+Download.swift */; };
94F2CDCF222A3602006D9C36 /* Service+SideEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F2CDC9222A3602006D9C36 /* Service+SideEffects.swift */; };
94F2CDD0222A3602006D9C36 /* Service+Upload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F2CDCA222A3602006D9C36 /* Service+Upload.swift */; };
B55A1178233E5D92006EAB34 /* Unkeyed.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55A1177233E5D92006EAB34 /* Unkeyed.swift */; };
B58694D12322B3CC006C20EE /* Epic.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58694D02322B3CC006C20EE /* Epic.swift */; };
B58782DC2538F9D700A62D73 /* AnyPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58782DB2538F9D700A62D73 /* AnyPublisher.swift */; };
B59A570924A7084F00EA68FF /* AnyCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59A570824A7084F00EA68FF /* AnyCancellable.swift */; };
B5B657D424A6444F00776C23 /* KumoNamespaceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B657D324A6444F00776C23 /* KumoNamespaceProxy.swift */; };
D0F9B5A62441566800038580 /* KumoCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = D0F9B5A42441566800038580 /* KumoCoding.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -187,13 +182,8 @@
94C185EA22D8E01100CD66DC /* ThrowingDataRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThrowingDataRepresentable.swift; sourceTree = "<group>"; };
94C185EC22D8E13400CD66DC /* UIImage+DataRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+DataRepresentable.swift"; sourceTree = "<group>"; };
94C185EE22D8E19400CD66DC /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
94C3BD94219B0F8100B4A3E2 /* Progress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Progress.swift; sourceTree = "<group>"; };
94F2CDC7222A3602006D9C36 /* Service+Download.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Service+Download.swift"; sourceTree = "<group>"; };
94F2CDC9222A3602006D9C36 /* Service+SideEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Service+SideEffects.swift"; sourceTree = "<group>"; };
94F2CDCA222A3602006D9C36 /* Service+Upload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Service+Upload.swift"; sourceTree = "<group>"; };
B55A1177233E5D92006EAB34 /* Unkeyed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Unkeyed.swift; sourceTree = "<group>"; };
B58694D02322B3CC006C20EE /* Epic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Epic.swift; sourceTree = "<group>"; };
B58782DB2538F9D700A62D73 /* AnyPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyPublisher.swift; sourceTree = "<group>"; };
B59A570824A7084F00EA68FF /* AnyCancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyCancellable.swift; sourceTree = "<group>"; };
B5B657D324A6444F00776C23 /* KumoNamespaceProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KumoNamespaceProxy.swift; sourceTree = "<group>"; };
D0F9B5A22441566800038580 /* KumoCoding.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = KumoCoding.framework; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -313,9 +303,7 @@
children = (
B59A570824A7084F00EA68FF /* AnyCancellable.swift */,
945421752187889200932CAF /* Copying.swift */,
94C3BD94219B0F8100B4A3E2 /* Progress.swift */,
945421732187883B00932CAF /* URLSession.swift */,
B58782DB2538F9D700A62D73 /* AnyPublisher.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -526,9 +514,6 @@
94F2CDB92229F852006D9C36 /* Functions */ = {
isa = PBXGroup;
children = (
94F2CDC7222A3602006D9C36 /* Service+Download.swift */,
94F2CDC9222A3602006D9C36 /* Service+SideEffects.swift */,
94F2CDCA222A3602006D9C36 /* Service+Upload.swift */,
);
path = Functions;
sourceTree = "<group>";
Expand Down Expand Up @@ -782,9 +767,7 @@
945421742187883B00932CAF /* URLSession.swift in Sources */,
94C185EB22D8E01100CD66DC /* ThrowingDataRepresentable.swift in Sources */,
94C185ED22D8E13400CD66DC /* UIImage+DataRepresentable.swift in Sources */,
94C3BD95219B0F8100B4A3E2 /* Progress.swift in Sources */,
949AD294218BD7D500808C79 /* ResponseError.swift in Sources */,
94F2CDCD222A3602006D9C36 /* Service+Download.swift in Sources */,
9430733F22D764AD00AEAC8D /* HTTP.swift in Sources */,
94690FC222CCE16B002C37BF /* URLRequest+HTTPHeader.swift in Sources */,
949AD296218BD80E00808C79 /* MultipartForm.swift in Sources */,
Expand All @@ -793,12 +776,10 @@
EA646FBC26CE930200482D6B /* Kumo.docc in Sources */,
946F43AD22DCEA7500CE9EC9 /* DataConvertible.swift in Sources */,
B5B657D424A6444F00776C23 /* KumoNamespaceProxy.swift in Sources */,
94F2CDCF222A3602006D9C36 /* Service+SideEffects.swift in Sources */,
94690FBD22CBA823002C37BF /* Authorization.swift in Sources */,
94690FBF22CCE097002C37BF /* URLSessionConfiguration+HTTPHeader.swift in Sources */,
949AD292218BD75500808C79 /* UploadError.swift in Sources */,
946F43AB22DCEA5900CE9EC9 /* DataRepresentable.swift in Sources */,
94F2CDD0222A3602006D9C36 /* Service+Upload.swift in Sources */,
945D4114217F6222008ACFD0 /* Service.swift in Sources */,
946F43AF22DCEA9200CE9EC9 /* FailableDataConvertible.swift in Sources */,
94C185E922D8DEF200CD66DC /* FailableDataRepresentable.swift in Sources */,
Expand All @@ -816,7 +797,6 @@
B59A570924A7084F00EA68FF /* AnyCancellable.swift in Sources */,
94C185EF22D8E19400CD66DC /* Errors.swift in Sources */,
94A4E67622DCF3540033B480 /* Storage.swift in Sources */,
B58782DC2538F9D700A62D73 /* AnyPublisher.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -949,7 +929,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
Expand Down Expand Up @@ -1007,7 +987,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
Expand All @@ -1032,7 +1012,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Kumo/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -1062,7 +1042,7 @@
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/Sources/Kumo/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1085,7 +1065,7 @@
DEVELOPMENT_TEAM = 75Y586SA36;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/Tests/KumoTests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1106,7 +1086,7 @@
DEVELOPMENT_TEAM = 75Y586SA36;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = "$(SRCROOT)/Tests/KumoTests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand All @@ -1130,7 +1110,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "${SRCROOT}/Sources/KumoCoding/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -1159,7 +1139,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
INFOPLIST_FILE = "${SRCROOT}/Sources/KumoCoding/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import PackageDescription
let package = Package(
name: "Kumo",
platforms: [
.iOS(.v26),
.tvOS(.v26),
.macOS(.v26),
.iOS(.v18),
.tvOS(.v18),
.macOS(.v15),
],
products: [
.library(name: "Kumo", targets: ["Kumo"]),
Expand Down
83 changes: 25 additions & 58 deletions Sources/Kumo/ApplicationLayer.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Combine
@preconcurrency import Combine
import Foundation
import SystemConfiguration
import Network

/// The network connectivity status.
public enum NetworkConnectivity {
Expand All @@ -24,11 +24,11 @@ public enum NetworkConnectivity {
/// services and exposes a publisher ``networkConnectivity`` to monitor network
/// connectivity.
open class ApplicationLayer {

private var commonHeaders = [String: String]()

private let services: [ServiceKey: Service]

private let networkConnectivitySubject: CurrentValueSubject<NetworkConnectivity, Never> = .init(.unknown)
private let pathMonitor = NWPathMonitor()

/// A publisher that updates with the current network connectivity status
/// for the device.
Expand All @@ -40,18 +40,29 @@ open class ApplicationLayer {
/// pairs.
public init(with services: [ServiceKey: Service] = [:]) {
self.services = services

var address = sockaddr_in()
address.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
address.sin_family = sa_family_t(AF_INET)
withUnsafePointer(to: &address) { pointer in
pointer.withMemoryRebound(to: sockaddr.self, capacity: MemoryLayout<sockaddr>.size) {
SCNetworkReachabilityCreateWithAddress(nil, $0)

let subject = networkConnectivitySubject
pathMonitor.pathUpdateHandler = { path in
let connectivity: NetworkConnectivity
switch path.status {
case .satisfied:
#if os(iOS)
connectivity = path.isExpensive ? .wwan : .internet
#else
connectivity = .internet
#endif
case .unsatisfied, .requiresConnection:
connectivity = .notConnected
@unknown default:
connectivity = .unknown
}
subject.send(connectivity)
}
.map { [unowned self] in self.publishReachability($0) }?
.sink(receiveValue: { [unowned self] in self.networkConnectivitySubject.send($0) })
.withLifetime(of: self)
pathMonitor.start(queue: DispatchQueue(label: "DuetHealth.Kumo.networkMonitor"))
}

deinit {
pathMonitor.cancel()
}

/// Retrieves the service for a given `key`.
Expand All @@ -61,48 +72,4 @@ open class ApplicationLayer {
return services[key]!
}

private func publishReachability(_ reachability: SCNetworkReachability) -> AnyPublisher<NetworkConnectivity, Never> {
AnyPublisher.create { subscriber in
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = Unmanaged.passRetained(AnyObserverReference<NetworkConnectivity, Never>(subscriber)).toOpaque()
SCNetworkReachabilitySetCallback(reachability, { _, flags, info in
guard let observer = info.map({ Unmanaged<AnyObserverReference<NetworkConnectivity, Never>>.fromOpaque($0).takeUnretainedValue() }) else { return }
if flags.isReachable {
#if os(iOS)
observer.base.onNext(flags.contains(.isWWAN) ? .wwan : .internet)
#else
observer.base.onNext(.internet)
#endif
} else {
observer.base.onNext(.notConnected)
}
}, &context)
SCNetworkReachabilitySetDispatchQueue(reachability, DispatchQueue.main)
return AnyCancellable() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
}
}
}

}

private class AnyObserverReference<Input, Failure> where Failure: Error {

let base: AnyObserver<Input, Failure>

init(_ base: AnyObserver<Input, Failure>) {
self.base = base
}

}

private extension SCNetworkReachabilityFlags {

var isReachable: Bool {
let canConnectAutomatically = contains(.connectionOnDemand) || contains(.connectionOnTraffic) && !contains(.interventionRequired)
return contains(.reachable)
&& (!contains(.connectionRequired) || canConnectAutomatically)
}

}
Loading
Loading