Skip to content

Commit 882ce1c

Browse files
committed
Add noInternetConnection error mapping to all cloud providers, run SwiftFormat
1 parent 7e9b950 commit 882ce1c

34 files changed

Lines changed: 437 additions & 239 deletions

.swiftformat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
--minversion 0.58.3
1+
--minversion 0.60.1
22
--exclude DerivedData
33

44
# format options

CryptomatorCloudAccess.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
4A05900E2451A145008831F9 /* CloudItemList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A05900D2451A145008831F9 /* CloudItemList.swift */; };
1515
4A0590122451A180008831F9 /* CloudItemMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0590112451A180008831F9 /* CloudItemMetadata.swift */; };
1616
4A05901D2451DD26008831F9 /* CloudProviderError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A05901C2451DD26008831F9 /* CloudProviderError.swift */; };
17+
E00861F84DC147ECB9B19E34 /* NetworkConnectionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44DAB1C070984F179681E2B8 /* NetworkConnectionError.swift */; };
18+
71515B8520704180B19B44CE /* NetworkConnectionErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A308ED4691D4D12A3C0F7BF /* NetworkConnectionErrorTests.swift */; };
1719
4A0785302859F4FE0015DAE1 /* S3Credential.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A07852F2859F4FE0015DAE1 /* S3Credential.swift */; };
1820
4A0785322859F5860015DAE1 /* S3CopyTaskUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0785312859F5860015DAE1 /* S3CopyTaskUtility.swift */; };
1921
4A0785342859F5E30015DAE1 /* AWSS3+Promises.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A0785332859F5E30015DAE1 /* AWSS3+Promises.swift */; };
@@ -235,6 +237,8 @@
235237
4A05900D2451A145008831F9 /* CloudItemList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudItemList.swift; sourceTree = "<group>"; };
236238
4A0590112451A180008831F9 /* CloudItemMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudItemMetadata.swift; sourceTree = "<group>"; };
237239
4A05901C2451DD26008831F9 /* CloudProviderError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudProviderError.swift; sourceTree = "<group>"; };
240+
44DAB1C070984F179681E2B8 /* NetworkConnectionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectionError.swift; sourceTree = "<group>"; };
241+
0A308ED4691D4D12A3C0F7BF /* NetworkConnectionErrorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectionErrorTests.swift; sourceTree = "<group>"; };
238242
4A07852F2859F4FE0015DAE1 /* S3Credential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3Credential.swift; sourceTree = "<group>"; };
239243
4A0785312859F5860015DAE1 /* S3CopyTaskUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = S3CopyTaskUtility.swift; sourceTree = "<group>"; };
240244
4A0785332859F5E30015DAE1 /* AWSS3+Promises.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AWSS3+Promises.swift"; sourceTree = "<group>"; };
@@ -526,6 +530,7 @@
526530
4A05900B2451A107008831F9 /* CloudProvider.swift */,
527531
9EE62A0C247D54760089DAF7 /* CloudProvider+Convenience.swift */,
528532
4A05901C2451DD26008831F9 /* CloudProviderError.swift */,
533+
44DAB1C070984F179681E2B8 /* NetworkConnectionError.swift */,
529534
);
530535
path = API;
531536
sourceTree = "<group>";
@@ -1012,6 +1017,7 @@
10121017
children = (
10131018
74DF965D24F3DE6400763713 /* CloudPathTests.swift */,
10141019
9EE62A0F247D54E90089DAF7 /* CloudProvider+ConvenienceTests.swift */,
1020+
0A308ED4691D4D12A3C0F7BF /* NetworkConnectionErrorTests.swift */,
10151021
);
10161022
path = API;
10171023
sourceTree = "<group>";
@@ -1342,6 +1348,7 @@
13421348
7416F22924F659550074DA8E /* VaultFormat6ProviderDecorator.swift in Sources */,
13431349
4A3896CE2632D65B00BA3E88 /* MicrosoftGraphAuthenticationProvider.swift in Sources */,
13441350
4A05901D2451DD26008831F9 /* CloudProviderError.swift in Sources */,
1351+
E00861F84DC147ECB9B19E34 /* NetworkConnectionError.swift in Sources */,
13451352
4A0785362859FA820015DAE1 /* AWSEndpoint+CustomRegionName.swift in Sources */,
13461353
4A765CA32878596000794440 /* DirectoryContentCache.swift in Sources */,
13471354
746F091927BD0077003FCD9F /* PCloudIdentifierCache.swift in Sources */,
@@ -1415,6 +1422,7 @@
14151422
4ABE9AA62BCACD1300675D74 /* XCTAssertThrowsError+Async.swift in Sources */,
14161423
4A30DAF62636D79C004B5B4E /* MicrosoftGraphCloudProviderTests.swift in Sources */,
14171424
9EE62A10247D54E90089DAF7 /* CloudProvider+ConvenienceTests.swift in Sources */,
1425+
71515B8520704180B19B44CE /* NetworkConnectionErrorTests.swift in Sources */,
14181426
74E32BEA283E575B005D8A54 /* VaultFormat8CryptorMock.swift in Sources */,
14191427
9ED0E622246180AB00FDB438 /* VaultFormat7CloudProviderMock.swift in Sources */,
14201428
7416F22D24F678AF0074DA8E /* VaultFormat6CloudProviderMockTests.swift in Sources */,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// NetworkConnectionError.swift
3+
// CryptomatorCloudAccess
4+
//
5+
// Created by Tobias Hagemann on 13.03.26.
6+
// Copyright © 2026 Skymatic GmbH. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
/// Returns `true` if the given error (or any underlying error) indicates a network connectivity issue.
12+
///
13+
/// Checks for `URLError` codes that indicate the device is offline or cannot reach the host.
14+
/// Recursively unwraps errors stored in `NSUnderlyingErrorKey` to handle SDK-wrapped errors.
15+
func isNetworkConnectionError(_ error: Error) -> Bool {
16+
if let urlError = error as? URLError {
17+
return urlError.code.isNetworkConnectionErrorCode
18+
}
19+
let nsError = error as NSError
20+
if nsError.domain == NSURLErrorDomain {
21+
return URLError.Code(rawValue: nsError.code).isNetworkConnectionErrorCode
22+
}
23+
if let underlyingError = nsError.userInfo[NSUnderlyingErrorKey] as? Error {
24+
return isNetworkConnectionError(underlyingError)
25+
}
26+
return false
27+
}
28+
29+
private extension URLError.Code {
30+
var isNetworkConnectionErrorCode: Bool {
31+
switch self {
32+
case .notConnectedToInternet,
33+
.networkConnectionLost,
34+
.cannotFindHost,
35+
.cannotConnectToHost,
36+
.dnsLookupFailed,
37+
.internationalRoamingOff,
38+
.dataNotAllowed:
39+
return true
40+
default:
41+
return false
42+
}
43+
}
44+
}

