diff --git a/Tusker/Controllers/MastodonController.swift b/Tusker/Controllers/MastodonController.swift index 2c02d7be..b132f3d0 100644 --- a/Tusker/Controllers/MastodonController.swift +++ b/Tusker/Controllers/MastodonController.swift @@ -46,6 +46,9 @@ class MastodonController: ObservableObject { @Published private(set) var instance: Instance! private(set) var customEmojis: [Emoji]? + private var pendingOwnInstanceRequestCallbacks = [(Instance) -> Void]() + private var ownInstanceRequest: URLSessionTask? + var loggedIn: Bool { accountInfo != nil } @@ -115,17 +118,56 @@ class MastodonController: ObservableObject { } } - // todo: this should dedup requests func getOwnInstance(completion: ((Instance) -> Void)? = nil) { + getOwnInstanceInternal(retryAttempt: 0, completion: completion) + } + + private func getOwnInstanceInternal(retryAttempt: Int, completion: ((Instance) -> Void)?) { + // this is main thread only to prevent concurrent access to ownInstanceRequest and pendingOwnInstanceRequestCallbacks + assert(Thread.isMainThread) + if let instance = self.instance { completion?(instance) } else { - let request = Client.getInstance() - run(request) { (response) in - guard case let .success(instance, _) = response else { fatalError() } - DispatchQueue.main.async { - self.instance = instance - completion?(instance) + if let completion = completion { + pendingOwnInstanceRequestCallbacks.append(completion) + } + + if ownInstanceRequest == nil { + let request = Client.getInstance() + ownInstanceRequest = run(request) { (response) in + switch response { + case .failure(_): + let delay: DispatchTimeInterval + switch retryAttempt { + case 0: + delay = .seconds(1) + case 1: + delay = .seconds(5) + case 2: + delay = .seconds(30) + case 3: + delay = .seconds(60) + default: + // if we've failed four times, just give up :/ + return + } + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + // completion is nil because in this invocation of getOwnInstanceInternal we've already added it to the pending callbacks array + self.getOwnInstanceInternal(retryAttempt: retryAttempt + 1, completion: nil) + } + + case let .success(instance, _): + DispatchQueue.main.async { + self.ownInstanceRequest = nil + self.instance = instance + + for completion in self.pendingOwnInstanceRequestCallbacks { + completion(instance) + } + self.pendingOwnInstanceRequestCallbacks = [] + } + } } } }