From 3f0621ed79cddbee08541ff622830b0a60a02319 Mon Sep 17 00:00:00 2001 From: Alberto Sendra Date: Fri, 29 Nov 2024 10:24:07 +0100 Subject: [PATCH 1/2] CacheStorage setup --- .../Cache/GRDBCache/GRDBCacheStorage.swift | 50 ++++++++++++------- .../Internal/InternalPlayerLoader.swift | 17 ++++--- .../Internal/PlayerLoader.swift | 3 +- .../Player/PlaybackEngine/PlayerEngine.swift | 3 +- Sources/Player/Player.swift | 27 +++++++++- .../Internal/Cache/CacheStorageMock.swift | 24 +++++++++ .../Internal/PlayerLoaderMock.swift | 7 ++- Tests/PlayerTests/Mocks/Player+Mock.swift | 6 ++- .../Internal/InternalPlayerLoaderTests.swift | 3 +- Tests/PlayerTests/Player/PlayerTests.swift | 16 ++++-- Tests/PlayerTests/Playlog/PlayLogTests.swift | 13 +++-- 11 files changed, 122 insertions(+), 47 deletions(-) create mode 100644 Tests/PlayerTests/Mocks/PlaybackEngine/Internal/Cache/CacheStorageMock.swift diff --git a/Sources/Player/PlaybackEngine/Internal/Cache/GRDBCache/GRDBCacheStorage.swift b/Sources/Player/PlaybackEngine/Internal/Cache/GRDBCache/GRDBCacheStorage.swift index a58fedd4..bd2f1f53 100644 --- a/Sources/Player/PlaybackEngine/Internal/Cache/GRDBCache/GRDBCacheStorage.swift +++ b/Sources/Player/PlaybackEngine/Internal/Cache/GRDBCache/GRDBCacheStorage.swift @@ -8,7 +8,28 @@ final class GRDBCacheStorage { init(dbQueue: DatabaseQueue) { self.dbQueue = dbQueue - try? initializeDatabase() + } + + static func initializeDatabase(dbQueue: DatabaseQueue) throws { + try dbQueue.write { db in + if try !db.tableExists(CacheEntryGRDBEntity.databaseTableName) { + try db.create(table: CacheEntryGRDBEntity.databaseTableName) { t in + t.column("key", .text).primaryKey() + t.column("type", .text).notNull() + t.column("url", .text).notNull() + t.column("lastAccessedAt", .datetime).notNull() + t.column("size", .integer).notNull() + } + } + } + } + + static func withDefaultDatabase() throws -> GRDBCacheStorage { + let databaseURL = try GRDBCacheStorage.databaseURL() + let dbQueue = try DatabaseQueue(path: databaseURL.path) + try GRDBCacheStorage.initializeDatabase(dbQueue: dbQueue) + + return GRDBCacheStorage(dbQueue: dbQueue) } } @@ -100,23 +121,14 @@ extension GRDBCacheStorage: CacheStorage { } private extension GRDBCacheStorage { - func initializeDatabase() throws { - do { - try dbQueue.write { db in - if try !db.tableExists(CacheEntryGRDBEntity.databaseTableName) { - try db.create(table: CacheEntryGRDBEntity.databaseTableName) { t in - t.column("key", .text).primaryKey() - t.column("type", .text).notNull() - t.column("url", .text).notNull() - t.column("lastAccessedAt", .datetime).notNull() - t.column("size", .integer).notNull() - } - } - } - } catch { - // TODO: Log error - print("Failed to initialize table \(CacheEntryGRDBEntity.databaseTableName): \(error)") - throw error - } + static func databaseURL() throws -> URL { + let appSupportURL = PlayerWorld.fileManagerClient.applicationSupportDirectory() + let directoryURL = appSupportURL.appendingPathComponent("PlayerCacheDatabase", isDirectory: true) + try PlayerWorld.fileManagerClient.createDirectory( + at: directoryURL, + withIntermediateDirectories: true, + attributes: [FileAttributeKey.protectionKey: URLFileProtection.none] + ) + return directoryURL.appendingPathComponent("db.sqlite") } } diff --git a/Sources/Player/PlaybackEngine/Internal/InternalPlayerLoader.swift b/Sources/Player/PlaybackEngine/Internal/InternalPlayerLoader.swift index 08e7d85d..f18700f3 100644 --- a/Sources/Player/PlaybackEngine/Internal/InternalPlayerLoader.swift +++ b/Sources/Player/PlaybackEngine/Internal/InternalPlayerLoader.swift @@ -11,10 +11,9 @@ typealias MainPlayerType = GenericMediaPlayer & LiveMediaPlayer & UCMediaPlayer final class InternalPlayerLoader: PlayerLoader { private let configuration: Configuration private let fairPlayLicenseFetcher: FairPlayLicenseFetcher - private let credentialsProvider: CredentialsProvider - private let featureFlagProvider: FeatureFlagProvider + private let cacheStorage: CacheStorage? let mainPlayer: MainPlayerType var players: [GenericMediaPlayer] = [] @@ -29,20 +28,22 @@ final class InternalPlayerLoader: PlayerLoader { required init( with configuration: Configuration, - and fairplayLicenseFetcher: FairPlayLicenseFetcher, + and fairPlayLicenseFetcher: FairPlayLicenseFetcher, featureFlagProvider: FeatureFlagProvider, credentialsProvider: CredentialsProvider, mainPlayer: MainPlayerType.Type, - externalPlayers: [GenericMediaPlayer.Type] + externalPlayers: [GenericMediaPlayer.Type], + cacheStorage: CacheStorage? ) { self.configuration = configuration - fairPlayLicenseFetcher = fairplayLicenseFetcher + self.fairPlayLicenseFetcher = fairPlayLicenseFetcher self.credentialsProvider = credentialsProvider self.featureFlagProvider = featureFlagProvider + self.cacheStorage = cacheStorage - let fileManager = PlayerWorld.fileManagerClient + let cachePath = PlayerWorld.fileManagerClient.cachesDirectory() self.mainPlayer = mainPlayer.init( - cachePath: fileManager.cachesDirectory(), + cachePath: cachePath, featureFlagProvider: featureFlagProvider ) @@ -51,7 +52,7 @@ final class InternalPlayerLoader: PlayerLoader { externalPlayers.forEach { externalPlayerType in registerPlayer( externalPlayerType.init( - cachePath: fileManager.cachesDirectory(), + cachePath: cachePath, featureFlagProvider: featureFlagProvider ) ) diff --git a/Sources/Player/PlaybackEngine/Internal/PlayerLoader.swift b/Sources/Player/PlaybackEngine/Internal/PlayerLoader.swift index bdade251..fd1ec158 100644 --- a/Sources/Player/PlaybackEngine/Internal/PlayerLoader.swift +++ b/Sources/Player/PlaybackEngine/Internal/PlayerLoader.swift @@ -25,7 +25,8 @@ protocol PlayerLoader: AnyObject { featureFlagProvider: FeatureFlagProvider, credentialsProvider: CredentialsProvider, mainPlayer: MainPlayerType.Type, - externalPlayers: [GenericMediaPlayer.Type] + externalPlayers: [GenericMediaPlayer.Type], + cacheStorage: CacheStorage? ) func load(_ playableStorageMediaProduct: PlayableOfflinedMediaProduct) async throws -> Asset diff --git a/Sources/Player/PlaybackEngine/PlayerEngine.swift b/Sources/Player/PlaybackEngine/PlayerEngine.swift index e79f1e72..13f8a89d 100644 --- a/Sources/Player/PlaybackEngine/PlayerEngine.swift +++ b/Sources/Player/PlaybackEngine/PlayerEngine.swift @@ -105,7 +105,8 @@ final class PlayerEngine { _ configuration: Configuration, _ playerEventSender: PlayerEventSender, _ networkMonitor: NetworkMonitor, - _ offlineStorage: OfflineStorage?, + _ offlineStorage: OfflineStorage, + _ cacheStorage: CacheStorage?, _ offlinePlaybackPrivilegeCheck: (() -> Bool)?, _ playerLoader: PlayerLoader, _ featureFlagProvider: FeatureFlagProvider, diff --git a/Sources/Player/Player.swift b/Sources/Player/Player.swift index 82425e55..1fe09c24 100644 --- a/Sources/Player/Player.swift +++ b/Sources/Player/Player.swift @@ -23,6 +23,9 @@ public final class Player { @Atomic private(set) var offlineStorage: OfflineStorage + @Atomic + private(set) var cacheStorage: CacheStorage? + @Atomic private(set) var offlineEngine: OfflineEngine @@ -64,6 +67,7 @@ public final class Player { urlSession: URLSession, configuration: Configuration, offlineStorage: OfflineStorage, + cacheStorage: CacheStorage?, djProducer: DJProducer, playerEventSender: PlayerEventSender, fairplayLicenseFetcher: FairPlayLicenseFetcher, @@ -81,6 +85,7 @@ public final class Player { playerURLSession = urlSession self.configuration = configuration self.offlineStorage = offlineStorage + self.cacheStorage = cacheStorage self.djProducer = djProducer self.fairplayLicenseFetcher = fairplayLicenseFetcher self.streamingPrivilegesHandler = streamingPrivilegesHandler @@ -141,6 +146,14 @@ public extension Player { return nil } + let cacheStorage: CacheStorage + do { + cacheStorage = try Player.initializedCacheStorage() + } catch { + PlayerWorld.logger?.log(loggable: PlayerLoggable.withDefaultDatabase(error: error)) + return nil + } + Time.initialise() let timeoutPolicy = TimeoutPolicy.standard @@ -204,6 +217,7 @@ public extension Player { sharedPlayerURLSession, configuration, offlineStorage, + cacheStorage, djProducer, fairplayLicenseFetcher, networkMonitor, @@ -220,6 +234,7 @@ public extension Player { urlSession: sharedPlayerURLSession, configuration: configuration, offlineStorage: offlineStorage, + cacheStorage: cacheStorage, djProducer: djProducer, playerEventSender: playerEventSender, fairplayLicenseFetcher: fairplayLicenseFetcher, @@ -413,6 +428,7 @@ private extension Player { playerURLSession, configuration, offlineStorage, + cacheStorage, djProducer, fairplayLicenseFetcher, networkMonitor, @@ -428,7 +444,8 @@ private extension Player { static func newPlayerEngine( _ urlSession: URLSession, _ configuration: Configuration, - _ offlineStorage: OfflineStorage?, + _ offlineStorage: OfflineStorage, + _ cacheStorage: CacheStorage?, _ djProducer: DJProducer, _ fairplayLicenseFetcher: FairPlayLicenseFetcher, _ networkMonitor: NetworkMonitor, @@ -445,7 +462,8 @@ private extension Player { featureFlagProvider: featureFlagProvider, credentialsProvider: credentialsProvider, mainPlayer: Player.mainPlayerType(), - externalPlayers: externalPlayersSupplier?() ?? [] + externalPlayers: externalPlayersSupplier?() ?? [], + cacheStorage: cacheStorage ) let playerInstance = PlayerEngine( @@ -458,6 +476,7 @@ private extension Player { playerEventSender, networkMonitor, offlineStorage, + cacheStorage, offlinePlaybackPrivilegeCheck, internalPlayerLoader, featureFlagProvider, @@ -471,6 +490,10 @@ private extension Player { try GRDBOfflineStorage.withDefaultDatabase() } + static func initializedCacheStorage() throws -> CacheStorage { + try GRDBCacheStorage.withDefaultDatabase() + } + static func newOfflineEngine( _ storage: OfflineStorage, _ configuration: Configuration, diff --git a/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/Cache/CacheStorageMock.swift b/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/Cache/CacheStorageMock.swift new file mode 100644 index 00000000..27cbc62e --- /dev/null +++ b/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/Cache/CacheStorageMock.swift @@ -0,0 +1,24 @@ +import Foundation +@testable import Player + +final class CacheStorageMock: CacheStorage { + func save(_ entry: CacheEntry) throws {} + + func get(key: String) throws -> CacheEntry? { + nil + } + + func delete(key: String) throws {} + + func update(_ entry: CacheEntry) throws {} + + func getAll() throws -> [CacheEntry] { + [] + } + + func totalSize() throws -> Int { + 0 + } + + func pruneToSize(_ maxSize: Int) throws {} +} diff --git a/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/PlayerLoaderMock.swift b/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/PlayerLoaderMock.swift index d9301ff6..f9df54f1 100644 --- a/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/PlayerLoaderMock.swift +++ b/Tests/PlayerTests/Mocks/PlaybackEngine/Internal/PlayerLoaderMock.swift @@ -18,15 +18,14 @@ final class PlayerLoaderMock: PlayerLoader { featureFlagProvider: FeatureFlagProvider = .mock, credentialsProvider: CredentialsProvider = CredentialsProviderMock(), mainPlayer: MainPlayerType.Type = PlayerMock.self, - externalPlayers: [GenericMediaPlayer.Type] = [] + externalPlayers: [GenericMediaPlayer.Type] = [], + cacheStorage: CacheStorage? = nil ) {} convenience init() { self.init( with: Configuration.mock(), - and: FairPlayLicenseFetcher.mock(), - featureFlagProvider: .mock, - externalPlayers: [] + and: FairPlayLicenseFetcher.mock() ) } diff --git a/Tests/PlayerTests/Mocks/Player+Mock.swift b/Tests/PlayerTests/Mocks/Player+Mock.swift index f1db685e..c3f0fe15 100644 --- a/Tests/PlayerTests/Mocks/Player+Mock.swift +++ b/Tests/PlayerTests/Mocks/Player+Mock.swift @@ -16,7 +16,8 @@ extension PlayerEngine { configuration: Configuration = Configuration.mock(), playerEventSender: PlayerEventSender = PlayerEventSenderMock(), networkMonitor: NetworkMonitor = NetworkMonitorMock(), - storage: OfflineStorage = OfflineStorageMock(), + offlineStorage: OfflineStorage = OfflineStorageMock(), + cacheStorage: CacheStorage? = CacheStorageMock(), playerLoader: PlayerLoader = PlayerLoaderMock(), featureFlagProvider: FeatureFlagProvider = .mock, notificationsHandler: NotificationsHandler? = .mock(queue: DispatchQueue(label: "com.tidal.queue.for.testing")) @@ -30,7 +31,8 @@ extension PlayerEngine { configuration, playerEventSender, networkMonitor, - storage, + offlineStorage, + cacheStorage, nil, playerLoader, featureFlagProvider, diff --git a/Tests/PlayerTests/Player/PlaybackEngine/Internal/InternalPlayerLoaderTests.swift b/Tests/PlayerTests/Player/PlaybackEngine/Internal/InternalPlayerLoaderTests.swift index 8d06289f..e6cc8600 100644 --- a/Tests/PlayerTests/Player/PlaybackEngine/Internal/InternalPlayerLoaderTests.swift +++ b/Tests/PlayerTests/Player/PlaybackEngine/Internal/InternalPlayerLoaderTests.swift @@ -30,7 +30,8 @@ final class InternalPlayerLoaderTests: XCTestCase { featureFlagProvider: FeatureFlagProvider.mock, credentialsProvider: CredentialsProviderMock(), mainPlayer: PlayerMock.self, - externalPlayers: [] + externalPlayers: [], + cacheStorage: CacheStorageMock() ) mockPlayer = internalLoader.getMainPlayerInstance() as? PlayerMock } diff --git a/Tests/PlayerTests/Player/PlayerTests.swift b/Tests/PlayerTests/Player/PlayerTests.swift index 45d27c9c..23a444e0 100644 --- a/Tests/PlayerTests/Player/PlayerTests.swift +++ b/Tests/PlayerTests/Player/PlayerTests.swift @@ -8,7 +8,8 @@ import XCTest final class PlayerTests: XCTestCase { private var player: Player! private var playerEventSender: PlayerEventSenderMock! - private var dbQueue: DatabaseQueue! + private var offlineDbQueue: DatabaseQueue! + private var cachedDQueue: DatabaseQueue! override func setUpWithError() throws { PlayerWorld = PlayerWorldClient.mock(developmentFeatureFlagProvider: DevelopmentFeatureFlagProvider.mock) @@ -33,8 +34,12 @@ final class PlayerTests: XCTestCase { dataWriter: dataWriter ) - dbQueue = try DatabaseQueue() - let storage = GRDBOfflineStorage(dbQueue: dbQueue) + offlineDbQueue = try DatabaseQueue() + let offlineStorage = GRDBOfflineStorage(dbQueue: offlineDbQueue) + + cachedDQueue = try DatabaseQueue() + let cacheStorage = GRDBCacheStorage(dbQueue: cachedDQueue) + let fairplayLicenseFetcher = FairPlayLicenseFetcher.mock() let networkMonitor = NetworkMonitorMock() @@ -51,7 +56,7 @@ final class PlayerTests: XCTestCase { let notificationsHandler = NotificationsHandler.mock() let offlineEngine = OfflineEngine.mock( - storage: storage, + storage: offlineStorage, playerEventSender: playerEventSender, notificationsHandler: notificationsHandler ) @@ -61,7 +66,8 @@ final class PlayerTests: XCTestCase { queue: OperationQueueMock(), urlSession: urlSession, configuration: configuration, - offlineStorage: storage, + offlineStorage: offlineStorage, + cacheStorage: cacheStorage, djProducer: djProducer, playerEventSender: playerEventSender, fairplayLicenseFetcher: fairplayLicenseFetcher, diff --git a/Tests/PlayerTests/Playlog/PlayLogTests.swift b/Tests/PlayerTests/Playlog/PlayLogTests.swift index a9ba0107..519857fc 100644 --- a/Tests/PlayerTests/Playlog/PlayLogTests.swift +++ b/Tests/PlayerTests/Playlog/PlayLogTests.swift @@ -38,7 +38,8 @@ final class PlayLogTests: XCTestCase { private var fairplayLicenseFetcher: FairPlayLicenseFetcher! private var djProducer: DJProducer! private var playerLoader: InternalPlayerLoader! - private var storage: OfflineStorage! + private var offlineStorage: OfflineStorage! + private var cacheStorage: CacheStorage! private var networkMonitor: NetworkMonitorMock! private var configuration: Configuration! private var notificationsHandler: NotificationsHandler! @@ -118,7 +119,9 @@ final class PlayLogTests: XCTestCase { featureFlagProvider: featureFlagProvider ) - storage = OfflineStorageMock() + offlineStorage = OfflineStorageMock() + cacheStorage = CacheStorageMock() + fairplayLicenseFetcher = FairPlayLicenseFetcher.mock( httpClient: HttpClient(using: urlSession), credentialsProvider: credentialsProvider, @@ -1508,7 +1511,8 @@ private extension PlayLogTests { featureFlagProvider: featureFlagProvider, credentialsProvider: credentialsProvider, mainPlayer: Player.mainPlayerType(), - externalPlayers: [] + externalPlayers: [], + cacheStorage: cacheStorage ) let operationQueue = OperationQueue() @@ -1525,7 +1529,8 @@ private extension PlayLogTests { configuration: configuration, playerEventSender: playerEventSender, networkMonitor: networkMonitor, - storage: storage, + offlineStorage: offlineStorage, + cacheStorage: cacheStorage, playerLoader: playerLoader, featureFlagProvider: featureFlagProvider, notificationsHandler: notificationsHandler From e53fd84114eaa72b5d4a75c534a3e1eabf54b2f3 Mon Sep 17 00:00:00 2001 From: Alberto Sendra Date: Fri, 29 Nov 2024 12:21:00 +0100 Subject: [PATCH 2/2] Fix UTs --- .../Internal/Cache/DBCache/GRDBCacheStorageTests.swift | 2 +- Tests/PlayerTests/Player/PlayerTests.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/PlayerTests/Player/PlaybackEngine/Internal/Cache/DBCache/GRDBCacheStorageTests.swift b/Tests/PlayerTests/Player/PlaybackEngine/Internal/Cache/DBCache/GRDBCacheStorageTests.swift index c4bf0227..e056b2b1 100644 --- a/Tests/PlayerTests/Player/PlaybackEngine/Internal/Cache/DBCache/GRDBCacheStorageTests.swift +++ b/Tests/PlayerTests/Player/PlaybackEngine/Internal/Cache/DBCache/GRDBCacheStorageTests.swift @@ -39,7 +39,7 @@ final class GRDBCacheStorageTests: XCTestCase { override func setUpWithError() throws { // Create an in-memory database for testing dbQueue = try DatabaseQueue() - + try GRDBCacheStorage.initializeDatabase(dbQueue: dbQueue) cacheStorage = GRDBCacheStorage(dbQueue: dbQueue) } diff --git a/Tests/PlayerTests/Player/PlayerTests.swift b/Tests/PlayerTests/Player/PlayerTests.swift index 23a444e0..3833b55f 100644 --- a/Tests/PlayerTests/Player/PlayerTests.swift +++ b/Tests/PlayerTests/Player/PlayerTests.swift @@ -9,7 +9,7 @@ final class PlayerTests: XCTestCase { private var player: Player! private var playerEventSender: PlayerEventSenderMock! private var offlineDbQueue: DatabaseQueue! - private var cachedDQueue: DatabaseQueue! + private var cachedDbQueue: DatabaseQueue! override func setUpWithError() throws { PlayerWorld = PlayerWorldClient.mock(developmentFeatureFlagProvider: DevelopmentFeatureFlagProvider.mock) @@ -37,8 +37,8 @@ final class PlayerTests: XCTestCase { offlineDbQueue = try DatabaseQueue() let offlineStorage = GRDBOfflineStorage(dbQueue: offlineDbQueue) - cachedDQueue = try DatabaseQueue() - let cacheStorage = GRDBCacheStorage(dbQueue: cachedDQueue) + cachedDbQueue = try DatabaseQueue() + let cacheStorage = GRDBCacheStorage(dbQueue: cachedDbQueue) let fairplayLicenseFetcher = FairPlayLicenseFetcher.mock() let networkMonitor = NetworkMonitorMock()