Implement offline library snapshot
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
import Foundation
|
||||
import VelodyDomain
|
||||
import VelodyPersistence
|
||||
|
||||
public actor OfflineLibraryService {
|
||||
private let syncService: RemoteLibrarySyncService
|
||||
private let audioFileStore: any OfflineAudioFileStore
|
||||
|
||||
public init(
|
||||
syncService: RemoteLibrarySyncService,
|
||||
audioFileStore: any OfflineAudioFileStore
|
||||
) {
|
||||
self.syncService = syncService
|
||||
self.audioFileStore = audioFileStore
|
||||
}
|
||||
|
||||
public func loadSnapshot() async throws -> OfflineLibrarySnapshot {
|
||||
let remoteTracks = try await syncService.loadCachedRemoteTracks()
|
||||
let downloadStates = try await syncService.loadDownloadStates()
|
||||
let downloadStatesByTrackID = Dictionary(
|
||||
uniqueKeysWithValues: downloadStates.map { ($0.remoteTrackId, $0) }
|
||||
)
|
||||
|
||||
var remoteLibraryTracks: [OfflineLibraryRemoteTrack] = []
|
||||
var availableOfflineTracks: [OfflineLibraryTrack] = []
|
||||
|
||||
for remoteTrack in remoteTracks {
|
||||
let downloadState = downloadStatesByTrackID[remoteTrack.trackId]
|
||||
let isFileAvailable = await Self.isFileAvailable(
|
||||
for: downloadState,
|
||||
audioFileStore: audioFileStore
|
||||
)
|
||||
let localFilePath = downloadState?.localFilePath ?? ""
|
||||
let downloadedAt = downloadState?.downloadedAt
|
||||
let status = Self.remoteStatus(for: downloadState, isFileAvailable: isFileAvailable)
|
||||
let lastDownloadError = Self.lastDownloadError(
|
||||
for: downloadState,
|
||||
status: status
|
||||
)
|
||||
|
||||
remoteLibraryTracks.append(
|
||||
OfflineLibraryRemoteTrack(
|
||||
remoteTrack: remoteTrack,
|
||||
localFilePath: localFilePath,
|
||||
downloadedAt: downloadedAt,
|
||||
isFileAvailable: isFileAvailable,
|
||||
status: status,
|
||||
lastDownloadError: lastDownloadError
|
||||
)
|
||||
)
|
||||
|
||||
guard isFileAvailable else {
|
||||
continue
|
||||
}
|
||||
|
||||
availableOfflineTracks.append(
|
||||
OfflineLibraryTrack(
|
||||
remoteTrackId: remoteTrack.trackId,
|
||||
assetId: remoteTrack.assetId,
|
||||
title: remoteTrack.title,
|
||||
artist: remoteTrack.artist,
|
||||
durationSeconds: remoteTrack.durationSeconds,
|
||||
sha256: remoteTrack.sha256,
|
||||
localFilePath: localFilePath,
|
||||
downloadedAt: downloadedAt,
|
||||
isFileAvailable: true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return OfflineLibrarySnapshot(
|
||||
remoteTracks: remoteLibraryTracks,
|
||||
availableTracks: availableOfflineTracks
|
||||
)
|
||||
}
|
||||
|
||||
private static func isFileAvailable(
|
||||
for downloadState: RemoteTrackDownloadState?,
|
||||
audioFileStore: any OfflineAudioFileStore
|
||||
) async -> Bool {
|
||||
guard let downloadState,
|
||||
downloadState.downloadStatus == .downloaded,
|
||||
downloadState.hasLocalFile
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
return await audioFileStore.fileExists(at: downloadState.localFilePath)
|
||||
}
|
||||
|
||||
private static func remoteStatus(
|
||||
for downloadState: RemoteTrackDownloadState?,
|
||||
isFileAvailable: Bool
|
||||
) -> OfflineLibraryRemoteTrackStatus {
|
||||
guard let downloadState else {
|
||||
return .notDownloaded
|
||||
}
|
||||
|
||||
switch downloadState.downloadStatus {
|
||||
case .notDownloaded:
|
||||
return .notDownloaded
|
||||
case .downloading:
|
||||
return .downloading
|
||||
case .downloaded:
|
||||
return isFileAvailable ? .downloaded : .missing
|
||||
case .failed:
|
||||
return .failed
|
||||
}
|
||||
}
|
||||
|
||||
private static func lastDownloadError(
|
||||
for downloadState: RemoteTrackDownloadState?,
|
||||
status: OfflineLibraryRemoteTrackStatus
|
||||
) -> String? {
|
||||
if status == .missing {
|
||||
return "The downloaded MP3 file is missing."
|
||||
}
|
||||
|
||||
return downloadState?.lastDownloadError
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user