This commit is contained in:
JinJiangHuang
2026-03-03 12:28:02 +08:00
parent aebf17dfb5
commit 76ed32ccf3
7 changed files with 568 additions and 149 deletions
+128 -52
View File
@@ -3,6 +3,13 @@ import SwiftData
/// SwiftData - Android Room
/// /source + vodId
private func makeVodBusinessKey(vodId: String, sourceKey: String) -> String {
let normalizedVodId = vodId.trimmingCharacters(in: .whitespacesAndNewlines)
let normalizedSourceKey = sourceKey.trimmingCharacters(in: .whitespacesAndNewlines)
return "\(normalizedSourceKey)::\(normalizedVodId)"
}
///
struct VodPlaybackState: Codable {
/// 线
@@ -16,6 +23,8 @@ struct VodPlaybackState: Codable {
///
@Model
final class VodCollect {
/// sourceKey + vodId
var bizKey: String = ""
/// ID sourceKey
var vodId: String = ""
///
@@ -28,6 +37,7 @@ final class VodCollect {
var updateTime: Date = Date()
init(vodId: String, vodName: String, vodPic: String, sourceKey: String) {
self.bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
self.vodId = vodId
self.vodName = vodName
self.vodPic = vodPic
@@ -39,6 +49,8 @@ final class VodCollect {
///
@Model
final class VodRecord {
/// sourceKey + vodId
var bizKey: String = ""
/// ID
var vodId: String = ""
///
@@ -55,6 +67,7 @@ final class VodRecord {
var updateTime: Date = Date()
init(vodId: String, vodName: String, vodPic: String, sourceKey: String, playNote: String = "") {
self.bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
self.vodId = vodId
self.vodName = vodName
self.vodPic = vodPic
@@ -89,50 +102,64 @@ actor CacheStore {
@MainActor
func addCollect(_ video: Movie.Video, context: ModelContext) {
//
let vodId = video.id
let sourceKey = video.sourceKey
let predicate = #Predicate<VodCollect> { item in
item.vodId == vodId && item.sourceKey == sourceKey
}
let descriptor = FetchDescriptor<VodCollect>(predicate: predicate)
let bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
if let existing = try? context.fetch(descriptor), !existing.isEmpty {
return //
do {
let matched = try fetchCollects(vodId: vodId, sourceKey: sourceKey, context: context)
if let first = matched.first {
first.bizKey = bizKey
first.vodName = video.name
first.vodPic = video.pic
first.updateTime = Date()
for duplicate in matched.dropFirst() {
context.delete(duplicate)
}
} else {
let collect = VodCollect(
vodId: vodId,
vodName: video.name,
vodPic: video.pic,
sourceKey: sourceKey
)
context.insert(collect)
}
try context.save()
} catch {
print("写入收藏失败: \(error)")
}
let collect = VodCollect(
vodId: video.id,
vodName: video.name,
vodPic: video.pic,
sourceKey: video.sourceKey
)
context.insert(collect)
try? context.save()
}
@MainActor
func removeCollect(vodId: String, sourceKey: String, context: ModelContext) {
// (vodId, sourceKey)
let predicate = #Predicate<VodCollect> { item in
item.vodId == vodId && item.sourceKey == sourceKey
}
let descriptor = FetchDescriptor<VodCollect>(predicate: predicate)
if let items = try? context.fetch(descriptor) {
do {
let items = try fetchCollects(vodId: vodId, sourceKey: sourceKey, context: context)
guard !items.isEmpty else { return }
for item in items {
context.delete(item)
}
try? context.save()
try context.save()
} catch {
print("删除收藏失败: \(error)")
}
}
@MainActor
func isCollected(vodId: String, sourceKey: String, context: ModelContext) -> Bool {
let bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
let predicate = #Predicate<VodCollect> { item in
item.vodId == vodId && item.sourceKey == sourceKey
item.bizKey == bizKey || (item.bizKey == "" && item.vodId == vodId && item.sourceKey == sourceKey)
}
let descriptor = FetchDescriptor<VodCollect>(predicate: predicate)
return (try? context.fetchCount(descriptor)) ?? 0 > 0
do {
let count = try context.fetchCount(descriptor)
return count > 0
} catch {
print("查询收藏状态失败: \(error)")
return false
}
}
@MainActor
@@ -144,39 +171,55 @@ actor CacheStore {
) {
let vodId = video.id
let sourceKey = video.sourceKey
let record = fetchRecord(vodId: vodId, sourceKey: sourceKey, context: context)
let encodedState = Self.encodePlaybackState(playbackState)
let bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
//
if let record {
record.playNote = playNote
if let encodedState {
record.dataJson = encodedState
do {
let matched = try fetchRecords(vodId: vodId, sourceKey: sourceKey, context: context)
//
if let record = matched.first {
record.bizKey = bizKey
record.playNote = playNote
if let encodedState {
record.dataJson = encodedState
}
record.updateTime = Date()
for duplicate in matched.dropFirst() {
context.delete(duplicate)
}
} else {
let record = VodRecord(
vodId: vodId,
vodName: video.name,
vodPic: video.pic,
sourceKey: sourceKey,
playNote: playNote
)
if let encodedState {
record.dataJson = encodedState
}
context.insert(record)
}
record.updateTime = Date()
} else {
let record = VodRecord(
vodId: video.id,
vodName: video.name,
vodPic: video.pic,
sourceKey: video.sourceKey,
playNote: playNote
)
if let encodedState {
record.dataJson = encodedState
}
context.insert(record)
try context.save()
} catch {
print("写入播放记录失败: \(error)")
}
try? context.save()
}
/// JSON `nil`
@MainActor
func getPlaybackState(vodId: String, sourceKey: String, context: ModelContext) -> VodPlaybackState? {
guard let record = fetchRecord(vodId: vodId, sourceKey: sourceKey, context: context) else {
do {
guard let record = try fetchRecords(vodId: vodId, sourceKey: sourceKey, context: context).first else {
return nil
}
return Self.decodePlaybackState(record.dataJson)
} catch {
print("读取续播状态失败: \(error)")
return nil
}
return Self.decodePlaybackState(record.dataJson)
}
@MainActor
@@ -190,14 +233,47 @@ actor CacheStore {
}
@MainActor
private func fetchRecord(vodId: String, sourceKey: String, context: ModelContext) -> VodRecord? {
// (vodId, sourceKey)
private func fetchRecords(vodId: String, sourceKey: String, context: ModelContext) throws -> [VodRecord] {
let bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
let predicate = #Predicate<VodRecord> { item in
item.vodId == vodId && item.sourceKey == sourceKey
item.bizKey == bizKey || (item.bizKey == "" && item.vodId == vodId && item.sourceKey == sourceKey)
}
let descriptor = FetchDescriptor<VodRecord>(predicate: predicate)
guard let records = try? context.fetch(descriptor) else { return nil }
return records.first
let records = try context.fetch(descriptor)
// legacy
var needsSave = false
for record in records where record.bizKey.isEmpty {
record.bizKey = bizKey
needsSave = true
}
if needsSave {
try context.save()
}
return records.sorted(by: { $0.updateTime > $1.updateTime })
}
@MainActor
private func fetchCollects(vodId: String, sourceKey: String, context: ModelContext) throws -> [VodCollect] {
let bizKey = makeVodBusinessKey(vodId: vodId, sourceKey: sourceKey)
let predicate = #Predicate<VodCollect> { item in
item.bizKey == bizKey || (item.bizKey == "" && item.vodId == vodId && item.sourceKey == sourceKey)
}
let descriptor = FetchDescriptor<VodCollect>(predicate: predicate)
let collects = try context.fetch(descriptor)
// legacy
var needsSave = false
for collect in collects where collect.bizKey.isEmpty {
collect.bizKey = bizKey
needsSave = true
}
if needsSave {
try context.save()
}
return collects.sorted(by: { $0.updateTime > $1.updateTime })
}
private nonisolated static func encodePlaybackState(_ state: VodPlaybackState?) -> String? {