#import "TGFileCache.h" #import "TGStringUtils.h" NSString *const TGFileCacheDomain = @"com.telegram.FileCache"; @interface TGFileCache () { NSCache *_memoryCache; dispatch_queue_t _queue; NSURL *_url; } @end @implementation TGFileCache - (instancetype)init { return [self initWithName:nil useMemoryCache:true]; } - (instancetype)initWithName:(NSString *)name useMemoryCache:(bool)useMemoryCache { return [self initWithName:name useMemoryCache:useMemoryCache useApplicationGroup:false]; } - (instancetype)initWithName:(NSString *)name useMemoryCache:(bool)useMemoryCache useApplicationGroup:(bool)useApplicationGroup { self = [super init]; if (self != nil) { if (useMemoryCache) _memoryCache = [[NSCache alloc] init]; _queue = dispatch_queue_create(TGFileCacheDomain.UTF8String, nil); NSURL *baseURL = useApplicationGroup ? [TGFileCache baseAppGroupURL] : [TGFileCache baseURL]; if (name.length == 0) _url = baseURL; else _url = [NSURL fileURLWithPath:name relativeToURL:baseURL]; dispatch_async(_queue, ^ { if (![[NSFileManager defaultManager] fileExistsAtPath:_url.path]) { NSError *error; [[NSFileManager defaultManager] createDirectoryAtURL:_url withIntermediateDirectories:true attributes:nil error:&error]; } }); } return self; } - (void)fetchDataForKey:(NSString *)key synchronous:(bool)synchronous unserializeBlock:(id (^)(NSData *))unserializeBlock completion:(void (^)(id))completion { [self fetchDataForKey:key memoryOnly:false synchronous:synchronous unserializeBlock:unserializeBlock completion:completion]; } - (void)fetchDataForKey:(NSString *)key memoryOnly:(bool)memoryOnly synchronous:(bool)synchronous unserializeBlock:(id (^)(NSData *))unserializeBlock completion:(void (^)(id))completion { if (completion == nil) return; void (^block)(void) = ^ { id cachedObject = [_memoryCache objectForKey:key]; if (cachedObject != nil) { completion(cachedObject); return; } if (!memoryOnly) { NSData *data = [[NSData alloc] initWithContentsOfURL:[self urlForKey:key] options:kNilOptions error:nil]; if (data.length > 0) { id result = data; if (unserializeBlock != nil) { result = unserializeBlock(data); [_memoryCache setObject:result forKey:key]; } completion(result); return; } } completion(nil); }; if (synchronous) dispatch_sync(_queue, block); else dispatch_async(_queue, block); } - (void)cacheData:(NSData *)data key:(NSString *)key synchronous:(bool)synchronous completion:(void (^)(NSURL *))completion { [self cacheData:data key:key synchronous:synchronous serializeBlock:nil completion:completion]; } - (void)cacheData:(NSObject *)data key:(NSString *)key synchronous:(bool)synchronous serializeBlock:(NSData *(^)(NSObject *))serializeBlock completion:(void (^)(NSURL *))completion { void (^block)(void) = ^ { NSURL *url = [self urlForKey:key]; NSData *serializedData = nil; if (serializeBlock != nil) serializedData = serializeBlock(data); else if ([data isKindOfClass:[NSData class]]) serializedData = (NSData *)data; [serializedData writeToURL:url atomically:true]; if (completion != nil) completion(url); }; if (synchronous) dispatch_sync(_queue, block); else dispatch_async(_queue, block); } - (void)cacheFileAtURL:(NSURL *)url key:(NSString *)key synchronous:(bool)synchronous completion:(void (^)(NSURL *))completion { [self cacheFileAtURL:url key:key synchronous:synchronous unserializeBlock:nil completion:completion]; } - (void)cacheFileAtURL:(NSURL *)url key:(NSString *)key synchronous:(bool)synchronous unserializeBlock:(id (^)(NSData *))unserializeBlock completion:(void (^)(NSURL *))completion { void (^block)(void) = ^ { NSURL *newUrl = [self urlForKey:key]; [[NSFileManager defaultManager] copyItemAtURL:url toURL:newUrl error:NULL]; if (completion != nil) completion(newUrl); if (unserializeBlock != nil && _memoryCache != nil) { NSData *data = [NSData dataWithContentsOfURL:url]; id result = unserializeBlock(data); [_memoryCache setObject:result forKey:key]; } }; if (synchronous) dispatch_sync(_queue, block); else dispatch_async(_queue, block); } - (void)clearCacheSynchronous:(bool)synchronous { void (^block)(void) = ^ { NSArray *contents = [[NSFileManager defaultManager] contentsOfDirectoryAtURL:_url includingPropertiesForKeys:nil options:kNilOptions error:NULL]; for (NSURL *url in contents) [[NSFileManager defaultManager] removeItemAtURL:url error:NULL]; }; if (synchronous) dispatch_sync(_queue, block); else dispatch_async(_queue, block); } - (bool)hasDataForKey:(NSString *)key { return [[NSFileManager defaultManager] fileExistsAtPath:[self urlForKey:key].path]; } - (NSURL *)urlForKey:(NSString *)key { NSString *fileName = [TGStringUtils md5WithString:key]; if (self.defaultFileExtension != nil) fileName = [fileName stringByAppendingPathExtension:self.defaultFileExtension]; return [NSURL fileURLWithPath:[_url.path stringByAppendingPathComponent:fileName]]; } + (NSURL *)baseURL { static dispatch_once_t onceToken; static NSURL *baseURL; dispatch_once(&onceToken, ^ { NSString *cachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, true)[0]; baseURL = [[NSURL alloc] initFileURLWithPath:[cachesPath stringByAppendingPathComponent:TGFileCacheDomain]]; }); return baseURL; } + (NSURL *)baseAppGroupURL { static dispatch_once_t onceToken; static NSURL *baseAppGroupURL; dispatch_once(&onceToken, ^ { NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[self groupName]]; baseAppGroupURL = [[NSURL alloc] initFileURLWithPath:[groupURL.path stringByAppendingPathComponent:TGFileCacheDomain]]; }); return baseAppGroupURL; } + (NSString *)groupName { static NSString *groupName = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { groupName = [@"group." stringByAppendingString:[[NSBundle mainBundle] bundleIdentifier]]; if ([groupName hasSuffix:@".watchkitapp.watchkitextension"]) groupName = [groupName substringWithRange:NSMakeRange(0, groupName.length - @".watchkitapp.watchkitextension".length)]; }); return groupName; } @end