@@ -9,19 +9,23 @@ import NextcloudKit
99
1010public let NotifyPushAuthenticatedNotificationName = Notification . Name ( " NotifyPushAuthenticated " )
1111
12- public final class RemoteChangeObserver : NSObject , NextcloudKitDelegate , URLSessionWebSocketDelegate {
12+ public actor RemoteChangeObserver : NSObject , Sendable {
13+ // @unchecked Sendable is used because 'account' is mutable, but mutation is controlled and safe in this context.
1314 public let remoteInterface : RemoteInterface
1415 public let changeNotificationInterface : ChangeNotificationInterface
1516 public let domain : NSFileProviderDomain ?
1617 public let dbManager : FilesDatabaseManager
17- public var account : Account
18+ public let account : Account
1819 public var accountId : String { account. ncKitAccount }
1920
20- public var webSocketPingIntervalNanoseconds : UInt64 = 3 * 1_000_000_000
21- public var webSocketReconfigureIntervalNanoseconds : UInt64 = 1 * 1_000_000_000
22- public var webSocketPingFailLimit = 8
23- public var webSocketAuthenticationFailLimit = 3
24- public var webSocketTaskActive : Bool { webSocketTask != nil }
21+ public let webSocketPingIntervalNanoseconds : UInt64 = 3 * 1_000_000_000
22+ public let webSocketReconfigureIntervalNanoseconds : UInt64 = 1 * 1_000_000_000
23+ public let webSocketPingFailLimit = 8
24+ public let webSocketAuthenticationFailLimit = 3
25+
26+ public var webSocketTaskActive : Bool {
27+ webSocketTask != nil
28+ }
2529
2630 private let logger : FileProviderLogger
2731
@@ -45,7 +49,9 @@ public final class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSess
4549 }
4650 }
4751
48- public var pollingActive : Bool { pollingTimer != nil }
52+ public var pollingActive : Bool {
53+ pollingTimer != nil
54+ }
4955
5056 private( set) var networkReachability : NKTypeReachability = . unknown {
5157 didSet {
@@ -81,7 +87,7 @@ public final class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSess
8187
8288 private func startPollingTimer( ) {
8389 guard !invalidated else { return }
84- Task { @ MainActor in
90+ Task {
8591 pollingTimer = Timer . scheduledTimer (
8692 withTimeInterval: pollInterval, repeats: true
8793 ) { [ weak self] _ in
@@ -93,7 +99,7 @@ public final class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSess
9399 }
94100
95101 private func stopPollingTimer( ) {
96- Task { @ MainActor in
102+ Task {
97103 logger. info ( " Stopping polling timer. " )
98104 pollingTimer? . invalidate ( )
99105 pollingTimer = nil
@@ -219,40 +225,6 @@ public final class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSess
219225 }
220226 }
221227
222- public func urlSession(
223- _: URLSession ,
224- webSocketTask _: URLSessionWebSocketTask ,
225- didOpenWithProtocol _: String ?
226- ) {
227- guard !invalidated else { return }
228- logger. debug ( " Websocket connected \( accountId) sending auth details " , [ . account: accountId] )
229- Task { await authenticateWebSocket ( ) }
230- }
231-
232- public func urlSession(
233- _: URLSession ,
234- webSocketTask: URLSessionWebSocketTask ,
235- didCloseWith _: URLSessionWebSocketTask . CloseCode ,
236- reason: Data ?
237- ) {
238- guard !invalidated else { return }
239- // If the task that closed is not the current active task, it means we have
240- // already initiated a reset and this is a stale callback. Ignore it.
241- guard webSocketTask === self . webSocketTask else {
242- logger. debug ( " An old websocket task closed, ignoring. " )
243- return
244- }
245-
246- logger. debug ( " Socket connection closed for \( accountId) . " , [ . account: accountId] )
247-
248- if let reason {
249- logger. debug ( " Reason: \( String ( data: reason, encoding: . utf8) ?? " " ) " )
250- }
251-
252- logger. debug ( " Retrying websocket connection for \( accountId) . " , [ . account: accountId] )
253- reconnectWebSocket ( )
254- }
255-
256228 private func authenticateWebSocket( ) async {
257229 guard !invalidated else {
258230 return
@@ -316,7 +288,7 @@ public final class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSess
316288 switch result {
317289 case . failure:
318290 self . logger. debug ( " Failed to read websocket \( self . accountId) " , [ . account: self . accountId] )
319- // Do not reconnect here, delegate methods will handle reconnecting
291+ // Do not reconnect here, delegate methods will handle reconnecting
320292 case let . success( message) :
321293 switch message {
322294 case let . data( data) :
@@ -368,14 +340,48 @@ public final class RemoteChangeObserver: NSObject, NextcloudKitDelegate, URLSess
368340 logger. error ( " Received unknown string from websocket \( accountId) : \( string) " , [ . account: accountId] )
369341 }
370342 }
343+ }
371344
372- // MARK: - NextcloudKitDelegate methods
345+ // MARK: - URLSessionWebSocketDelegate
373346
374- public func networkReachabilityObserver( _ typeReachability: NKTypeReachability ) {
375- networkReachability = typeReachability
347+ extension RemoteChangeObserver : URLSessionWebSocketDelegate {
348+ nonisolated public func urlSession( _: URLSession , webSocketTask _: URLSessionWebSocketTask , didOpenWithProtocol _: String ? ) {
349+ guard !invalidated else {
350+ return
351+ }
352+
353+ logger. debug ( " Websocket connected \( accountId) sending auth details " , [ . account: accountId] )
354+ Task { await authenticateWebSocket ( ) }
376355 }
377356
357+ nonisolated public func urlSession( _: URLSession , webSocketTask: URLSessionWebSocketTask , didCloseWith _: URLSessionWebSocketTask . CloseCode , reason: Data ? ) {
358+ guard !invalidated else { return }
359+ // If the task that closed is not the current active task, it means we have
360+ // already initiated a reset and this is a stale callback. Ignore it.
361+ guard webSocketTask === self . webSocketTask else {
362+ logger. debug ( " An old websocket task closed, ignoring. " )
363+ return
364+ }
365+
366+ logger. debug ( " Socket connection closed for \( accountId) . " , [ . account: accountId] )
367+
368+ if let reason {
369+ logger. debug ( " Reason: \( String ( data: reason, encoding: . utf8) ?? " " ) " )
370+ }
371+
372+ logger. debug ( " Retrying websocket connection for \( accountId) . " , [ . account: accountId] )
373+ reconnectWebSocket ( )
374+ }
375+
378376 public func urlSessionDidFinishEvents( forBackgroundURLSession _: URLSession ) { }
377+ }
378+
379+ // MARK: - NextcloudKitDelegate methods
380+
381+ extension RemoteChangeObserver : NextcloudKitDelegate {
382+ public func networkReachabilityObserver( _ typeReachability: NKTypeReachability ) {
383+ networkReachability = typeReachability
384+ }
379385
380386 public func downloadProgress(
381387 _: Float ,
0 commit comments