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

452 lines
13 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 LocalTrackArtwork: Codable, Hashable, Sendable {
public var localFilePath: String
public var sha256: String
public var mimeType: String
public var width: Int?
public var height: Int?
public init(
localFilePath: String,
sha256: String,
mimeType: String,
width: Int? = nil,
height: Int? = nil
) {
self.localFilePath = localFilePath
self.sha256 = sha256
self.mimeType = mimeType
self.width = width
self.height = height
}
}
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 artwork: LocalTrackArtwork?
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,
artwork: LocalTrackArtwork? = 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.artwork = artwork
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 deviceAccessToken: String
public var bootstrapToken: String
public var serverTime: String
public init(
deviceId: String,
deviceAccessToken: String,
bootstrapToken: String,
serverTime: String
) {
self.deviceId = deviceId
self.deviceAccessToken = deviceAccessToken
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 struct ArtworkPayload: Codable, Hashable, Sendable {
public var dataBase64: String
public var sha256: String
public var mimeType: String
public var width: Int?
public var height: Int?
public init(
dataBase64: String,
sha256: String,
mimeType: String,
width: Int? = nil,
height: Int? = nil
) {
self.dataBase64 = dataBase64
self.sha256 = sha256
self.mimeType = mimeType
self.width = width
self.height = height
}
}
public var title: String
public var artist: String
public var album: String?
public var durationMs: Int?
public var artwork: ArtworkPayload?
public init(
title: String,
artist: String,
album: String? = nil,
durationMs: Int? = nil,
artwork: ArtworkPayload? = nil
) {
self.title = title
self.artist = artist
self.album = album
self.durationMs = durationMs
self.artwork = artwork
}
}
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 cursor: SyncCursor
public var entityType: String
public var entityId: String
public var action: String
public var track: RemoteTrack?
public var deletedTrackId: String?
public var createdAt: String
public init(
cursor: SyncCursor,
entityType: String,
entityId: String,
action: String,
track: RemoteTrack? = nil,
deletedTrackId: String? = nil,
createdAt: String
) {
self.cursor = cursor
self.entityType = entityType
self.entityId = entityId
self.action = action
self.track = track
self.deletedTrackId = deletedTrackId
self.createdAt = createdAt
}
private enum CodingKeys: String, CodingKey {
case cursor
case entityType
case entityId
case action
case track
case deletedTrackId
case createdAt
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
cursor = SyncCursor(
value: try container.decode(String.self, forKey: .cursor)
)
entityType = try container.decode(String.self, forKey: .entityType)
entityId = try container.decode(String.self, forKey: .entityId)
action = try container.decode(String.self, forKey: .action)
track = try container.decodeIfPresent(RemoteTrack.self, forKey: .track)
deletedTrackId = try container.decodeIfPresent(String.self, forKey: .deletedTrackId)
createdAt = try container.decode(String.self, forKey: .createdAt)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(cursor.value, forKey: .cursor)
try container.encode(entityType, forKey: .entityType)
try container.encode(entityId, forKey: .entityId)
try container.encode(action, forKey: .action)
try container.encodeIfPresent(track, forKey: .track)
try container.encodeIfPresent(deletedTrackId, forKey: .deletedTrackId)
try container.encode(createdAt, forKey: .createdAt)
}
}
public struct SyncBootstrapResponse: Codable, Hashable, Sendable {
public var nextCursor: SyncCursor
public var tracks: [RemoteTrack]
public var serverTime: String
public init(
nextCursor: SyncCursor,
tracks: [RemoteTrack],
serverTime: String
) {
self.nextCursor = nextCursor
self.tracks = tracks
self.serverTime = serverTime
}
private enum CodingKeys: String, CodingKey {
case nextCursor
case tracks
case serverTime
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nextCursor = SyncCursor(
value: try container.decode(String.self, forKey: .nextCursor)
)
tracks = try container.decode([RemoteTrack].self, forKey: .tracks)
serverTime = try container.decode(String.self, forKey: .serverTime)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(nextCursor.value, forKey: .nextCursor)
try container.encode(tracks, forKey: .tracks)
try container.encode(serverTime, forKey: .serverTime)
}
}
public struct SyncChangesResponse: Codable, Hashable, Sendable {
public var nextCursor: SyncCursor
public var hasMore: Bool
public var requiresBootstrap: Bool
public var reason: String?
public var events: [SyncEvent]
public var serverTime: String
public init(
nextCursor: SyncCursor,
hasMore: Bool,
requiresBootstrap: Bool,
reason: String? = nil,
events: [SyncEvent],
serverTime: String
) {
self.nextCursor = nextCursor
self.hasMore = hasMore
self.requiresBootstrap = requiresBootstrap
self.reason = reason
self.events = events
self.serverTime = serverTime
}
private enum CodingKeys: String, CodingKey {
case nextCursor
case hasMore
case requiresBootstrap
case reason
case events
case serverTime
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
nextCursor = SyncCursor(
value: try container.decode(String.self, forKey: .nextCursor)
)
hasMore = try container.decode(Bool.self, forKey: .hasMore)
requiresBootstrap = try container.decode(Bool.self, forKey: .requiresBootstrap)
reason = try container.decodeIfPresent(String.self, forKey: .reason)
events = try container.decode([SyncEvent].self, forKey: .events)
serverTime = try container.decode(String.self, forKey: .serverTime)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(nextCursor.value, forKey: .nextCursor)
try container.encode(hasMore, forKey: .hasMore)
try container.encode(requiresBootstrap, forKey: .requiresBootstrap)
try container.encodeIfPresent(reason, forKey: .reason)
try container.encode(events, forKey: .events)
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
}
}