Implement artwork download and cache
This commit is contained in:
@@ -5,13 +5,16 @@ import VelodyPersistence
|
||||
public actor OfflineLibraryService {
|
||||
private let syncService: RemoteLibrarySyncService
|
||||
private let audioFileStore: any OfflineAudioFileStore
|
||||
private let artworkStore: any ArtworkStore
|
||||
|
||||
public init(
|
||||
syncService: RemoteLibrarySyncService,
|
||||
audioFileStore: any OfflineAudioFileStore
|
||||
audioFileStore: any OfflineAudioFileStore,
|
||||
artworkStore: any ArtworkStore
|
||||
) {
|
||||
self.syncService = syncService
|
||||
self.audioFileStore = audioFileStore
|
||||
self.artworkStore = artworkStore
|
||||
}
|
||||
|
||||
public func loadSnapshot() async throws -> OfflineLibrarySnapshot {
|
||||
@@ -37,6 +40,10 @@ public actor OfflineLibraryService {
|
||||
for: downloadState,
|
||||
status: status
|
||||
)
|
||||
let localArtworkFilePath = await Self.localArtworkFilePath(
|
||||
for: remoteTrack.artwork,
|
||||
artworkStore: artworkStore
|
||||
)
|
||||
|
||||
remoteLibraryTracks.append(
|
||||
OfflineLibraryRemoteTrack(
|
||||
@@ -45,7 +52,8 @@ public actor OfflineLibraryService {
|
||||
downloadedAt: downloadedAt,
|
||||
isFileAvailable: isFileAvailable,
|
||||
status: status,
|
||||
lastDownloadError: lastDownloadError
|
||||
lastDownloadError: lastDownloadError,
|
||||
localArtworkFilePath: localArtworkFilePath
|
||||
)
|
||||
)
|
||||
|
||||
@@ -63,7 +71,8 @@ public actor OfflineLibraryService {
|
||||
sha256: remoteTrack.sha256,
|
||||
localFilePath: localFilePath,
|
||||
downloadedAt: downloadedAt,
|
||||
isFileAvailable: true
|
||||
isFileAvailable: true,
|
||||
localArtworkFilePath: localArtworkFilePath
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -118,4 +127,15 @@ public actor OfflineLibraryService {
|
||||
|
||||
return downloadState?.lastDownloadError
|
||||
}
|
||||
|
||||
private static func localArtworkFilePath(
|
||||
for artwork: RemoteArtwork?,
|
||||
artworkStore: any ArtworkStore
|
||||
) async -> String? {
|
||||
guard let artwork else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return await artworkStore.cachedFilePath(for: artwork)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ public protocol RemoteLibraryRepository: Actor {
|
||||
func loadCachedRemoteTracks() async throws -> [RemoteTrack]
|
||||
func syncRemoteTracks(deviceId: String) async throws -> [RemoteTrack]
|
||||
func downloadAudioAsset(assetId: String, deviceId: String) async throws -> Data
|
||||
func downloadArtwork(artworkId: String, deviceId: String) async throws -> Data
|
||||
}
|
||||
|
||||
public actor DefaultRemoteLibraryRepository: RemoteLibraryRepository {
|
||||
@@ -38,4 +39,11 @@ public actor DefaultRemoteLibraryRepository: RemoteLibraryRepository {
|
||||
) async throws -> Data {
|
||||
try await apiClient.downloadAudioAsset(assetId: assetId, deviceId: deviceId)
|
||||
}
|
||||
|
||||
public func downloadArtwork(
|
||||
artworkId: String,
|
||||
deviceId: String
|
||||
) async throws -> Data {
|
||||
try await apiClient.downloadArtwork(artworkId: artworkId, deviceId: deviceId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@ public actor RemoteLibrarySyncService {
|
||||
private let repository: any RemoteLibraryRepository
|
||||
private let downloadStateStore: any RemoteTrackDownloadStateStore
|
||||
private let audioFileStore: any OfflineAudioFileStore
|
||||
private let artworkStore: any ArtworkStore
|
||||
|
||||
public init(
|
||||
repository: any RemoteLibraryRepository,
|
||||
downloadStateStore: any RemoteTrackDownloadStateStore,
|
||||
audioFileStore: any OfflineAudioFileStore
|
||||
audioFileStore: any OfflineAudioFileStore,
|
||||
artworkStore: any ArtworkStore
|
||||
) {
|
||||
self.repository = repository
|
||||
self.downloadStateStore = downloadStateStore
|
||||
self.audioFileStore = audioFileStore
|
||||
self.artworkStore = artworkStore
|
||||
}
|
||||
|
||||
public func loadCachedRemoteTracks() async throws -> [RemoteTrack] {
|
||||
@@ -29,6 +32,7 @@ public actor RemoteLibrarySyncService {
|
||||
public func syncRemoteLibrary(deviceId: String) async throws -> [RemoteTrack] {
|
||||
let tracks = try await repository.syncRemoteTracks(deviceId: deviceId)
|
||||
try await ensureDownloadStates(for: tracks)
|
||||
await cacheArtwork(for: tracks, deviceId: deviceId)
|
||||
return tracks
|
||||
}
|
||||
|
||||
@@ -181,4 +185,29 @@ public actor RemoteLibrarySyncService {
|
||||
|
||||
return reconciledStates
|
||||
}
|
||||
|
||||
private func cacheArtwork(
|
||||
for tracks: [RemoteTrack],
|
||||
deviceId: String
|
||||
) async {
|
||||
for track in tracks {
|
||||
guard let artwork = track.artwork else {
|
||||
continue
|
||||
}
|
||||
|
||||
if await artworkStore.cachedFilePath(for: artwork) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
do {
|
||||
let artworkData = try await repository.downloadArtwork(
|
||||
artworkId: artwork.artworkId,
|
||||
deviceId: deviceId
|
||||
)
|
||||
_ = try await artworkStore.saveArtwork(artworkData, artwork: artwork)
|
||||
} catch {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user