summaryrefslogtreecommitdiffhomepage
path: root/ios/MullvadREST/Transport/Shadowsocks/ShadowsocksConfigurationCache.swift
blob: 7482018b97119c7a9574f30f7398ec49d86a347b (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
//
//  ShadowsocksConfigurationCache.swift
//  MullvadTransport
//
//  Created by Marco Nikic on 2023-06-05.
//  Copyright © 2025 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadTypes

public protocol ShadowsocksConfigurationCacheProtocol: Sendable {
    func read() throws -> ShadowsocksConfiguration
    func write(_ configuration: ShadowsocksConfiguration) throws
    func clear() throws
}

/// Holds a shadowsocks configuration object backed by a caching mechanism shared across processes
public final class ShadowsocksConfigurationCache: ShadowsocksConfigurationCacheProtocol, @unchecked Sendable {
    private let configurationLock = NSLock()
    private var cachedConfiguration: ShadowsocksConfiguration?
    private let fileCache: FileCache<ShadowsocksConfiguration>

    public init(cacheDirectory: URL) {
        fileCache = FileCache(
            fileURL: cacheDirectory.appendingPathComponent("shadowsocks-cache.json", isDirectory: false)
        )
    }

    /// Returns configuration from memory cache if available, otherwise attempts to load it from disk cache before
    /// returning.
    public func read() throws -> ShadowsocksConfiguration {
        configurationLock.lock()
        defer { configurationLock.unlock() }

        if let cachedConfiguration {
            return cachedConfiguration
        } else {
            let readConfiguration = try fileCache.read()
            cachedConfiguration = readConfiguration
            return readConfiguration
        }
    }

    /// Replace memory cache with new configuration and attempt to persist it on disk.
    public func write(_ configuration: ShadowsocksConfiguration) throws {
        configurationLock.lock()
        defer { configurationLock.unlock() }

        cachedConfiguration = configuration
        try fileCache.write(configuration)
    }

    /// Clear cached configuration.
    public func clear() throws {
        configurationLock.lock()
        defer { configurationLock.unlock() }
        cachedConfiguration = nil
        try fileCache.clear()
    }
}