summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/AddressCache/UpdateAddressCacheOperation.swift
blob: 81940acd21ea36c5cf89565b31cdebfa8e90015d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
//
//  UpdateAddressCacheOperation.swift
//  MullvadVPN
//
//  Created by pronebird on 08/12/2021.
//  Copyright © 2021 Mullvad VPN AB. All rights reserved.
//

import Foundation

extension AddressCache {

    enum CacheUpdateResult {
        /// Address cache update was throttled as it was requested too early.
        case throttled(_ lastUpdateDate: Date)

        /// Address cache is successfully updated.
        case finished
    }

    class UpdateAddressCacheOperation: ResultOperation<CacheUpdateResult, Error> {
        private let queue: DispatchQueue
        private let restClient: REST.Client
        private let store: AddressCache.Store
        private let updateInterval: TimeInterval

        private var requestTask: Cancellable?

        init(queue: DispatchQueue, restClient: REST.Client, store: AddressCache.Store, updateInterval: TimeInterval, completionHandler: CompletionHandler?) {
            self.queue = queue
            self.restClient = restClient
            self.store = store
            self.updateInterval = updateInterval

            super.init(completionQueue: queue, completionHandler: completionHandler)
        }

        override func main() {
            queue.async {
                self.startUpdate()
            }
        }

        override func cancel() {
            super.cancel()

            queue.async {
                self.requestTask?.cancel()
                self.requestTask = nil
            }
        }

        private func startUpdate() {
            guard !isCancelled else {
                finish(completion: .cancelled)
                return
            }

            let lastUpdate = store.getLastUpdateDateAndWait()
            let nextUpdate = Date(timeInterval: updateInterval, since: lastUpdate)

            guard nextUpdate <= Date() else {
                finish(completion: .success(.throttled(lastUpdate)))
                return
            }

            requestTask = restClient.getAddressList(retryStrategy: .default) { completion in
                self.queue.async {
                    self.handleResponse(completion)
                }
            }
        }

        private func handleResponse(_ completion: OperationCompletion<[AnyIPEndpoint], REST.Error>) {
            switch completion {
            case .success(let newEndpoints):
                self.store.setEndpoints(newEndpoints) { error in
                    if let error = error {
                        self.finish(completion: .failure(error))
                    } else {
                        self.finish(completion: .success(.finished))
                    }
                }

            case .failure(let error):
                if case URLError.cancelled = error {
                    self.finish(completion: .cancelled)
                } else {
                    self.finish(completion: .failure(error))
                }

            case .cancelled:
                self.finish(completion: .cancelled)
            }
        }
    }
}

extension OperationCompletion where Success == AddressCache.CacheUpdateResult {
    var isTaskCompleted: Bool {
        switch self {
        case .success:
            return true
        case .cancelled, .failure:
            return false
        }
    }
}