@@ -9,16 +9,16 @@ import NextcloudKit
99
1010public let NotifyPushAuthenticatedNotificationName = Notification . Name ( " NotifyPushAuthenticated " )
1111
12- public actor RemoteChangeObserver : NSObject , Sendable {
12+ public final class RemoteChangeObserver : NSObject , @ unchecked Sendable {
1313 // @unchecked Sendable is used because 'account' is mutable, but mutation is controlled and safe in this context.
1414 public let remoteInterface : RemoteInterface
1515 public let changeNotificationInterface : ChangeNotificationInterface
1616 public let domain : NSFileProviderDomain ?
1717 public let dbManager : FilesDatabaseManager
18- public let account : Account
18+ public var account : Account
1919 public var accountId : String { account. ncKitAccount }
2020
21- public let webSocketPingIntervalNanoseconds : UInt64 = 3 * 1_000_000_000
21+ public var webSocketPingIntervalNanoseconds : UInt64 = 3 * 1_000_000_000
2222 public let webSocketReconfigureIntervalNanoseconds : UInt64 = 1 * 1_000_000_000
2323 public let webSocketPingFailLimit = 8
2424 public let webSocketAuthenticationFailLimit = 3
@@ -40,14 +40,8 @@ public actor RemoteChangeObserver: NSObject, Sendable {
4040 private( set) var webSocketAuthenticationFailCount = 0
4141
4242 private( set) var pollingTimer : Timer ?
43- public var pollInterval : TimeInterval = 60 {
44- didSet {
45- if pollingActive {
46- stopPollingTimer ( )
47- startPollingTimer ( )
48- }
49- }
50- }
43+
44+ let pollInterval : TimeInterval
5145
5246 public var pollingActive : Bool {
5347 pollingTimer != nil
@@ -73,13 +67,15 @@ public actor RemoteChangeObserver: NSObject, Sendable {
7367 changeNotificationInterface: ChangeNotificationInterface ,
7468 domain: NSFileProviderDomain ? ,
7569 dbManager: FilesDatabaseManager ,
70+ pollInterval: TimeInterval = 60 ,
7671 log: any FileProviderLogging
7772 ) {
7873 self . account = account
7974 self . remoteInterface = remoteInterface
8075 self . changeNotificationInterface = changeNotificationInterface
8176 self . domain = domain
8277 self . dbManager = dbManager
78+ self . pollInterval = pollInterval
8379 logger = FileProviderLogger ( category: " RemoteChangeObserver " , log: log)
8480 super. init ( )
8581
@@ -89,22 +85,22 @@ public actor RemoteChangeObserver: NSObject, Sendable {
8985 webSocketAuthenticationFailCount = 0
9086
9187 Task {
92- await reconnectWebSocket ( )
88+ reconnectWebSocket ( )
9389 }
9490 }
9591
9692 private func startPollingTimer( ) {
97- guard !invalidated else { return }
98- Task {
99- pollingTimer = Timer . scheduledTimer (
100- withTimeInterval: pollInterval, repeats: true
101- ) { [ weak self] _ in
102- self ? . logger. info ( " Polling timer timeout, notifying change. " )
93+ guard !invalidated else {
94+ self . logger. error ( " Starting polling timer while the current one is not invalidated yet! " )
95+ return
96+ }
10397
104- Task {
105- await self ? . startWorkingSetCheck ( )
106- }
98+ Task { @MainActor in
99+ pollingTimer = Timer . scheduledTimer ( withTimeInterval: pollInterval, repeats: true ) { [ weak self] _ in
100+ self ? . logger. info ( " Polling timer timeout, notifying change. " )
101+ self ? . startWorkingSetCheck ( )
107102 }
103+
108104 logger. info ( " Starting polling timer. " )
109105 }
110106 }
@@ -118,29 +114,35 @@ public actor RemoteChangeObserver: NSObject, Sendable {
118114 }
119115
120116 public func invalidate( ) {
117+ logger. debug ( " Invalidating. " )
121118 invalidated = true
122119 resetWebSocket ( )
123120 }
124121
125122 private func reconnectWebSocket( ) {
123+ logger. debug ( " Reconnecting web socket... " )
126124 stopPollingTimer ( )
127125 resetWebSocket ( )
126+
128127 guard networkReachability != . notReachable else {
129128 logger. error ( " Network unreachable, will retry when reconnected. " )
130129 return
131130 }
131+
132132 guard webSocketAuthenticationFailCount < webSocketAuthenticationFailLimit else {
133133 logger. error ( " Exceeded authentication failures for notify push websocket \( account. ncKitAccount) , will poll instead. " , [ . account: account. ncKitAccount] )
134134 startPollingTimer ( )
135135 return
136136 }
137+
137138 Task { [ weak self] in
138139 try await Task . sleep ( nanoseconds: self ? . webSocketReconfigureIntervalNanoseconds ?? 0 )
139140 await self ? . configureNotifyPush ( )
140141 }
141142 }
142143
143144 public func resetWebSocket( ) {
145+ logger. debug ( " Resetting web socket... " )
144146 webSocketTask? . cancel ( )
145147 webSocketUrlSession = nil
146148 webSocketTask = nil
@@ -152,7 +154,10 @@ public actor RemoteChangeObserver: NSObject, Sendable {
152154 }
153155
154156 private func configureNotifyPush( ) async {
157+ logger. debug ( " Configuring notify push... " )
158+
155159 guard !invalidated else {
160+ logger. error ( " Attempt to configure notify push while being invalidated! " )
156161 return
157162 }
158163
@@ -246,7 +251,7 @@ public actor RemoteChangeObserver: NSObject, Sendable {
246251 }
247252
248253 Task {
249- await self . pingWebSocket ( )
254+ self . pingWebSocket ( )
250255 }
251256 }
252257 }
@@ -273,20 +278,20 @@ public actor RemoteChangeObserver: NSObject, Sendable {
273278
274279 guard error == nil else {
275280 self . logger. error ( " Websocket ping failed. " , [ . error: error] )
276- await self . incrementWebSocketPingFailCount ( )
281+ self . incrementWebSocketPingFailCount ( )
277282
278- if await self . webSocketPingFailCount > self . webSocketPingFailLimit {
283+ if self . webSocketPingFailCount > self . webSocketPingFailLimit {
279284 Task . detached ( priority: . medium) {
280- await self . reconnectWebSocket ( )
285+ self . reconnectWebSocket ( )
281286 }
282287 } else {
283- await startNewWebSocketPingTask ( )
288+ startNewWebSocketPingTask ( )
284289 }
285290
286291 return
287292 }
288293
289- await startNewWebSocketPingTask ( )
294+ startNewWebSocketPingTask ( )
290295 }
291296 }
292297 }
@@ -304,20 +309,20 @@ public actor RemoteChangeObserver: NSObject, Sendable {
304309
305310 switch result {
306311 case . failure:
307- let accountId = await self . accountId
312+ let accountId = self . accountId
308313 self . logger. debug ( " Failed to read websocket. " , [ . account: accountId] )
309314 // Do not reconnect here, delegate methods will handle reconnecting
310315 case let . success( message) :
311316 switch message {
312317 case let . data( data) :
313- await self . processWebsocket ( data: data)
318+ self . processWebsocket ( data: data)
314319 case let . string( string) :
315- await self . processWebsocket ( string: string)
320+ self . processWebsocket ( string: string)
316321 @unknown default :
317322 self . logger. error ( " Unknown case encountered while reading websocket! " )
318323 }
319324
320- await self . readWebSocket ( )
325+ self . readWebSocket ( )
321326 }
322327 }
323328 }
@@ -360,37 +365,45 @@ public actor RemoteChangeObserver: NSObject, Sendable {
360365 logger. error ( " Received unknown string from websocket \( account. ncKitAccount) : \( string) " , [ . account: account. ncKitAccount] )
361366 }
362367 }
368+
369+ func replaceAccount( with account: Account ) {
370+ self . account = account
371+ }
372+
373+ func setWebSocketPingInterval( to nanoseconds: UInt64 ) {
374+ self . webSocketPingIntervalNanoseconds = nanoseconds
375+ }
363376}
364377
365378// MARK: - URLSessionWebSocketDelegate
366379
367380extension RemoteChangeObserver : URLSessionWebSocketDelegate {
368381 nonisolated public func urlSession( _: URLSession , webSocketTask _: URLSessionWebSocketTask , didOpenWithProtocol _: String ? ) {
369382 Task {
370- guard await invalidated == false else {
383+ guard invalidated == false else {
371384 return
372385 }
373386
374- logger. debug ( " Websocket connected sending auth details " , [ . account: await accountId] )
387+ logger. debug ( " Websocket connected sending auth details " , [ . account: accountId] )
375388 await authenticateWebSocket ( )
376389 }
377390 }
378391
379392 nonisolated public func urlSession( _: URLSession , webSocketTask: URLSessionWebSocketTask , didCloseWith _: URLSessionWebSocketTask . CloseCode , reason: Data ? ) {
380393 Task {
381- guard await invalidated == false else {
394+ guard invalidated == false else {
382395 return
383396 }
384397
385398 // If the task that closed is not the current active task, it means we have
386399 // already initiated a reset and this is a stale callback. Ignore it.
387- guard await webSocketTask === self . webSocketTask else {
400+ guard webSocketTask === self . webSocketTask else {
388401 logger. debug ( " An old websocket task closed, ignoring. " )
389402 return
390403 }
391404
392- logger. debug ( " Socket connection closed: \( String ( data: reason ?? Data ( ) , encoding: . utf8) ?? " unknown reason " ) . Retrying websocket connection. " , [ . account: await accountId] )
393- await reconnectWebSocket ( )
405+ logger. debug ( " Socket connection closed: \( String ( data: reason ?? Data ( ) , encoding: . utf8) ?? " unknown reason " ) . Retrying websocket connection. " , [ . account: accountId] )
406+ reconnectWebSocket ( )
394407 }
395408 }
396409
@@ -406,7 +419,7 @@ extension RemoteChangeObserver: NextcloudKitDelegate {
406419 return
407420 }
408421
409- guard await !invalidated else {
422+ guard !invalidated else {
410423 return
411424 }
412425
@@ -440,7 +453,7 @@ extension RemoteChangeObserver: NextcloudKitDelegate {
440453 return
441454 }
442455
443- await self . setNetworkReachability ( typeReachability)
456+ self . setNetworkReachability ( typeReachability)
444457 }
445458 }
446459
@@ -500,8 +513,9 @@ extension RemoteChangeObserver: NextcloudKitDelegate {
500513 /// - Parameters:
501514 /// - completionHandler: An optional closure to call after the working set check completed.
502515 ///
503- func startWorkingSetCheck( completionHandler: ( ( ) -> Void ) ? = nil ) {
516+ func startWorkingSetCheck( completionHandler: ( @ Sendable ( ) -> Void ) ? = nil ) {
504517 guard !workingSetCheckOngoing, !invalidated else {
518+ logger. error ( " Cancelling dispatch of working set check because it either is already ongoing or this is invalidated! " )
505519 return
506520 }
507521
@@ -512,9 +526,11 @@ extension RemoteChangeObserver: NextcloudKitDelegate {
512526 }
513527
514528 private func checkWorkingSet( ) async {
529+ logger. debug ( " Checking working set... " )
515530 workingSetCheckOngoing = true
516531
517532 defer {
533+ logger. debug ( " Working set check no longer ongoing. " )
518534 workingSetCheckOngoing = false
519535 }
520536
0 commit comments