feat: sync httpdns sdk/platform updates without large binaries

This commit is contained in:
robin
2026-03-04 17:59:14 +08:00
parent 853897a6f8
commit 532891fad0
700 changed files with 6096 additions and 2712 deletions

View File

@@ -0,0 +1,75 @@
//
// HttpdnsDB.h
// TrustHttpDNS
//
// Created by xuyecan on 2025/3/15.
// Copyright © 2025 trustapp.com. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "HttpdnsHostRecord.h"
NS_ASSUME_NONNULL_BEGIN
/**
* SQLite3数据库操作类用于持久化存储HttpDNS缓存记录
*/
@interface HttpdnsDB : NSObject
/**
* 初始化数据库
* @param accountId 账户ID
* @return 数据库实<E5BA93><E5AE9E>?
*/
- (instancetype)initWithAccountId:(NSInteger)accountId;
/**
* 创建或更新记<E696B0><E8AEB0>?
* @param record 主机记录
* @return 是否成功
*/
- (BOOL)createOrUpdate:(HttpdnsHostRecord *)record;
/**
* 根据缓存键查询记<E8AFA2><E8AEB0>?
* @param cacheKey 缓存<E7BC93><E5AD98>?
* @return 查询到的记录如果不存在则返回nil
*/
- (nullable HttpdnsHostRecord *)selectByCacheKey:(NSString *)cacheKey;
/**
* 根据缓存键删除记<E999A4><E8AEB0>?
* @param cacheKey 缓存<E7BC93><E5AD98>?
* @return 是否成功
*/
- (BOOL)deleteByCacheKey:(NSString *)cacheKey;
/**
* 根据主机名数组批量删除记<E999A4><E8AEB0>?
* @param hostNameArr 主机名数<E5908D><E695B0>?
* @return 成功删除的记录数<E5BD95><E695B0>?
*/
- (NSInteger)deleteByHostNameArr:(NSArray<NSString *> *)hostNameArr;
/**
* 获取所有缓存记<E5AD98><E8AEB0>?
* @return 所有缓存记录数<E5BD95><E695B0>?
*/
- (NSArray<HttpdnsHostRecord *> *)getAllRecords;
/**
* 清理指定时间点已过期的记<E79A84><E8AEB0>?
* @param specifiedTime 指定的时间点epoch时间<E697B6><E997B4>?
* @return 清理的记录数<E5BD95><E695B0>?
*/
- (NSInteger)cleanRecordAlreadExpiredAt:(NSTimeInterval)specifiedTime;
/**
* 删除所有记<E69C89><E8AEB0>?
* @return 是否成功
*/
- (BOOL)deleteAll;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,608 @@
//
// HttpdnsDB.m
// NewHttpDNS
//
// Created by xuyecan on 2025/3/15.
// Copyright © 2025 new-inc.com. All rights reserved.
//
#import "HttpdnsDB.h"
#import "HttpdnsPersistenceUtils.h"
#import <sqlite3.h>
//
static NSString *const kTableName = @"httpdns_cache_table";
//
static NSString *const kColumnId = @"id";
static NSString *const kColumnCacheKey = @"cache_key";
static NSString *const kColumnHostName = @"host_name";
static NSString *const kColumnCreateAt = @"create_at";
static NSString *const kColumnModifyAt = @"modify_at";
static NSString *const kColumnClientIp = @"client_ip";
static NSString *const kColumnV4Ips = @"v4_ips";
static NSString *const kColumnV4Ttl = @"v4_ttl";
static NSString *const kColumnV4LookupTime = @"v4_lookup_time";
static NSString *const kColumnV6Ips = @"v6_ips";
static NSString *const kColumnV6Ttl = @"v6_ttl";
static NSString *const kColumnV6LookupTime = @"v6_lookup_time";
static NSString *const kColumnExtra = @"extra";
@interface HttpdnsDB ()
@property (nonatomic, assign) sqlite3 *db;
@property (nonatomic, copy) NSString *dbPath;
@property (nonatomic, strong) dispatch_queue_t dbQueue;
@end
@implementation HttpdnsDB
- (instancetype)initWithAccountId:(NSInteger)accountId {
self = [super init];
if (self) {
//
NSString *dbDir = [HttpdnsPersistenceUtils httpdnsDataDirectory];
//
_dbPath = [dbDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%ld_v20250406.db", (long)accountId]];
// 线
_dbQueue = dispatch_queue_create("com.new.httpdns.db", DISPATCH_QUEUE_SERIAL);
//
__block BOOL success = NO;
dispatch_sync(_dbQueue, ^{
success = [self openDB];
});
if (!success) {
return nil;
}
}
return self;
}
- (void)dealloc {
if (_db) {
sqlite3_close(_db);
_db = NULL;
}
}
#pragma mark - Public Methods
- (BOOL)createOrUpdate:(HttpdnsHostRecord *)record {
if (!record || !record.cacheKey) {
return NO;
}
__block BOOL result = NO;
dispatch_sync(_dbQueue, ^{
// 便createAt
HttpdnsHostRecord *existingRecord = [self selectByCacheKeyInternal:record.cacheKey];
//
NSDate *now = [NSDate date];
HttpdnsHostRecord *recordToSave;
if (existingRecord) {
// createAtmodifyAt
recordToSave = [[HttpdnsHostRecord alloc] initWithId:record.id
cacheKey:record.cacheKey
hostName:record.hostName
createAt:existingRecord.createAt // createAt
modifyAt:now // modifyAt
clientIp:record.clientIp
v4ips:record.v4ips
v4ttl:record.v4ttl
v4LookupTime:record.v4LookupTime
v6ips:record.v6ips
v6ttl:record.v6ttl
v6LookupTime:record.v6LookupTime
extra:record.extra];
} else {
// createAtmodifyAt
recordToSave = [[HttpdnsHostRecord alloc] initWithId:record.id
cacheKey:record.cacheKey
hostName:record.hostName
createAt:now // createAt
modifyAt:now // modifyAt
clientIp:record.clientIp
v4ips:record.v4ips
v4ttl:record.v4ttl
v4LookupTime:record.v4LookupTime
v6ips:record.v6ips
v6ttl:record.v6ttl
v6LookupTime:record.v6LookupTime
extra:record.extra];
}
// 使INSERT OR REPLACE
result = [self saveRecord:recordToSave];
});
return result;
}
- (nullable HttpdnsHostRecord *)selectByCacheKey:(NSString *)cacheKey {
if (!cacheKey) {
return nil;
}
__block HttpdnsHostRecord *record = nil;
dispatch_sync(_dbQueue, ^{
record = [self selectByCacheKeyInternal:cacheKey];
});
return record;
}
- (BOOL)deleteByCacheKey:(NSString *)cacheKey {
if (!cacheKey) {
return NO;
}
__block BOOL result = NO;
dispatch_sync(_dbQueue, ^{
NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = ?", kTableName, kColumnCacheKey];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, [cacheKey UTF8String], -1, SQLITE_TRANSIENT);
result = (sqlite3_step(stmt) == SQLITE_DONE);
sqlite3_finalize(stmt);
}
});
return result;
}
- (NSInteger)deleteByHostNameArr:(NSArray<NSString *> *)hostNameArr {
if (!hostNameArr || hostNameArr.count == 0) {
return 0;
}
//
NSMutableArray *validHostNames = [NSMutableArray array];
for (NSString *hostname in hostNameArr) {
if (hostname && hostname.length > 0) {
[validHostNames addObject:hostname];
}
}
if (validHostNames.count == 0) {
return 0;
}
__block NSInteger deletedCount = 0;
dispatch_sync(_dbQueue, ^{
// IN
NSMutableString *placeholders = [NSMutableString string];
for (NSUInteger i = 0; i < validHostNames.count; i++) {
[placeholders appendString:@"?"];
if (i < validHostNames.count - 1) {
[placeholders appendString:@","];
}
}
// SQL
NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ IN (%@)",
kTableName, kColumnHostName, placeholders];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) {
//
for (NSUInteger i = 0; i < validHostNames.count; i++) {
sqlite3_bind_text(stmt, (int)(i + 1), [validHostNames[i] UTF8String], -1, SQLITE_TRANSIENT);
}
//
if (sqlite3_step(stmt) == SQLITE_DONE) {
deletedCount = sqlite3_changes(_db);
} else {
NSLog(@"Failed to delete records: %s", sqlite3_errmsg(_db));
}
sqlite3_finalize(stmt);
} else {
NSLog(@"Failed to prepare delete statement: %s", sqlite3_errmsg(_db));
}
});
return deletedCount;
}
- (BOOL)deleteAll {
__block BOOL result = NO;
dispatch_sync(_dbQueue, ^{
NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@", kTableName];
char *errMsg;
result = (sqlite3_exec(_db, [sql UTF8String], NULL, NULL, &errMsg) == SQLITE_OK);
if (errMsg) {
NSLog(@"Failed to delete all records: %s", errMsg);
sqlite3_free(errMsg);
}
});
return result;
}
- (NSArray<HttpdnsHostRecord *> *)getAllRecords {
__block NSMutableArray<HttpdnsHostRecord *> *records = [NSMutableArray array];
dispatch_sync(_dbQueue, ^{
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@", kTableName];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
HttpdnsHostRecord *record = [self recordFromStatement:stmt];
if (record) {
[records addObject:record];
}
}
sqlite3_finalize(stmt);
} else {
NSLog(@"Failed to prepare getAllRecords statement: %s", sqlite3_errmsg(_db));
}
});
return [records copy];
}
- (NSInteger)cleanRecordAlreadExpiredAt:(NSTimeInterval)specifiedTime {
__block NSInteger cleanedCount = 0;
//
NSArray<HttpdnsHostRecord *> *allRecords = [self getAllRecords];
dispatch_sync(_dbQueue, ^{
for (HttpdnsHostRecord *record in allRecords) {
BOOL v4Expired = NO;
BOOL v6Expired = NO;
// IPv4
if (record.v4LookupTime + record.v4ttl <= specifiedTime) {
v4Expired = YES;
}
// IPv6
if (record.v6LookupTime + record.v6ttl <= specifiedTime) {
v6Expired = YES;
}
// IP
if (v4Expired && v6Expired) {
NSString *sql = [NSString stringWithFormat:@"DELETE FROM %@ WHERE %@ = ?", kTableName, kColumnCacheKey];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, [record.cacheKey UTF8String], -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) == SQLITE_DONE) {
cleanedCount++;
}
sqlite3_finalize(stmt);
}
}
// IP
else if (v4Expired || v6Expired) {
NSMutableArray<NSString *> *v4ips = [NSMutableArray arrayWithArray:record.v4ips];
NSMutableArray<NSString *> *v6ips = [NSMutableArray arrayWithArray:record.v6ips];
// IPv4IPv4
if (v4Expired) {
[v4ips removeAllObjects];
}
// IPv6IPv6
if (v6Expired) {
[v6ips removeAllObjects];
}
//
NSString *sql = [NSString stringWithFormat:
@"UPDATE %@ SET %@ = ?, %@ = ? WHERE %@ = ?",
kTableName,
kColumnV4Ips,
kColumnV6Ips,
kColumnCacheKey];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) == SQLITE_OK) {
// v4ips
if (v4ips.count > 0) {
NSString *v4ipsStr = [v4ips componentsJoinedByString:@","];
sqlite3_bind_text(stmt, 1, [v4ipsStr UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, 1);
}
// v6ips
if (v6ips.count > 0) {
NSString *v6ipsStr = [v6ips componentsJoinedByString:@","];
sqlite3_bind_text(stmt, 2, [v6ipsStr UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, 2);
}
// cacheKey
sqlite3_bind_text(stmt, 3, [record.cacheKey UTF8String], -1, SQLITE_TRANSIENT);
if (sqlite3_step(stmt) == SQLITE_DONE) {
cleanedCount++;
}
sqlite3_finalize(stmt);
}
}
}
});
return cleanedCount;
}
#pragma mark - Private Methods
- (BOOL)openDB {
if (sqlite3_open([_dbPath UTF8String], &_db) != SQLITE_OK) {
NSLog(@"Failed to open database: %s", sqlite3_errmsg(_db));
return NO;
}
//
return [self createTableIfNeeded];
}
- (BOOL)createTableIfNeeded {
NSString *sql = [NSString stringWithFormat:
@"CREATE TABLE IF NOT EXISTS %@ ("
@"%@ INTEGER PRIMARY KEY AUTOINCREMENT, "
@"%@ TEXT UNIQUE NOT NULL, "
@"%@ TEXT NOT NULL, "
@"%@ REAL, "
@"%@ REAL, "
@"%@ TEXT, "
@"%@ TEXT, "
@"%@ INTEGER, "
@"%@ INTEGER, "
@"%@ TEXT, "
@"%@ INTEGER, "
@"%@ INTEGER, "
@"%@ TEXT"
@")",
kTableName,
kColumnId,
kColumnCacheKey,
kColumnHostName,
kColumnCreateAt,
kColumnModifyAt,
kColumnClientIp,
kColumnV4Ips,
kColumnV4Ttl,
kColumnV4LookupTime,
kColumnV6Ips,
kColumnV6Ttl,
kColumnV6LookupTime,
kColumnExtra];
char *errMsg;
if (sqlite3_exec(_db, [sql UTF8String], NULL, NULL, &errMsg) != SQLITE_OK) {
NSLog(@"Failed to create table: %s", errMsg);
sqlite3_free(errMsg);
return NO;
}
return YES;
}
- (BOOL)saveRecord:(HttpdnsHostRecord *)record {
// 使INSERT OR REPLACE
NSString *sql = [NSString stringWithFormat:
@"INSERT OR REPLACE INTO %@ ("
@"%@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@, %@) "
@"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
kTableName,
kColumnCacheKey,
kColumnHostName,
kColumnCreateAt,
kColumnModifyAt,
kColumnClientIp,
kColumnV4Ips,
kColumnV4Ttl,
kColumnV4LookupTime,
kColumnV6Ips,
kColumnV6Ttl,
kColumnV6LookupTime,
kColumnExtra];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
NSLog(@"Failed to prepare save statement: %s", sqlite3_errmsg(_db));
return NO;
}
//
int index = 1;
// cacheKey ()
sqlite3_bind_text(stmt, index++, [record.cacheKey UTF8String], -1, SQLITE_TRANSIENT);
// hostName
sqlite3_bind_text(stmt, index++, [record.hostName UTF8String], -1, SQLITE_TRANSIENT);
// createAt
if (record.createAt) {
sqlite3_bind_double(stmt, index++, [record.createAt timeIntervalSince1970]);
} else {
sqlite3_bind_null(stmt, index++);
}
// modifyAt
if (record.modifyAt) {
sqlite3_bind_double(stmt, index++, [record.modifyAt timeIntervalSince1970]);
} else {
sqlite3_bind_null(stmt, index++);
}
// clientIp
if (record.clientIp) {
sqlite3_bind_text(stmt, index++, [record.clientIp UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, index++);
}
// v4ips
if (record.v4ips.count > 0) {
NSString *v4ipsStr = [record.v4ips componentsJoinedByString:@","];
sqlite3_bind_text(stmt, index++, [v4ipsStr UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, index++);
}
// v4ttl
sqlite3_bind_int64(stmt, index++, record.v4ttl);
// v4LookupTime
sqlite3_bind_int64(stmt, index++, record.v4LookupTime);
// v6ips
if (record.v6ips.count > 0) {
NSString *v6ipsStr = [record.v6ips componentsJoinedByString:@","];
sqlite3_bind_text(stmt, index++, [v6ipsStr UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, index++);
}
// v6ttl
sqlite3_bind_int64(stmt, index++, record.v6ttl);
// v6LookupTime
sqlite3_bind_int64(stmt, index++, record.v6LookupTime);
// extra
if (record.extra) {
sqlite3_bind_text(stmt, index++, [record.extra UTF8String], -1, SQLITE_TRANSIENT);
} else {
sqlite3_bind_null(stmt, index++);
}
BOOL result = (sqlite3_step(stmt) == SQLITE_DONE);
sqlite3_finalize(stmt);
return result;
}
- (HttpdnsHostRecord *)selectByCacheKeyInternal:(NSString *)cacheKey {
NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@ WHERE %@ = ?", kTableName, kColumnCacheKey];
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &stmt, NULL) != SQLITE_OK) {
NSLog(@"Failed to prepare query statement: %s", sqlite3_errmsg(_db));
return nil;
}
sqlite3_bind_text(stmt, 1, [cacheKey UTF8String], -1, SQLITE_TRANSIENT);
HttpdnsHostRecord *record = nil;
if (sqlite3_step(stmt) == SQLITE_ROW) {
record = [self recordFromStatement:stmt];
}
sqlite3_finalize(stmt);
return record;
}
- (HttpdnsHostRecord *)recordFromStatement:(sqlite3_stmt *)stmt {
// id
NSUInteger recordId = (NSUInteger)sqlite3_column_int64(stmt, 0);
// cacheKey
const char *cacheKeyChars = (const char *)sqlite3_column_text(stmt, 1);
NSString *cacheKey = cacheKeyChars ? [NSString stringWithUTF8String:cacheKeyChars] : nil;
// hostName
const char *hostNameChars = (const char *)sqlite3_column_text(stmt, 2);
NSString *hostName = hostNameChars ? [NSString stringWithUTF8String:hostNameChars] : nil;
// createAt
NSDate *createAt = nil;
if (sqlite3_column_type(stmt, 3) != SQLITE_NULL) {
double createAtTimestamp = sqlite3_column_double(stmt, 3);
createAt = [NSDate dateWithTimeIntervalSince1970:createAtTimestamp];
}
// modifyAt
NSDate *modifyAt = nil;
if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
double modifyAtTimestamp = sqlite3_column_double(stmt, 4);
modifyAt = [NSDate dateWithTimeIntervalSince1970:modifyAtTimestamp];
}
// clientIp
const char *clientIpChars = (const char *)sqlite3_column_text(stmt, 5);
NSString *clientIp = clientIpChars ? [NSString stringWithUTF8String:clientIpChars] : nil;
// v4ips
NSArray<NSString *> *v4ips = nil;
const char *v4ipsChars = (const char *)sqlite3_column_text(stmt, 6);
if (v4ipsChars) {
NSString *v4ipsStr = [NSString stringWithUTF8String:v4ipsChars];
v4ips = [v4ipsStr componentsSeparatedByString:@","];
} else {
v4ips = @[];
}
// v4ttl
int64_t v4ttl = sqlite3_column_int64(stmt, 7);
// v4LookupTime
int64_t v4LookupTime = sqlite3_column_int64(stmt, 8);
// v6ips
NSArray<NSString *> *v6ips = nil;
const char *v6ipsChars = (const char *)sqlite3_column_text(stmt, 9);
if (v6ipsChars) {
NSString *v6ipsStr = [NSString stringWithUTF8String:v6ipsChars];
v6ips = [v6ipsStr componentsSeparatedByString:@","];
} else {
v6ips = @[];
}
// v6ttl
int64_t v6ttl = sqlite3_column_int64(stmt, 10);
// v6LookupTime
int64_t v6LookupTime = sqlite3_column_int64(stmt, 11);
// extra
NSString *extra = nil;
const char *extraChars = (const char *)sqlite3_column_text(stmt, 12);
if (extraChars) {
extra = [NSString stringWithUTF8String:extraChars];
}
//
return [[HttpdnsHostRecord alloc] initWithId:recordId
cacheKey:cacheKey
hostName:hostName
createAt:createAt
modifyAt:modifyAt
clientIp:clientIp
v4ips:v4ips
v4ttl:v4ttl
v4LookupTime:v4LookupTime
v6ips:v6ips
v6ttl:v6ttl
v6LookupTime:v6LookupTime
extra:extra];
}
@end

