Implement incremental sync and offline recovery
This commit is contained in:
@@ -176,7 +176,8 @@ final class OfflineLibraryServiceTests: XCTestCase {
|
||||
let syncService = RemoteLibrarySyncService(
|
||||
repository: DefaultRemoteLibraryRepository(
|
||||
apiClient: OfflineLibraryMockAPIClient(audioAssetData: sampleMp3Data(seed: track.assetId)),
|
||||
store: remoteLibraryStore
|
||||
store: remoteLibraryStore,
|
||||
syncCursorStore: InMemoryRemoteLibrarySyncCursorStore()
|
||||
),
|
||||
downloadStateStore: downloadStateStore,
|
||||
audioFileStore: audioFileStore,
|
||||
@@ -226,8 +227,10 @@ final class OfflineLibraryServiceTests: XCTestCase {
|
||||
let remoteLibraryStore = InMemoryRemoteLibraryStore()
|
||||
let audioData = sampleMp3Data(seed: track.assetId)
|
||||
let apiClient = OfflineLibraryMockAPIClient(
|
||||
remoteLibraryResponse: RemoteLibraryResponseDTO(
|
||||
tracks: [makeRemoteTrackDTO(from: track)]
|
||||
bootstrapResponse: SyncBootstrapResponse(
|
||||
nextCursor: SyncCursor(value: "1"),
|
||||
tracks: [track],
|
||||
serverTime: "2026-05-30T08:00:00.000Z"
|
||||
),
|
||||
audioAssetData: audioData,
|
||||
artworkDataByArtworkID: [
|
||||
@@ -240,7 +243,8 @@ final class OfflineLibraryServiceTests: XCTestCase {
|
||||
let syncService = RemoteLibrarySyncService(
|
||||
repository: DefaultRemoteLibraryRepository(
|
||||
apiClient: apiClient,
|
||||
store: remoteLibraryStore
|
||||
store: remoteLibraryStore,
|
||||
syncCursorStore: InMemoryRemoteLibrarySyncCursorStore()
|
||||
),
|
||||
downloadStateStore: downloadStateStore,
|
||||
audioFileStore: audioFileStore,
|
||||
@@ -284,8 +288,10 @@ final class OfflineLibraryServiceTests: XCTestCase {
|
||||
makeRemoteTrack(trackId: "track-2", assetId: "asset-2", title: "Track 2", artworkId: "artwork-2"),
|
||||
]
|
||||
let apiClient = OfflineLibraryMockAPIClient(
|
||||
remoteLibraryResponse: RemoteLibraryResponseDTO(
|
||||
tracks: tracks.map { makeRemoteTrackDTO(from: $0) }
|
||||
bootstrapResponse: SyncBootstrapResponse(
|
||||
nextCursor: SyncCursor(value: "1"),
|
||||
tracks: tracks,
|
||||
serverTime: "2026-05-30T08:00:00.000Z"
|
||||
),
|
||||
audioAssetDataByAssetID: [
|
||||
"asset-1": sampleMp3Data(seed: "asset-1"),
|
||||
@@ -304,7 +310,8 @@ final class OfflineLibraryServiceTests: XCTestCase {
|
||||
|
||||
let firstRepository = DefaultRemoteLibraryRepository(
|
||||
apiClient: apiClient,
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL)
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL),
|
||||
syncCursorStore: InMemoryRemoteLibrarySyncCursorStore()
|
||||
)
|
||||
let firstDownloadStateStore = try FileRemoteTrackDownloadStateStore(fileURL: downloadStateFileURL)
|
||||
let firstAudioStore = try FileOfflineAudioFileStore(baseDirectoryURL: audioDirectory)
|
||||
@@ -329,7 +336,8 @@ final class OfflineLibraryServiceTests: XCTestCase {
|
||||
|
||||
let relaunchedRepository = DefaultRemoteLibraryRepository(
|
||||
apiClient: apiClient,
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL)
|
||||
store: try FileRemoteLibraryStore(fileURL: remoteLibraryFileURL),
|
||||
syncCursorStore: InMemoryRemoteLibrarySyncCursorStore(cursor: SyncCursor(value: "1"))
|
||||
)
|
||||
let relaunchedDownloadStateStore = try FileRemoteTrackDownloadStateStore(fileURL: downloadStateFileURL)
|
||||
let relaunchedAudioStore = try FileOfflineAudioFileStore(baseDirectoryURL: audioDirectory)
|
||||
@@ -404,18 +412,22 @@ private actor InMemoryRemoteLibraryRepository: RemoteLibraryRepository {
|
||||
}
|
||||
|
||||
private struct OfflineLibraryMockAPIClient: VelodyAPIClient {
|
||||
let remoteLibraryResponse: RemoteLibraryResponseDTO?
|
||||
let bootstrapResponse: SyncBootstrapResponse
|
||||
let audioAssetData: Data?
|
||||
let audioAssetDataByAssetID: [String: Data]
|
||||
let artworkDataByArtworkID: [String: Data]
|
||||
|
||||
init(
|
||||
remoteLibraryResponse: RemoteLibraryResponseDTO? = nil,
|
||||
bootstrapResponse: SyncBootstrapResponse = SyncBootstrapResponse(
|
||||
nextCursor: SyncCursor(value: "0"),
|
||||
tracks: [],
|
||||
serverTime: "2026-05-30T08:00:00.000Z"
|
||||
),
|
||||
audioAssetData: Data? = nil,
|
||||
audioAssetDataByAssetID: [String: Data] = [:],
|
||||
artworkDataByArtworkID: [String: Data] = [:]
|
||||
) {
|
||||
self.remoteLibraryResponse = remoteLibraryResponse
|
||||
self.bootstrapResponse = bootstrapResponse
|
||||
self.audioAssetData = audioAssetData
|
||||
self.audioAssetDataByAssetID = audioAssetDataByAssetID
|
||||
self.artworkDataByArtworkID = artworkDataByArtworkID
|
||||
@@ -444,11 +456,17 @@ private struct OfflineLibraryMockAPIClient: VelodyAPIClient {
|
||||
}
|
||||
|
||||
func fetchSyncBootstrap() async throws -> SyncBootstrapResponse {
|
||||
SyncBootstrapResponse(
|
||||
nextCursor: SyncCursor(value: "0"),
|
||||
tracks: [],
|
||||
bootstrapResponse
|
||||
}
|
||||
|
||||
func fetchSyncChanges(
|
||||
cursor: SyncCursor
|
||||
) async throws -> SyncChangesResponse {
|
||||
SyncChangesResponse(
|
||||
nextCursor: cursor,
|
||||
hasMore: false,
|
||||
requiresBootstrap: false,
|
||||
events: [],
|
||||
deletedTrackIds: [],
|
||||
serverTime: "2026-05-30T08:00:00.000Z"
|
||||
)
|
||||
}
|
||||
@@ -457,7 +475,9 @@ private struct OfflineLibraryMockAPIClient: VelodyAPIClient {
|
||||
deviceId: String
|
||||
) async throws -> RemoteLibraryResponseDTO {
|
||||
_ = deviceId
|
||||
return remoteLibraryResponse ?? RemoteLibraryResponseDTO(tracks: [])
|
||||
return RemoteLibraryResponseDTO(
|
||||
tracks: bootstrapResponse.tracks.map { makeRemoteTrackDTO(from: $0) }
|
||||
)
|
||||
}
|
||||
|
||||
func downloadAudioAsset(
|
||||
|
||||
Reference in New Issue
Block a user