mirror of
https://github.com/RevenueCat/purchases-ios
synced 2026-11-11 22:55:55 +05:45
* Pull files from staging branch to introduce a file repository and intelligent task sharing * removed typed throws and fix some docs * Make tests satisfy predictive swift 6 failures * @_spi(Internal) public * Stop using the shared instance in an instance method * Pull cache directory logic out of file repository * update cache naming conventions * @fire-at-will feedback -> use url instead of path * Use strings file pattern * remove priority from repository * Remove RC Internal access from keyed deferred value store * Remove spi internal from cache and network service * makeSUT -> makeSystemUnderTest * remove public scope in extensions * rename function for clarity
130 lines
4.4 KiB
Swift
130 lines
4.4 KiB
Swift
//
|
|
// Copyright RevenueCat Inc. All Rights Reserved.
|
|
//
|
|
// Licensed under the MIT License (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://opensource.org/licenses/MIT
|
|
//
|
|
// FileRepository.swift
|
|
//
|
|
// Created by Jacob Zivan Rakidzich on 8/13/25.
|
|
|
|
import Foundation
|
|
|
|
/// A file cache
|
|
@_spi(Internal) public class FileRepository: FileRepositoryType, @unchecked Sendable {
|
|
let networkService: SimpleNetworkServiceType
|
|
|
|
private let store = KeyedDeferredValueStore<InputURL, OutputURL>()
|
|
private let fileManager: LargeItemCacheType
|
|
|
|
/// Create a file repository
|
|
/// - Parameters:
|
|
/// - networkService: A service capable of fetching data from a URL
|
|
/// - fileManager: A service capable of storing data and returning the URL where that stored data exists
|
|
init(
|
|
networkService: SimpleNetworkServiceType = URLSession.shared,
|
|
fileManager: LargeItemCacheType = FileManager.default
|
|
) {
|
|
self.networkService = networkService
|
|
self.fileManager = fileManager
|
|
}
|
|
|
|
/// Create a file repository
|
|
@_spi(Internal) public convenience init() {
|
|
self.init(
|
|
networkService: URLSession.shared,
|
|
fileManager: FileManager.default
|
|
)
|
|
}
|
|
|
|
/// Prefetch files at the given urls
|
|
/// - Parameter urls: An array of URL to fetch data from
|
|
@_spi(Internal) public func prefetch(urls: [InputURL]) {
|
|
for url in urls {
|
|
Task { [weak self] in
|
|
try await self?.generateOrGetCachedFileURL(for: url)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create and/or get the cached file url
|
|
/// - Parameters:
|
|
/// - url: The url for the remote data to cache into a file
|
|
@_spi(Internal) public func generateOrGetCachedFileURL(for url: InputURL) async throws -> OutputURL {
|
|
try await store.getOrPut(
|
|
Task { [weak self] in
|
|
guard let self, let cachedUrl = self.fileManager.generateLocalFilesystemURL(forRemoteURL: url) else {
|
|
Logger.error(Strings.fileRepository.failedToCreateCacheDirectory(url).description)
|
|
throw Error.failedToCreateCacheDirectory(url.absoluteString)
|
|
}
|
|
|
|
if fileManager.cachedContentExists(at: cachedUrl) {
|
|
return cachedUrl
|
|
}
|
|
|
|
let data = try await downloadFile(from: url)
|
|
try saveCachedFile(url: cachedUrl, data: data)
|
|
return cachedUrl
|
|
},
|
|
forKey: url
|
|
).value
|
|
}
|
|
|
|
private func downloadFile(from url: URL) async throws -> Data {
|
|
do {
|
|
return try await networkService.data(from: url)
|
|
} catch {
|
|
let message = Strings.fileRepository.failedToFetchFileFromRemoteSource(url, error).description
|
|
Logger.error(message)
|
|
throw Error.failedToFetchFileFromRemoteSource(message)
|
|
}
|
|
}
|
|
|
|
private func saveCachedFile(url: URL, data: Data) throws {
|
|
do {
|
|
try fileManager.saveData(data, to: url)
|
|
} catch {
|
|
let message = Strings.fileRepository.failedToSaveCachedFile(url, error).description
|
|
Logger.error(message)
|
|
throw Error.failedToSaveCachedFile(message)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A file cache
|
|
@_spi(Internal) public protocol FileRepositoryType {
|
|
|
|
/// Prefetch files at the given urls
|
|
/// - Parameter urls: An array of URL to fetch data from
|
|
func prefetch(urls: [InputURL])
|
|
|
|
/// Create and/or get the cached file url
|
|
/// - Parameters:
|
|
/// - url: The url for the remote data to cache into a file
|
|
func generateOrGetCachedFileURL(for url: InputURL) async throws -> OutputURL
|
|
}
|
|
|
|
/// The input URL is the URL that the repository will read remote data from
|
|
@_spi(Internal) public typealias InputURL = URL
|
|
|
|
/// The output URL is the local file's URL where the data can be found after caching is complete
|
|
@_spi(Internal) public typealias OutputURL = URL
|
|
|
|
extension FileRepository {
|
|
|
|
/// File repository error cases
|
|
@_spi(Internal) public enum Error: Swift.Error {
|
|
/// Used when creating the folder on disk fails
|
|
case failedToCreateCacheDirectory(String)
|
|
|
|
/// Used when saving the file on disk fails
|
|
case failedToSaveCachedFile(String)
|
|
|
|
/// Used when fetching the data fails
|
|
case failedToFetchFileFromRemoteSource(String)
|
|
}
|
|
}
|