View File

@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import <Foundation/Foundation.h>
@interface HttpdnsPersistenceUtils : NSObject
+ (NSString *)httpdnsDataDirectory;
+ (NSString *)scheduleCenterResultDirectory;
/// 多账号隔离返回指定账号的调度结果目<E69E9C><E79BAE>?
+ (NSString *)scheduleCenterResultDirectoryForAccount:(NSInteger)accountId;
+ (BOOL)saveJSON:(id)JSON toPath:(NSString *)path;
+ (id)getJSONFromPath:(NSString *)path;
@end

View File

@@ -0,0 +1,154 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "HttpdnsPersistenceUtils.h"
#import "HttpdnsService.h"
#import "HttpdnsUtil.h"
static NSString *const Trust_HTTPDNS_ROOT_DIR_NAME = @"HTTPDNS";
static NSString *const Trust_HTTPDNS_HOST_CACHE_DIR_NAME = @"HostCache";
static dispatch_queue_t _fileCacheQueue = 0;
@implementation HttpdnsPersistenceUtils
#pragma mark - Base Path
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_fileCacheQueue = dispatch_queue_create("com.Trust.sdk.httpdns.fileCacheQueue", DISPATCH_QUEUE_SERIAL);
});
}
#pragma mark - File Utils
+ (BOOL)saveJSON:(id)JSON toPath:(NSString *)path {
if (![HttpdnsUtil isNotEmptyString:path]) {
return NO;
}
BOOL isValid = [HttpdnsUtil isValidJSON:JSON];
if (isValid) {
__block BOOL saveSucceed = NO;
@try {
[self removeFile:path];
dispatch_sync(_fileCacheQueue, ^{
saveSucceed = [NSKeyedArchiver archiveRootObject:JSON toFile:path];
});
} @catch (NSException *exception) {}
return saveSucceed;
}
return NO;
}
+ (id)getJSONFromPath:(NSString *)path {
if (![HttpdnsUtil isNotEmptyString:path]) {
return nil;
}
__block id JSON = nil;
@try {
dispatch_sync(_fileCacheQueue, ^{
JSON = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
});
BOOL isValid = [HttpdnsUtil isValidJSON:JSON];
if (isValid) {
return JSON;
}
} @catch (NSException *exception) {
//deal with the previous file version
if ([[exception name] isEqualToString:NSInvalidArgumentException]) {
JSON = [NSMutableDictionary dictionaryWithContentsOfFile:path];
if (!JSON) {
JSON = [NSMutableArray arrayWithContentsOfFile:path];
}
}
}
return JSON;
}
+ (BOOL)removeFile:(NSString *)path {
if (![HttpdnsUtil isNotEmptyString:path]) {
return NO;
}
__block NSError * error = nil;
__block BOOL ret = NO;
dispatch_sync(_fileCacheQueue, ^{
ret = [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
});
return ret;
}
+ (void)createDirectoryIfNeeded:(NSString *)path {
if (![HttpdnsUtil isNotEmptyString:path]) {
return;
}
dispatch_sync(_fileCacheQueue, ^{
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:NULL];
}
});
}
#pragma mark - ~/Libraray/Private Documents
/// Base path, all paths depend it
+ (NSString *)homeDirectoryPath {
return NSHomeDirectory();
}
// ~/Library
+ (NSString *)libraryDirectory {
static NSString *path = nil;
if (!path) {
path = [[self homeDirectoryPath] stringByAppendingPathComponent:@"Library"];
}
return path;
}
// ~/Library/Private Documents/HTTPDNS
+ (NSString *)httpdnsDataDirectory {
NSString *directory = [[HttpdnsPersistenceUtils libraryDirectory] stringByAppendingPathComponent:@"Private Documents/HTTPDNS"];
[self createDirectoryIfNeeded:directory];
return directory;
}
//Library/Private Documents/HTTPDNS/scheduleCenterResult
+ (NSString *)scheduleCenterResultDirectory {
NSString *directory = [[HttpdnsPersistenceUtils httpdnsDataDirectory] stringByAppendingPathComponent:@"scheduleCenterResult"];
[self createDirectoryIfNeeded:directory];
return directory;
}
//Library/Private Documents/HTTPDNS/scheduleCenterResult/<accountId>
+ (NSString *)scheduleCenterResultDirectoryForAccount:(NSInteger)accountId {
NSString *base = [self scheduleCenterResultDirectory];
NSString *directory = [base stringByAppendingPathComponent:[NSString stringWithFormat:@"%ld", (long)accountId]];
[self createDirectoryIfNeeded:directory];
return directory;
}
@end