// // XSDataCenter.m // XenonSDK // // Created by SAGESSE on 2019/5/30. // Copyright © 2019 SAGESSE. All rights reserved. // #import "XenonSDK.h" #import "XSDataCenter.h" #if SDK_USE_DATA_DB #import #endif @implementation XSDataCenter + (NSString*)path { return [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSAllDomainsMask, YES) firstObject]; } #if SDK_USE_DATA_DB + (sqlite3*)db { static sqlite3* db = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Check file path. if (![NSFileManager.defaultManager fileExistsAtPath:self.path]) { [NSFileManager.defaultManager createDirectoryAtPath:self.path withIntermediateDirectories:YES attributes:nil error:nil]; } NSString* path = [self.path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.db", NSStringFromClass(self)]]; if (sqlite3_open(path.UTF8String, &db) != 0) { db = nil; return; } if (sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS 'DC' ('key' TEXT NOT NULL PRIMARY KEY, 'value' TEXT, 'isa' TEXT);", nil, nil, nil) != 0) { db = nil; return; } }); return db; } #endif + (void)setString:(id)value forKey:(id)key { [self setValue:[NSArray arrayWithObject:value] forKey:key]; } + (id)stringForKey:(id)key { return [[self valueForKey:key] firstObject]; } + (void)setDouble:(CGFloat)value forKey:(id)key { [self setValue:@[@(value)] forKey:key]; } + (CGFloat)doubleForKey:(id)key { return [[[self valueForKey:key] firstObject] doubleValue]; } + (void)setValue:(id)value forKey:(id)key { // Must be locked. @synchronized (self) { // To data; id isa = NSStringFromClass([value class]); id json = value; if ([json isKindOfClass:NSArray.class]) { __block id nisa = nil; NSMutableArray* res = [NSMutableArray array]; [json enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if ([obj isKindOfClass:JSONModel.class]) { nisa = NSStringFromClass([obj class]); [res addObject:[obj toDictionary]]; } else { nisa = nil; *stop = YES; } }]; // Same type. if (nisa != nil) { json = res; isa = nisa; } } if ([json isKindOfClass:JSONModel.class]) { json = [value toDictionary]; } if (json != nil) { id data = [NSJSONSerialization dataWithJSONObject:json options:0 error:nil]; json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; ; } #if SDK_USE_DATA_DB NSString* dw = sdk_md5(key); NSString* dsql = [NSString stringWithFormat:@"DELETE FROM DC WHERE key = '%@';", dw]; sqlite3_exec(self.db, dsql.UTF8String, nil, nil, nil); if (json != nil) { NSString* base64 = [[json dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]; NSString* isql = [NSString stringWithFormat:@"INSERT INTO DC (key, value, isa) VALUES ('%@', '%@', '%@');", dw, base64, isa]; sqlite3_exec(self.db, isql.UTF8String, nil, nil, nil); } #endif #if SDK_USE_DATA_UD if (value == nil) { [NSUserDefaults.standardUserDefaults setObject:nil forKey:key]; [NSUserDefaults.standardUserDefaults synchronize]; } else { [NSUserDefaults.standardUserDefaults setObject:@[isa, json] forKey:key]; [NSUserDefaults.standardUserDefaults synchronize]; } #endif #if SDK_USE_DATA_FILE // Check file path. if (![NSFileManager.defaultManager fileExistsAtPath:self.path]) { [NSFileManager.defaultManager createDirectoryAtPath:self.path withIntermediateDirectories:YES attributes:nil error:nil]; } // Make new path. NSString* path = [self.path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.db", sdk_md5(key)]]; if (value == nil) { [NSFileManager.defaultManager removeItemAtPath:path error:nil]; } else { [@[isa, json] writeToFile:path atomically:YES]; } #endif } } + (id)valueForKey:(id)key { // Must be locked. @synchronized (self) { NSString* isa = nil; NSString* json = nil; #if SDK_USE_DATA_DB NSString* dw = sdk_md5(key); NSString* qsql = [NSString stringWithFormat:@"SELECT isa, value FROM DC WHERE key = '%@';", dw]; sqlite3_stmt* stmt = nil; if (sqlite3_prepare_v2(self.db, qsql.UTF8String, -1, &stmt, nil) != 0) { return nil; } sqlite3_step(stmt); void* col1 = (void*)sqlite3_column_text(stmt, 0); void* col2 = (void*)sqlite3_column_text(stmt, 1); if (col1 != nil) { isa = [NSString stringWithUTF8String:col1]; } if (col2 != nil) { NSData* data = [[NSData alloc] initWithBase64EncodedString:[NSString stringWithUTF8String:col2] options:0]; json = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; } sqlite3_finalize(stmt); #endif #if SDK_USE_DATA_UD NSArray* uaw = [NSUserDefaults.standardUserDefaults arrayForKey:key]; if (uaw.count == 2) { isa = uaw[0]; json = uaw[1]; } #endif #if SDK_USE_DATA_FILE // Make new path. NSString* path = [self.path stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.db", sdk_md5(key)]]; NSArray* faw = [NSArray arrayWithContentsOfFile:path]; if (faw.count == 2) { isa = faw[0]; json = faw[1]; } #endif if (isa != nil || json != nil) { Class cls = NSClassFromString(isa); NSData* data = [json dataUsingEncoding:NSUTF8StringEncoding]; id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; if ([cls isSubclassOfClass:JSONModel.class]) { if ([object isKindOfClass:NSArray.class]) { return [cls arrayOfModelsFromDictionaries:object error:nil]; } return [[cls alloc] initWithDictionary:object error:nil]; } return object; } return nil; } } //class func set(_ value: T?, forKey key: String) { // //} // //class func value(forKey key: String) -> T? { // // Must be locked. // objc_sync_enter(self) // defer { // objc_sync_exit(self) // } // // var value: T? // //#if SDK_USE_DATA_DB // var stmt: UnsafeRawPointer? // guard sqlite3_prepare_v2(db, "SELECT value FROM DC WHERE key = '\(sdk_md5(key))';", -1, &stmt, nil) == 0 else { // return nil // } // defer { // _ = sqlite3_finalize(stmt) // } // _ = sqlite3_step(stmt) // guard let ptr = sqlite3_column_text(stmt, 0), let str = NSString(cString: ptr, encoding: String.Encoding.utf8.rawValue) else { // return nil // } // let data = Data(base64Encoded: str as String) // let decoder = PropertyListDecoder() // value = try? data.map { try decoder.decode(T.self, from: $0) } //#endif // //#if SDK_USE_DATA_UD // let data = UserDefaults.standard.data(forKey: defaultName) // let decoder = PropertyListDecoder() // value = try? data.map { try decoder.decode(T.self, from: $0) } //#endif // //#if SDK_USE_DATA_FILE // guard let path = XSDataCenter.path else { // return nil // } // var url = URL(fileURLWithPath: path) // url.appendPathComponent("\(sdk_md5(key)).db") // let data = try? Data(contentsOf: url) // let decoder = PropertyListDecoder() // value = try? data.map { try decoder.decode(T.self, from: $0) } //#endif // // return value //} @end