Sources/CryptomatorCloudAccess/Box/BoxCloudProvider.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,9 @@ public class BoxCloudProvider: CloudProvider {
578578
}
579579

580580
private func convertStandardError(_ error: Error) -> Error {
581+
if isNetworkConnectionError(error) {
582+
return CloudProviderError.noInternetConnection
583+
}
581584
switch error {
582585
case let error as BoxAPIError where (error.responseInfo.statusCode == 400 && error.responseInfo.rawBody?.contains("invalid_grant") == true) || error.responseInfo.statusCode == 401 || error.responseInfo.statusCode == 403:
583586
return CloudProviderError.unauthorized

Sources/CryptomatorCloudAccess/Crypto/VaultFormat6/VaultFormat6ShortenedNameCache.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import Promises
1313

1414
struct VaultFormat6ShorteningResult {
1515
let cloudPath: CloudPath
16-
var pointsToLNG: Bool { cloudPath.pathExtension == "lng" }
16+
var pointsToLNG: Bool {
17+
cloudPath.pathExtension == "lng"
18+
}
1719
}
1820

1921
private struct CachedEntry: Decodable, FetchableRecord, TableRecord {

Sources/CryptomatorCloudAccess/Crypto/VaultFormat7/VaultFormat7ShortenedNameCache.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ struct C9SDir {
1919
struct VaultFormat7ShorteningResult {
2020
let cloudPath: CloudPath
2121
let c9sDir: C9SDir?
22-
var pointsToC9S: Bool { cloudPath == c9sDir?.cloudPath }
22+
var pointsToC9S: Bool {
23+
cloudPath == c9sDir?.cloudPath
24+
}
2325
}
2426

2527
private struct CachedEntry: Decodable, FetchableRecord, TableRecord {

Sources/CryptomatorCloudAccess/Dropbox/DropboxCloudProvider.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,9 @@ public class DropboxCloudProvider: CloudProvider {
659659
} else if error.isInternalServerError() {
660660
return DropboxError.internalServerError
661661
} else if error.isClientError() {
662+
if isNetworkConnectionError(error.asClientError().nsError) {
663+
return CloudProviderError.noInternetConnection
664+
}
662665
return DropboxError.clientError
663666
} else {
664667
return DropboxError.unexpectedError

Sources/CryptomatorCloudAccess/GoogleDrive/GoogleDriveCloudProvider.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ public class GoogleDriveCloudProvider: CloudProvider {
531531
self.runningTickets.removeAll { $0 == ticket }
532532
if let error = error as NSError? {
533533
CloudAccessDDLogDebug("GoogleDriveCloudProvider: executeQuery(\(query.requestID)) failed with error: \(error)")
534-
if error.domain == NSURLErrorDomain, error.code == NSURLErrorNotConnectedToInternet || error.code == NSURLErrorCannotConnectToHost || error.code == NSURLErrorNetworkConnectionLost || error.code == NSURLErrorDNSLookupFailed || error.code == NSURLErrorResourceUnavailable || error.code == NSURLErrorInternationalRoamingOff {
534+
if isNetworkConnectionError(error) {
535535
reject(CloudProviderError.noInternetConnection)
536536
} else if (error.domain == kGTLRErrorObjectDomain && (error.code == 401 || error.code == 403)) || (error.domain == OIDOAuthTokenErrorDomain && error.code == OIDErrorCodeOAuth.invalidGrant.rawValue) {
537537
reject(CloudProviderError.unauthorized)
@@ -562,7 +562,9 @@ public class GoogleDriveCloudProvider: CloudProvider {
562562
self.runningFetchers.removeAll { $0 == fetcher }
563563
if let error = error as NSError? {
564564
CloudAccessDDLogDebug("GoogleDriveCloudProvider: executeFetcher(\(fetcher.request?.description ?? "nil")) failed with error: \(error)")
565-
if error.domain == kGTMSessionFetcherStatusDomain && (error.code == 401 || error.code == 403) {
565+
if isNetworkConnectionError(error) {
566+
reject(CloudProviderError.noInternetConnection)
567+
} else if error.domain == kGTMSessionFetcherStatusDomain && (error.code == 401 || error.code == 403) {
566568
reject(CloudProviderError.unauthorized)
567569
} else if error.domain == kGTMSessionFetcherStatusDomain, error.code == 404 {
568570
reject(CloudProviderError.itemNotFound)

Sources/CryptomatorCloudAccess/MicrosoftGraph/MicrosoftGraphCloudProvider.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,16 +335,14 @@ public class MicrosoftGraphCloudProvider: CloudProvider {
335335
guard let url = URL(string: "\(requestURLString(for: item))/children?$top=\(maxPageSize)") else {
336336
throw MicrosoftGraphError.invalidURL
337337
}
338-
let request = NSMutableURLRequest(url: url)
339-
return request
338+
return NSMutableURLRequest(url: url)
340339
}
341340

342341
func contentRequest(for item: MicrosoftGraphItem) throws -> NSMutableURLRequest {
343342
guard let url = URL(string: "\(requestURLString(for: item))/content") else {
344343
throw MicrosoftGraphError.invalidURL
345344
}
346-
let request = NSMutableURLRequest(url: url)
347-
return request
345+
return NSMutableURLRequest(url: url)
348346
}
349347

350348
func createUploadSessionRequest(for parentItem: MicrosoftGraphItem, with name: String) throws -> NSMutableURLRequest {
@@ -624,6 +622,9 @@ public class MicrosoftGraphCloudProvider: CloudProvider {
624622
}
625623

626624
private func convertStandardError(_ error: Error) -> Error {
625+
if isNetworkConnectionError(error) {
626+
return CloudProviderError.noInternetConnection
627+
}
627628
switch error {
628629
case MicrosoftGraphAuthenticationProviderError.accountNotFound, MicrosoftGraphAuthenticationProviderError.noAccounts:
629630
return CloudProviderError.unauthorized

Sources/CryptomatorCloudAccess/MicrosoftGraph/MicrosoftGraphDiscovery.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,24 +73,21 @@ public class MicrosoftGraphDiscovery {
7373
guard let url = URL(string: "\(MSGraphBaseURL)/sites/\(hostName):\(serverRelativePath)") else {
7474
throw MicrosoftGraphError.invalidURL
7575
}
76-
let request = NSMutableURLRequest(url: url)
77-
return request
76+
return NSMutableURLRequest(url: url)
7877
}
7978

8079
func sharePointDrivesRequest(for siteID: String) throws -> NSMutableURLRequest {
8180
guard let url = URL(string: "\(MSGraphBaseURL)/sites/\(siteID)/drives") else {
8281
throw MicrosoftGraphError.invalidURL
8382
}
84-
let request = NSMutableURLRequest(url: url)
85-
return request
83+
return NSMutableURLRequest(url: url)
8684
}
8785

8886
func sharePointDriveRequest(for driveID: String) throws -> NSMutableURLRequest {
8987
guard let url = URL(string: "\(MSGraphBaseURL)/drives/\(driveID)") else {
9088
throw MicrosoftGraphError.invalidURL
9189
}
92-
let request = NSMutableURLRequest(url: url)
93-
return request
90+
return NSMutableURLRequest(url: url)
9491
}
9592

9693
// MARK: - Execution

0 commit comments

Comments
 (0)