Implement remote library metadata sync for iPhone

This commit is contained in:
diyaa
2026-05-29 08:54:09 +02:00
parent ebc187f4a1
commit 6e73c1878e
20 changed files with 1379 additions and 48 deletions
@@ -0,0 +1,74 @@
import Foundation
import VelodyDomain
public protocol RemoteLibraryStore: Actor {
func loadRemoteTracks() async throws -> [RemoteTrack]
func replaceRemoteTracks(_ tracks: [RemoteTrack]) async throws
}
public actor FileRemoteLibraryStore: RemoteLibraryStore {
private let fileURL: URL
private let fileManager: FileManager
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
public init(
fileURL: URL? = nil,
fileManager: FileManager = .default
) throws {
self.fileManager = fileManager
if let fileURL {
self.fileURL = fileURL
} else {
self.fileURL = try Self.defaultFileURL(fileManager: fileManager)
}
}
public func loadRemoteTracks() async throws -> [RemoteTrack] {
guard fileManager.fileExists(atPath: fileURL.path) else {
return []
}
let data = try Data(contentsOf: fileURL)
return try decoder.decode([RemoteTrack].self, from: data)
}
public func replaceRemoteTracks(_ tracks: [RemoteTrack]) async throws {
try fileManager.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true
)
let data = try encoder.encode(tracks)
try data.write(to: fileURL, options: .atomic)
}
private static func defaultFileURL(fileManager: FileManager) throws -> URL {
guard let applicationSupportURL = fileManager.urls(
for: .applicationSupportDirectory,
in: .userDomainMask
).first else {
throw CocoaError(.fileNoSuchFile)
}
return applicationSupportURL
.appendingPathComponent("Velody", isDirectory: true)
.appendingPathComponent("remote-library.json")
}
}
public actor InMemoryRemoteLibraryStore: RemoteLibraryStore {
private var tracks: [RemoteTrack]
public init(tracks: [RemoteTrack] = []) {
self.tracks = tracks
}
public func loadRemoteTracks() async throws -> [RemoteTrack] {
tracks
}
public func replaceRemoteTracks(_ tracks: [RemoteTrack]) async throws {
self.tracks = tracks
}
}
@@ -0,0 +1,40 @@
import Foundation
import XCTest
import VelodyDomain
@testable import VelodyPersistence
final class RemoteLibraryStoreTests: XCTestCase {
func testFileRemoteLibraryStorePersistsTracksAcrossInstances() async throws {
let fileManager = FileManager.default
let tempDirectory = fileManager.temporaryDirectory.appendingPathComponent(
UUID().uuidString,
isDirectory: true
)
let fileURL = tempDirectory.appendingPathComponent("remote-library.json")
defer {
try? fileManager.removeItem(at: tempDirectory)
}
let firstStore = try FileRemoteLibraryStore(fileURL: fileURL)
let tracks = [
RemoteTrack(
trackId: "track-123",
title: "Remote Title",
artist: "Remote Artist",
durationSeconds: 245,
sha256: String(repeating: "a", count: 64),
assetId: "asset-456",
createdAt: "2026-05-29T08:00:00.000Z",
updatedAt: "2026-05-29T08:05:00.000Z"
),
]
try await firstStore.replaceRemoteTracks(tracks)
let secondStore = try FileRemoteLibraryStore(fileURL: fileURL)
let restoredTracks = try await secondStore.loadRemoteTracks()
XCTAssertEqual(restoredTracks, tracks)
}
}