| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- //
- // JXPlaybackProgressStorage.m
- // AICity
- //
- // Feature: 003-ios-api-https
- // 播放进度本地存储管理器实现
- //
- #import "JXPlaybackProgressStorage.h"
- @interface JXPlaybackProgressStorage ()
- @property (nonatomic, strong) NSPersistentContainer *persistentContainer;
- @property (nonatomic, strong) NSManagedObjectContext *context;
- @end
- @implementation JXPlaybackProgressStorage
- + (instancetype)sharedStorage {
- static JXPlaybackProgressStorage *instance = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- instance = [[self alloc] init];
- });
- return instance;
- }
- - (instancetype)init {
- self = [super init];
- if (self) {
- [self setupCoreDataStack];
- }
- return self;
- }
- #pragma mark - Core Data Stack
- - (void)setupCoreDataStack {
- // 创建NSManagedObjectModel
- NSManagedObjectModel *model = [self createManagedObjectModel];
-
- // 创建NSPersistentStoreCoordinator
- NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
-
- // 获取存储路径
- NSURL *storeURL = [self storeURL];
-
- // 添加持久化存储
- NSError *error = nil;
- NSDictionary *options = @{
- NSMigratePersistentStoresAutomaticallyOption: @YES,
- NSInferMappingModelAutomaticallyOption: @YES
- };
-
- if (![coordinator addPersistentStoreWithType:NSSQLiteStoreType
- configuration:nil
- URL:storeURL
- options:options
- error:&error]) {
- NSLog(@"Error adding persistent store: %@", error);
- // 如果添加失败,尝试删除旧数据库并重新创建
- [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];
- [coordinator addPersistentStoreWithType:NSSQLiteStoreType
- configuration:nil
- URL:storeURL
- options:options
- error:nil];
- }
-
- // 创建NSManagedObjectContext
- self.context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
- self.context.persistentStoreCoordinator = coordinator;
- }
- - (NSManagedObjectModel *)createManagedObjectModel {
- NSManagedObjectModel *model = [[NSManagedObjectModel alloc] init];
-
- // 创建实体描述
- NSEntityDescription *entity = [[NSEntityDescription alloc] init];
- [entity setName:@"JXPlaybackProgress"];
- [entity setManagedObjectClassName:@"JXPlaybackProgressModel"];
-
- // 定义属性
- NSMutableArray *properties = [NSMutableArray array];
-
- // episodeId (String, 主键)
- NSAttributeDescription *episodeIdAttr = [[NSAttributeDescription alloc] init];
- [episodeIdAttr setName:@"episodeId"];
- [episodeIdAttr setAttributeType:NSStringAttributeType];
- [episodeIdAttr setOptional:NO];
- [properties addObject:episodeIdAttr];
-
- // dramaId (String)
- NSAttributeDescription *dramaIdAttr = [[NSAttributeDescription alloc] init];
- [dramaIdAttr setName:@"dramaId"];
- [dramaIdAttr setAttributeType:NSStringAttributeType];
- [dramaIdAttr setOptional:NO];
- [properties addObject:dramaIdAttr];
-
- // position (Integer64)
- NSAttributeDescription *positionAttr = [[NSAttributeDescription alloc] init];
- [positionAttr setName:@"position"];
- [positionAttr setAttributeType:NSInteger64AttributeType];
- [positionAttr setOptional:NO];
- [properties addObject:positionAttr];
-
- // duration (Integer64)
- NSAttributeDescription *durationAttr = [[NSAttributeDescription alloc] init];
- [durationAttr setName:@"duration"];
- [durationAttr setAttributeType:NSInteger64AttributeType];
- [durationAttr setOptional:NO];
- [properties addObject:durationAttr];
-
- // progress (Float)
- NSAttributeDescription *progressAttr = [[NSAttributeDescription alloc] init];
- [progressAttr setName:@"progress"];
- [progressAttr setAttributeType:NSFloatAttributeType];
- [progressAttr setOptional:NO];
- [properties addObject:progressAttr];
-
- // isCompleted (Boolean)
- NSAttributeDescription *isCompletedAttr = [[NSAttributeDescription alloc] init];
- [isCompletedAttr setName:@"isCompleted"];
- [isCompletedAttr setAttributeType:NSBooleanAttributeType];
- [isCompletedAttr setOptional:NO];
- [properties addObject:isCompletedAttr];
-
- // updatedAt (Date)
- NSAttributeDescription *updatedAtAttr = [[NSAttributeDescription alloc] init];
- [updatedAtAttr setName:@"updatedAt"];
- [updatedAtAttr setAttributeType:NSDateAttributeType];
- [updatedAtAttr setOptional:NO];
- [properties addObject:updatedAtAttr];
-
- // synced (Boolean)
- NSAttributeDescription *syncedAttr = [[NSAttributeDescription alloc] init];
- [syncedAttr setName:@"synced"];
- [syncedAttr setAttributeType:NSBooleanAttributeType];
- [syncedAttr setOptional:NO];
- [properties addObject:syncedAttr];
-
- // 设置属性到实体
- [entity setProperties:properties];
-
- // 添加实体到模型
- [model setEntities:@[entity]];
-
- return model;
- }
- - (NSURL *)storeURL {
- NSURL *documentsURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
- return [documentsURL URLByAppendingPathComponent:@"JXPlaybackProgress.sqlite"];
- }
- #pragma mark - Public Methods
- - (void)saveProgressWithEpisodeId:(NSString *)episodeId
- dramaId:(NSString *)dramaId
- position:(int64_t)position
- duration:(int64_t)duration
- isCompleted:(BOOL)isCompleted
- synced:(BOOL)synced
- completion:(void (^)(BOOL, NSError * _Nullable))completion {
-
- // 计算进度百分比
- float progress = (duration > 0) ? (float)position / (float)duration : 0.0f;
- if (progress < 0.0f) progress = 0.0f;
- if (progress > 1.0f) progress = 1.0f;
-
- // 查找或创建实体
- JXPlaybackProgressModel *progressModel = [self getProgressWithEpisodeId:episodeId];
- if (!progressModel) {
- progressModel = [NSEntityDescription insertNewObjectForEntityForName:@"JXPlaybackProgress"
- inManagedObjectContext:self.context];
- progressModel.episodeId = episodeId;
- }
-
- // 更新属性
- progressModel.dramaId = dramaId;
- progressModel.position = position;
- progressModel.duration = duration;
- progressModel.progress = progress;
- progressModel.isCompleted = isCompleted;
- progressModel.updatedAt = [NSDate date];
- progressModel.synced = synced;
-
- // 保存上下文
- NSError *error = nil;
- BOOL success = [self.context save:&error];
-
- if (completion) {
- completion(success, error);
- }
-
- if (!success) {
- NSLog(@"Error saving progress: %@", error);
- }
- }
- - (JXPlaybackProgressModel *)getProgressWithEpisodeId:(NSString *)episodeId {
- NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"JXPlaybackProgress"];
- request.predicate = [NSPredicate predicateWithFormat:@"episodeId == %@", episodeId];
- request.fetchLimit = 1;
-
- NSError *error = nil;
- NSArray *results = [self.context executeFetchRequest:request error:&error];
-
- if (error) {
- NSLog(@"Error fetching progress: %@", error);
- return nil;
- }
-
- return results.firstObject;
- }
- - (NSArray<JXPlaybackProgressModel *> *)getProgressByDramaId:(NSString *)dramaId {
- NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"JXPlaybackProgress"];
- request.predicate = [NSPredicate predicateWithFormat:@"dramaId == %@", dramaId];
- request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"updatedAt" ascending:NO]];
-
- NSError *error = nil;
- NSArray *results = [self.context executeFetchRequest:request error:&error];
-
- if (error) {
- NSLog(@"Error fetching progress by drama: %@", error);
- return @[];
- }
-
- return results ?: @[];
- }
- - (NSArray<JXPlaybackProgressModel *> *)getUnsyncedProgresses {
- NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"JXPlaybackProgress"];
- request.predicate = [NSPredicate predicateWithFormat:@"synced == NO"];
- request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"updatedAt" ascending:YES]];
-
- NSError *error = nil;
- NSArray *results = [self.context executeFetchRequest:request error:&error];
-
- if (error) {
- NSLog(@"Error fetching unsynced progress: %@", error);
- return @[];
- }
-
- return results ?: @[];
- }
- - (void)markAsSyncedWithEpisodeId:(NSString *)episodeId {
- JXPlaybackProgressModel *progressModel = [self getProgressWithEpisodeId:episodeId];
- if (progressModel) {
- progressModel.synced = YES;
- [self.context save:nil];
- }
- }
- - (void)deleteProgressWithEpisodeId:(NSString *)episodeId {
- JXPlaybackProgressModel *progressModel = [self getProgressWithEpisodeId:episodeId];
- if (progressModel) {
- [self.context deleteObject:progressModel];
- [self.context save:nil];
- }
- }
- - (void)deleteProgressByDramaId:(NSString *)dramaId {
- NSArray *progresses = [self getProgressByDramaId:dramaId];
- for (JXPlaybackProgressModel *progress in progresses) {
- [self.context deleteObject:progress];
- }
- [self.context save:nil];
- }
- - (void)clearAll {
- NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"JXPlaybackProgress"];
- NSBatchDeleteRequest *deleteRequest = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
-
- NSError *error = nil;
- [self.context.persistentStoreCoordinator executeRequest:deleteRequest withContext:self.context error:&error];
-
- if (error) {
- NSLog(@"Error clearing all progress: %@", error);
- }
- }
- - (void)cleanupOldRecords {
- // 删除7天前的记录
- NSDate *sevenDaysAgo = [NSDate dateWithTimeIntervalSinceNow:-(7 * 24 * 60 * 60)];
-
- NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"JXPlaybackProgress"];
- request.predicate = [NSPredicate predicateWithFormat:@"updatedAt < %@", sevenDaysAgo];
-
- NSError *error = nil;
- NSArray *oldRecords = [self.context executeFetchRequest:request error:&error];
-
- if (!error) {
- for (JXPlaybackProgressModel *record in oldRecords) {
- [self.context deleteObject:record];
- }
- [self.context save:nil];
- }
- }
- @end
|