velody/packages/apple/VelodyDomain/Sources/VelodyDomain/Models.swift

338 lines
9.1 KiB
Swift

import Foundation
public enum DevicePlatform: String, Codable, Sendable, CaseIterable {
case macos = "MACOS"
case iphone = "IPHONE"
}
public enum LocalUploadStatus: String, Codable, Hashable, Sendable, CaseIterable {
case localOnly
case preparing
case uploading
case uploaded
case failed
}
public struct LibraryTrack: Identifiable, Codable, Hashable, Sendable {
public let id: String
public var title: String
public var artist: String
public var album: String?
public var durationSeconds: Double?
public var localFilePath: String
public var sha256: String?
public var uploadStatus: LocalUploadStatus?
public var remoteTrackId: String?
public var lastUploadError: String?
public init(
id: String = UUID().uuidString,
title: String,
artist: String = "Unknown Artist",
album: String? = nil,
durationSeconds: Double? = nil,
localFilePath: String = "",
sha256: String? = nil,
uploadStatus: LocalUploadStatus? = nil,
remoteTrackId: String? = nil,
lastUploadError: String? = nil
) {
self.id = id
self.title = title
self.artist = artist
self.album = album
self.durationSeconds = durationSeconds
self.localFilePath = localFilePath
self.sha256 = sha256
self.uploadStatus = uploadStatus
self.remoteTrackId = remoteTrackId
self.lastUploadError = lastUploadError
}
}
public struct DeviceRegistrationPayload: Codable, Hashable, Sendable {
public var platform: DevicePlatform
public var deviceName: String
public var appVersion: String
public init(
platform: DevicePlatform,
deviceName: String,
appVersion: String
) {
self.platform = platform
self.deviceName = deviceName
self.appVersion = appVersion
}
}
public struct DeviceRegistrationResponse: Codable, Hashable, Sendable {
public var deviceId: String
public var bootstrapToken: String
public var serverTime: String
public init(
deviceId: String,
bootstrapToken: String,
serverTime: String
) {
self.deviceId = deviceId
self.bootstrapToken = bootstrapToken
self.serverTime = serverTime
}
}
public struct DeviceHeartbeatPayload: Codable, Hashable, Sendable {
public var deviceId: String
public var appVersion: String
public init(
deviceId: String,
appVersion: String
) {
self.deviceId = deviceId
self.appVersion = appVersion
}
}
public struct DeviceHeartbeatResponse: Codable, Hashable, Sendable {
public var ok: Bool
public var serverTime: String
public init(
ok: Bool,
serverTime: String
) {
self.ok = ok
self.serverTime = serverTime
}
}
public enum UploadPrepareStatus: String, Codable, Hashable, Sendable {
case exists
case uploadRequired = "upload_required"
}
public struct UploadPrepareRequest: Codable, Hashable, Sendable {
public var deviceId: String
public var sha256: String
public var originalFilename: String
public var sizeBytes: Int
public init(
deviceId: String,
sha256: String,
originalFilename: String,
sizeBytes: Int
) {
self.deviceId = deviceId
self.sha256 = sha256
self.originalFilename = originalFilename
self.sizeBytes = sizeBytes
}
}
public struct UploadPrepareResponse: Codable, Hashable, Sendable {
public var status: UploadPrepareStatus
public var uploadId: String?
public var nextOffset: Int?
public var trackId: String?
public var assetId: String?
public init(
status: UploadPrepareStatus,
uploadId: String? = nil,
nextOffset: Int? = nil,
trackId: String? = nil,
assetId: String? = nil
) {
self.status = status
self.uploadId = uploadId
self.nextOffset = nextOffset
self.trackId = trackId
self.assetId = assetId
}
}
public enum UploadSessionState: String, Codable, Hashable, Sendable {
case pending = "PENDING"
case readyToUpload = "READY_TO_UPLOAD"
case completed = "COMPLETED"
case failed = "FAILED"
}
public struct UploadSessionStatusResponse: Codable, Hashable, Sendable {
public var uploadId: String
public var status: UploadSessionState
public var receivedBytes: String
public var expectedSizeBytes: String
public var nextOffset: String
public var finalizedAt: String?
public init(
uploadId: String,
status: UploadSessionState,
receivedBytes: String,
expectedSizeBytes: String,
nextOffset: String,
finalizedAt: String? = nil
) {
self.uploadId = uploadId
self.status = status
self.receivedBytes = receivedBytes
self.expectedSizeBytes = expectedSizeBytes
self.nextOffset = nextOffset
self.finalizedAt = finalizedAt
}
}
public struct UploadFinalizeRequest: Codable, Hashable, Sendable {
public var title: String
public var artist: String
public var album: String?
public var durationMs: Int?
public init(
title: String,
artist: String,
album: String? = nil,
durationMs: Int? = nil
) {
self.title = title
self.artist = artist
self.album = album
self.durationMs = durationMs
}
}
public struct UploadFinalizeResponse: Codable, Hashable, Sendable {
public var trackId: String
public var assetId: String
public init(
trackId: String,
assetId: String
) {
self.trackId = trackId
self.assetId = assetId
}
}
public struct SyncCursor: Codable, Hashable, Sendable {
public var value: String
public init(value: String = "0") {
self.value = value
}
}
public struct SyncEvent: Codable, Hashable, Sendable {
public var entityType: String
public var entityId: String
public var action: String
public var eventId: String
public init(
entityType: String,
entityId: String,
action: String,
eventId: String
) {
self.entityType = entityType
self.entityId = entityId
self.action = action
self.eventId = eventId
}
}
public struct SyncBootstrapResponse: Codable, Hashable, Sendable {
public var nextCursor: SyncCursor
public var tracks: [LibraryTrack]
public var events: [SyncEvent]
public var deletedTrackIds: [String]
public var serverTime: String
public init(
nextCursor: SyncCursor,
tracks: [LibraryTrack],
events: [SyncEvent],
deletedTrackIds: [String],
serverTime: String
) {
self.nextCursor = nextCursor
self.tracks = tracks
self.events = events
self.deletedTrackIds = deletedTrackIds
self.serverTime = serverTime
}
private enum CodingKeys: String, CodingKey {
case nextCursor
case tracks
case events
case deletedTrackIds
case serverTime
}
private struct WireTrack: Codable {
var id: String?
var title: String?
var artist: String?
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let wireTracks = try container.decode([WireTrack].self, forKey: .tracks)
nextCursor = SyncCursor(
value: try container.decode(String.self, forKey: .nextCursor)
)
tracks = wireTracks.map { track in
LibraryTrack(
id: track.id ?? UUID().uuidString,
title: track.title ?? "Unknown Title",
artist: track.artist ?? "Unknown Artist",
album: nil,
durationSeconds: nil,
localFilePath: "",
sha256: nil
)
}
events = try container.decode([SyncEvent].self, forKey: .events)
deletedTrackIds = try container.decode([String].self, forKey: .deletedTrackIds)
serverTime = try container.decode(String.self, forKey: .serverTime)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let wireTracks = tracks.map { track in
WireTrack(
id: track.id,
title: track.title,
artist: track.artist
)
}
try container.encode(nextCursor.value, forKey: .nextCursor)
try container.encode(wireTracks, forKey: .tracks)
try container.encode(events, forKey: .events)
try container.encode(deletedTrackIds, forKey: .deletedTrackIds)
try container.encode(serverTime, forKey: .serverTime)
}
}
public struct ServerEnvironment: Codable, Hashable, Sendable {
public static let defaultLocalBaseURL = URL(string: "http://localhost:3007")!
public var baseURL: URL
public var appVersion: String
public init(
baseURL: URL = ServerEnvironment.defaultLocalBaseURL,
appVersion: String
) {
self.baseURL = baseURL
self.appVersion = appVersion
}
}