diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2021-09-16 11:59:54 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2021-09-17 10:40:20 +0200 |
| commit | 42fde982b7d7a71ef9ec32e9397b399a50415b06 (patch) | |
| tree | 411735dba3185242c4b5ba44cc7c4d79eb6adee9 /ios/MullvadVPN/RelayCache/RelayCacheIO.swift | |
| parent | 9a4412b5a2b48b2c069daa1c44a21280fa8e599b (diff) | |
| download | mullvadvpn-42fde982b7d7a71ef9ec32e9397b399a50415b06.tar.xz mullvadvpn-42fde982b7d7a71ef9ec32e9397b399a50415b06.zip | |
RelayCache: refactor
Diffstat (limited to 'ios/MullvadVPN/RelayCache/RelayCacheIO.swift')
| -rw-r--r-- | ios/MullvadVPN/RelayCache/RelayCacheIO.swift | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/ios/MullvadVPN/RelayCache/RelayCacheIO.swift b/ios/MullvadVPN/RelayCache/RelayCacheIO.swift new file mode 100644 index 0000000000..fc9a13dd41 --- /dev/null +++ b/ios/MullvadVPN/RelayCache/RelayCacheIO.swift @@ -0,0 +1,112 @@ +// +// RelayCacheIO.swift +// RelayCacheIO +// +// Created by pronebird on 27/07/2021. +// Copyright © 2021 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +extension RelayCache { + enum IO {} +} + +extension RelayCache.IO { + /// The default cache file location bound by app group container. + static func defaultCacheFileURL(forSecurityApplicationGroupIdentifier appGroupIdentifier: String) -> URL? { + let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) + + return containerURL?.appendingPathComponent("relays.json") + } + + /// The path to pre-bundled `relays.json` file. + static var preBundledRelaysFileURL: URL? { + return Bundle.main.url(forResource: "relays", withExtension: "json") + } + + /// Safely read the cache file from disk using file coordinator. + static func read(cacheFileURL: URL) -> Result<RelayCache.CachedRelays, RelayCache.Error> { + var result: Result<RelayCache.CachedRelays, RelayCache.Error>? + let fileCoordinator = NSFileCoordinator(filePresenter: nil) + + let accessor = { (fileURLForReading: URL) -> Void in + // Decode data from disk + result = Result { try Data(contentsOf: fileURLForReading) } + .mapError { RelayCache.Error.readCache($0) } + .flatMap { (data) in + Result { try JSONDecoder().decode(RelayCache.CachedRelays.self, from: data) } + .mapError { RelayCache.Error.decodeCache($0) } + } + } + + var error: NSError? + fileCoordinator.coordinate(readingItemAt: cacheFileURL, + options: [.withoutChanges], + error: &error, + byAccessor: accessor) + + if let error = error { + result = .failure(.readCache(error)) + } + + return result! + } + + /// Safely read the cache file from disk using file coordinator and fallback to prebundled relays in case if the + /// relay cache file is missing. + static func readWithFallback(cacheFileURL: URL, preBundledRelaysFileURL: URL) -> Result<RelayCache.CachedRelays, RelayCache.Error> { + return Self.read(cacheFileURL: cacheFileURL) + .flatMapError { (error) -> Result<RelayCache.CachedRelays, RelayCache.Error> in + switch error { + case .decodeCache, .readCache(CocoaError.fileReadNoSuchFile): + return RelayCache.IO.readPrebundledRelays(fileURL: preBundledRelaysFileURL) + default: + return .failure(error) + } + } + } + + /// Read pre-bundled relays file from disk. + static func readPrebundledRelays(fileURL: URL) -> Result<RelayCache.CachedRelays, RelayCache.Error> { + return Result { try Data(contentsOf: fileURL) } + .mapError { RelayCache.Error.readPrebundledRelays($0) } + .flatMap { (data) -> Result<RelayCache.CachedRelays, RelayCache.Error> in + return Result { try REST.Coding.makeJSONDecoder().decode(REST.ServerRelaysResponse.self, from: data) } + .mapError { RelayCache.Error.decodePrebundledRelays($0) } + .map { (relays) -> RelayCache.CachedRelays in + return RelayCache.CachedRelays( + relays: relays, + updatedAt: Date(timeIntervalSince1970: 0) + ) + } + } + } + + /// Safely write the cache file on disk using file coordinator. + static func write(cacheFileURL: URL, record: RelayCache.CachedRelays) -> Result<(), RelayCache.Error> { + var result: Result<(), RelayCache.Error>? + let fileCoordinator = NSFileCoordinator(filePresenter: nil) + + let accessor = { (fileURLForWriting: URL) -> Void in + result = Result { try JSONEncoder().encode(record) } + .mapError { RelayCache.Error.encodeCache($0) } + .flatMap { (data) in + Result { try data.write(to: fileURLForWriting) } + .mapError { RelayCache.Error.writeCache($0) } + } + } + + var error: NSError? + fileCoordinator.coordinate(writingItemAt: cacheFileURL, + options: [.forReplacing], + error: &error, + byAccessor: accessor) + + if let error = error { + result = .failure(.writeCache(error)) + } + + return result! + } +} |
