| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- import Foundation
- import SwiftData
- @Model
- final class CelestiaSession {
- var id: UUID
- var title: String
- var startTime: Date
- var endTime: Date?
- var localAudioPath: String?
- var isSynced: Bool
- var cloudSessionId: String?
- var ownerUserID: String?
- // Defaults must live on the persisted properties so SwiftData can backfill
- // existing rows during a lightweight migration. Initializer defaults only
- // apply to newly-created model instances.
- var serverRevision: Int64 = 0
- var syncStateRaw: String = SessionSyncState.localOnly.rawValue
- var lastSyncError: String?
- var lastSyncedAt: Date?
-
- @Relationship(deleteRule: .cascade, inverse: \CelestiaTimelineEvent.session)
- var events: [CelestiaTimelineEvent]
-
- @Relationship(deleteRule: .cascade, inverse: \AudioChunk.session)
- var audioChunks: [AudioChunk]
-
- var durationMs: Int64 {
- guard let end = endTime else { return 0 }
- return Int64(end.timeIntervalSince(startTime) * 1000)
- }
-
- var durationFormatted: String {
- guard let end = endTime else { return "正在记录..." }
- let interval = end.timeIntervalSince(startTime)
- let hours = Int(interval) / 3600
- let minutes = (Int(interval) % 3600) / 60
- let seconds = Int(interval) % 60
- if hours > 0 {
- return String(format: "%d:%02d:%02d", hours, minutes, seconds)
- }
- return String(format: "%02d:%02d", minutes, seconds)
- }
-
- var photoCount: Int {
- events.filter { $0.eventType == "PHOTO" }.count
- }
-
- var noteCount: Int {
- events.filter {
- $0.eventType == "NOTE"
- || ($0.eventType == "MARKER" && !$0.isContinuationMarker)
- }.count
- }
-
- var syncState: SessionSyncState {
- get { SessionSyncState(rawValue: syncStateRaw) ?? .localOnly }
- set { syncStateRaw = newValue.rawValue }
- }
- /// The revision represented by the current local content.
- /// Unsynced edits form the next revision based on the last known server revision.
- var localRevision: Int64 {
- if isSynced && syncState == .synced {
- return max(serverRevision, 1)
- }
- return max(serverRevision + 1, 1)
- }
- init(id: UUID = UUID(), title: String, localAudioPath: String? = nil, startTime: Date = Date()) {
- self.id = id
- self.title = title
- self.startTime = startTime
- self.endTime = nil
- self.localAudioPath = localAudioPath
- self.isSynced = false
- self.cloudSessionId = nil
- self.ownerUserID = nil
- self.serverRevision = 0
- self.syncStateRaw = SessionSyncState.localOnly.rawValue
- self.lastSyncError = nil
- self.lastSyncedAt = nil
- self.events = []
- self.audioChunks = []
- }
- }
- enum SessionSyncState: String, Codable {
- case localOnly
- case pending
- case syncing
- case synced
- case failed
- case conflict
- }
|