|
| 1 | +// Copyright 2004-present Facebook. All Rights Reserved. |
| 2 | + |
| 3 | +#import "RCTCache.h" |
| 4 | + |
| 5 | +#import <UIKit/UIKit.h> |
| 6 | +#import <sys/xattr.h> |
| 7 | + |
| 8 | +static NSString *const CacheSubdirectoryName = @"ReactKit"; |
| 9 | +static NSString *const KeyExtendedAttributeName = @"com.facebook.ReactKit.RCTCacheManager.Key"; |
| 10 | +static dispatch_queue_t Queue; |
| 11 | + |
| 12 | +#pragma mark - Cache Record - |
| 13 | + |
| 14 | +@interface RCTCacheRecord : NSObject |
| 15 | + |
| 16 | +@property (nonatomic, copy) NSUUID *UUID; |
| 17 | +@property (nonatomic, copy) NSData *data; |
| 18 | + |
| 19 | +@end |
| 20 | + |
| 21 | +@implementation RCTCacheRecord |
| 22 | + |
| 23 | +@end |
| 24 | + |
| 25 | +#pragma mark - Cache |
| 26 | + |
| 27 | +@implementation RCTCache |
| 28 | +{ |
| 29 | + NSString *_name; |
| 30 | + NSFileManager *_fileManager; |
| 31 | + NSMutableDictionary *_storage; |
| 32 | + NSURL *_cacheDirectoryURL; |
| 33 | +} |
| 34 | + |
| 35 | ++ (void)initialize |
| 36 | +{ |
| 37 | + if (self == [RCTCache class]) { |
| 38 | + Queue = dispatch_queue_create("com.facebook.ReactKit.RCTCache", DISPATCH_QUEUE_SERIAL); |
| 39 | + } |
| 40 | +} |
| 41 | +- (instancetype)init |
| 42 | +{ |
| 43 | + return [self initWithName:@"default"]; |
| 44 | +} |
| 45 | + |
| 46 | +- (instancetype)initWithName:(NSString *)name |
| 47 | +{ |
| 48 | + NSParameterAssert(name.length < NAME_MAX); |
| 49 | + if ((self = [super init])) { |
| 50 | + _name = [name copy]; |
| 51 | + _fileManager = [[NSFileManager alloc] init]; |
| 52 | + _storage = [NSMutableDictionary dictionary]; |
| 53 | + |
| 54 | + NSURL *cacheDirectoryURL = [[_fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject]; |
| 55 | + cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:CacheSubdirectoryName isDirectory:YES]; |
| 56 | + _cacheDirectoryURL = [cacheDirectoryURL URLByAppendingPathComponent:name isDirectory:YES]; |
| 57 | + [_fileManager createDirectoryAtURL:_cacheDirectoryURL withIntermediateDirectories:YES attributes:nil error:NULL]; |
| 58 | + |
| 59 | + NSArray *fileURLs = [_fileManager contentsOfDirectoryAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL]; |
| 60 | + for (NSURL *fileURL in fileURLs) { |
| 61 | + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:fileURL.lastPathComponent]; |
| 62 | + if (!uuid) continue; |
| 63 | + |
| 64 | + NSString *key = [self keyOfItemAtURL:fileURL error:NULL]; |
| 65 | + if (!key) { |
| 66 | + [_fileManager removeItemAtURL:fileURL error:NULL]; |
| 67 | + continue; |
| 68 | + } |
| 69 | + |
| 70 | + RCTCacheRecord *record = [[RCTCacheRecord alloc] init]; |
| 71 | + record.UUID = uuid; |
| 72 | + _storage[key] = record; |
| 73 | + } |
| 74 | + } |
| 75 | + return self; |
| 76 | +} |
| 77 | + |
| 78 | +- (void)runOnQueue:(dispatch_block_t)block |
| 79 | +{ |
| 80 | + UIBackgroundTaskIdentifier identifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]; |
| 81 | + dispatch_async(Queue, ^{ |
| 82 | + if (block) block(); |
| 83 | + if (identifier != UIBackgroundTaskInvalid) { |
| 84 | + [[UIApplication sharedApplication] endBackgroundTask:identifier]; |
| 85 | + } |
| 86 | + }); |
| 87 | +} |
| 88 | + |
| 89 | +- (BOOL)hasDataForKey:(NSString *)key |
| 90 | +{ |
| 91 | + return _storage[key] != nil; |
| 92 | +} |
| 93 | + |
| 94 | +- (void)fetchDataForKey:(NSString *)key completionHandler:(void (^)(NSData *))completionHandler |
| 95 | +{ |
| 96 | + NSParameterAssert(key.length > 0); |
| 97 | + NSParameterAssert(completionHandler != nil); |
| 98 | + [self runOnQueue:^{ |
| 99 | + RCTCacheRecord *record = _storage[key]; |
| 100 | + if (record && !record.data) { |
| 101 | + record.data = [NSData dataWithContentsOfURL:[_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]]; |
| 102 | + } |
| 103 | + |
| 104 | + dispatch_async(dispatch_get_main_queue(), ^{ |
| 105 | + completionHandler(record.data); |
| 106 | + }); |
| 107 | + }]; |
| 108 | +} |
| 109 | + |
| 110 | +- (void)setData:(NSData *)data forKey:(NSString *)key |
| 111 | +{ |
| 112 | + NSParameterAssert(key.length > 0); |
| 113 | + [self runOnQueue:^{ |
| 114 | + RCTCacheRecord *record = _storage[key]; |
| 115 | + if (data) { |
| 116 | + if (!record) { |
| 117 | + record = [[RCTCacheRecord alloc] init]; |
| 118 | + record.UUID = [NSUUID UUID]; |
| 119 | + _storage[key] = record; |
| 120 | + } |
| 121 | + |
| 122 | + record.data = data; |
| 123 | + |
| 124 | + NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]; |
| 125 | + [data writeToURL:fileURL options:NSDataWritingAtomic error:NULL]; |
| 126 | + } else if (record) { |
| 127 | + [_storage removeObjectForKey:key]; |
| 128 | + |
| 129 | + NSURL *fileURL = [_cacheDirectoryURL URLByAppendingPathComponent:record.UUID.UUIDString]; |
| 130 | + [_fileManager removeItemAtURL:fileURL error:NULL]; |
| 131 | + } |
| 132 | + }]; |
| 133 | +} |
| 134 | + |
| 135 | +- (void)removeAllData |
| 136 | +{ |
| 137 | + [self runOnQueue:^{ |
| 138 | + [_storage removeAllObjects]; |
| 139 | + |
| 140 | + NSDirectoryEnumerator *enumerator = [_fileManager enumeratorAtURL:_cacheDirectoryURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles errorHandler:nil]; |
| 141 | + for (NSURL *fileURL in enumerator) { |
| 142 | + [_fileManager removeItemAtURL:fileURL error:NULL]; |
| 143 | + } |
| 144 | + }]; |
| 145 | +} |
| 146 | + |
| 147 | +#pragma mark - Extended Attributes |
| 148 | + |
| 149 | +- (NSError *)errorWithPOSIXErrorNumber:(int)errorNumber |
| 150 | +{ |
| 151 | + NSDictionary *userInfo = @{ |
| 152 | + NSLocalizedDescriptionKey: @(strerror(errorNumber)) |
| 153 | + }; |
| 154 | + return [NSError errorWithDomain:NSPOSIXErrorDomain code:errorNumber userInfo:userInfo]; |
| 155 | +} |
| 156 | + |
| 157 | +- (BOOL)setAttribute:(NSString *)key value:(NSString *)value ofItemAtURL:(NSURL *)fileURL error:(NSError **)error |
| 158 | +{ |
| 159 | + const char *path = fileURL.fileSystemRepresentation; |
| 160 | + |
| 161 | + int result; |
| 162 | + if (value) { |
| 163 | + const char *valueUTF8String = value.UTF8String; |
| 164 | + result = setxattr(path, key.UTF8String, valueUTF8String, strlen(valueUTF8String), 0, 0); |
| 165 | + } else { |
| 166 | + result = removexattr(path, key.UTF8String, 0); |
| 167 | + } |
| 168 | + |
| 169 | + if (result) { |
| 170 | + if (error) *error = [self errorWithPOSIXErrorNumber:errno]; |
| 171 | + return NO; |
| 172 | + } |
| 173 | + |
| 174 | + return YES; |
| 175 | +} |
| 176 | + |
| 177 | +- (NSString *)attribute:(NSString *)key ofItemAtURL:(NSURL *)fileURL error:(NSError **)error |
| 178 | +{ |
| 179 | + const char *path = fileURL.fileSystemRepresentation; |
| 180 | + const ssize_t length = getxattr(path, key.UTF8String, NULL, 0, 0, 0); |
| 181 | + if (length <= 0) { |
| 182 | + if (error) *error = [self errorWithPOSIXErrorNumber:errno]; |
| 183 | + return nil; |
| 184 | + } |
| 185 | + |
| 186 | + char *buffer = malloc(length); |
| 187 | + ssize_t result = getxattr(path, key.UTF8String, buffer, length, 0, 0); |
| 188 | + if (result == 0) { |
| 189 | + return [[NSString alloc] initWithBytesNoCopy:buffer length:length encoding:NSUTF8StringEncoding freeWhenDone:YES]; |
| 190 | + } |
| 191 | + |
| 192 | + free(buffer); |
| 193 | + if (error) *error = [self errorWithPOSIXErrorNumber:errno]; |
| 194 | + return nil; |
| 195 | +} |
| 196 | + |
| 197 | +#pragma mark - Extended Attributes - Key |
| 198 | + |
| 199 | +- (NSString *)keyOfItemAtURL:(NSURL *)fileURL error:(NSError **)error |
| 200 | +{ |
| 201 | + return [self attribute:KeyExtendedAttributeName ofItemAtURL:fileURL error:error]; |
| 202 | +} |
| 203 | + |
| 204 | +- (BOOL)setKey:(NSString *)key ofItemAtURL:(NSURL *)fileURL error:(NSError **)error |
| 205 | +{ |
| 206 | + return [self setAttribute:KeyExtendedAttributeName value:key ofItemAtURL:fileURL error:error]; |
| 207 | +} |
| 208 | + |
| 209 | +@end |
0 commit comments