summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadVPN/AddressCache/UpdateAddressCacheOperation.swift
blob: 4ca1d4d3c7646de7c1538f3d7a8591e92505d789 (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
109
110
111
112
113
//
//  UpdateAddressCacheOperation.swift
//  MullvadVPN
//
//  Created by pronebird on 08/12/2021.
//  Copyright © 2021 Mullvad VPN AB. All rights reserved.
//

import Foundation

extension AddressCache {

    enum CacheUpdateResult {
        /// Operation was cancelled.
        case cancelled

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

        /// Failure to update address cache.
        case failure(Error)

        /// Address cache is successfully updated.
        case success

        var isTaskCompleted: Bool {
            switch self {
            case .cancelled, .failure:
                return false
            case .success, .throttled:
                return true
            }
        }
    }

    class UpdateAddressCacheOperation: AsyncOperation {
        typealias CompletionHandler = (_ result: CacheUpdateResult) -> Void

        private let queue: DispatchQueue
        private let restClient: REST.Client
        private let store: AddressCache.Store
        private let updateInterval: TimeInterval

        private var completionHandler: CompletionHandler?
        private var restCancellationHandle: 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
            self.completionHandler = completionHandler
        }

        override func cancel() {
            queue.async {
                super.cancel()
                self.restCancellationHandle?.cancel()
            }
        }

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

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

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

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

            restCancellationHandle = restClient.getAddressList()
                .execute(retryStrategy: .default) { restResult in
                    self.queue.async {
                        switch restResult {
                        case .success(let newEndpoints):
                            self.store.setEndpoints(newEndpoints) { error in
                                self.queue.async {
                                    if let error = error {
                                        self.finish(with: .failure(error))
                                    } else {
                                        self.finish(with: .success)
                                    }
                                }
                            }

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

        private func finish(with result: CacheUpdateResult) {
            completionHandler?(result)
            completionHandler = nil
        }
    }
}