VICacheConfiguration.m 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. //
  2. // VICacheConfiguration.m
  3. // VIMediaCacheDemo
  4. //
  5. // Created by Vito on 4/21/16.
  6. // Copyright © 2016 Vito. All rights reserved.
  7. //
  8. #import "VICacheConfiguration.h"
  9. #import "VICacheManager.h"
  10. #import <MobileCoreServices/MobileCoreServices.h>
  11. static NSString *kFileNameKey = @"kFileNameKey";
  12. static NSString *kCacheFragmentsKey = @"kCacheFragmentsKey";
  13. static NSString *kDownloadInfoKey = @"kDownloadInfoKey";
  14. static NSString *kContentInfoKey = @"kContentInfoKey";
  15. static NSString *kURLKey = @"kURLKey";
  16. @interface VICacheConfiguration () <NSCoding>
  17. @property (nonatomic, copy) NSString *filePath;
  18. @property (nonatomic, copy) NSString *fileName;
  19. @property (nonatomic, copy) NSArray<NSValue *> *internalCacheFragments;
  20. @property (nonatomic, copy) NSArray *downloadInfo;
  21. @end
  22. @implementation VICacheConfiguration
  23. + (instancetype)configurationWithFilePath:(NSString *)filePath {
  24. filePath = [self configurationFilePathForFilePath:filePath];
  25. VICacheConfiguration *configuration = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
  26. if (!configuration) {
  27. configuration = [[VICacheConfiguration alloc] init];
  28. configuration.fileName = [filePath lastPathComponent];
  29. }
  30. configuration.filePath = filePath;
  31. return configuration;
  32. }
  33. + (NSString *)configurationFilePathForFilePath:(NSString *)filePath {
  34. return [filePath stringByAppendingPathExtension:@"mt_cfg"];
  35. }
  36. - (NSArray<NSValue *> *)internalCacheFragments {
  37. if (!_internalCacheFragments) {
  38. _internalCacheFragments = [NSArray array];
  39. }
  40. return _internalCacheFragments;
  41. }
  42. - (NSArray *)downloadInfo {
  43. if (!_downloadInfo) {
  44. _downloadInfo = [NSArray array];
  45. }
  46. return _downloadInfo;
  47. }
  48. - (NSArray<NSValue *> *)cacheFragments {
  49. return [_internalCacheFragments copy];
  50. }
  51. - (float)progress {
  52. float progress = self.downloadedBytes / (float)self.contentInfo.contentLength;
  53. return progress;
  54. }
  55. - (long long)downloadedBytes {
  56. float bytes = 0;
  57. @synchronized (self.internalCacheFragments) {
  58. for (NSValue *range in self.internalCacheFragments) {
  59. bytes += range.rangeValue.length;
  60. }
  61. }
  62. return bytes;
  63. }
  64. - (float)downloadSpeed {
  65. long long bytes = 0;
  66. NSTimeInterval time = 0;
  67. @synchronized (self.downloadInfo) {
  68. for (NSArray *a in self.downloadInfo) {
  69. bytes += [[a firstObject] longLongValue];
  70. time += [[a lastObject] doubleValue];
  71. }
  72. }
  73. return bytes / 1024.0 / time;
  74. }
  75. #pragma mark - NSCoding
  76. - (void)encodeWithCoder:(NSCoder *)aCoder {
  77. [aCoder encodeObject:self.fileName forKey:kFileNameKey];
  78. [aCoder encodeObject:self.internalCacheFragments forKey:kCacheFragmentsKey];
  79. [aCoder encodeObject:self.downloadInfo forKey:kDownloadInfoKey];
  80. [aCoder encodeObject:self.contentInfo forKey:kContentInfoKey];
  81. [aCoder encodeObject:self.url forKey:kURLKey];
  82. }
  83. - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder {
  84. self = [super init];
  85. if (self) {
  86. _fileName = [aDecoder decodeObjectForKey:kFileNameKey];
  87. _internalCacheFragments = [[aDecoder decodeObjectForKey:kCacheFragmentsKey] mutableCopy];
  88. if (!_internalCacheFragments) {
  89. _internalCacheFragments = [NSArray array];
  90. }
  91. _downloadInfo = [aDecoder decodeObjectForKey:kDownloadInfoKey];
  92. _contentInfo = [aDecoder decodeObjectForKey:kContentInfoKey];
  93. _url = [aDecoder decodeObjectForKey:kURLKey];
  94. }
  95. return self;
  96. }
  97. #pragma mark - NSCopying
  98. - (id)copyWithZone:(nullable NSZone *)zone {
  99. VICacheConfiguration *configuration = [[VICacheConfiguration allocWithZone:zone] init];
  100. configuration.fileName = self.fileName;
  101. configuration.filePath = self.filePath;
  102. configuration.internalCacheFragments = self.internalCacheFragments;
  103. configuration.downloadInfo = self.downloadInfo;
  104. configuration.url = self.url;
  105. configuration.contentInfo = self.contentInfo;
  106. return configuration;
  107. }
  108. #pragma mark - Update
  109. - (void)save {
  110. @synchronized (self.internalCacheFragments) {
  111. [NSKeyedArchiver archiveRootObject:self toFile:self.filePath];
  112. }
  113. }
  114. - (void)addCacheFragment:(NSRange)fragment {
  115. if (fragment.location == NSNotFound || fragment.length == 0) {
  116. return;
  117. }
  118. @synchronized (self.internalCacheFragments) {
  119. NSMutableArray *internalCacheFragments = [self.internalCacheFragments mutableCopy];
  120. NSValue *fragmentValue = [NSValue valueWithRange:fragment];
  121. NSInteger count = self.internalCacheFragments.count;
  122. if (count == 0) {
  123. [internalCacheFragments addObject:fragmentValue];
  124. } else {
  125. NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
  126. [internalCacheFragments enumerateObjectsUsingBlock:^(NSValue * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
  127. NSRange range = obj.rangeValue;
  128. if ((fragment.location + fragment.length) <= range.location) {
  129. if (indexSet.count == 0) {
  130. [indexSet addIndex:idx];
  131. }
  132. *stop = YES;
  133. } else if (fragment.location <= (range.location + range.length) && (fragment.location + fragment.length) > range.location) {
  134. [indexSet addIndex:idx];
  135. } else if (fragment.location >= range.location + range.length) {
  136. if (idx == count - 1) { // Append to last index
  137. [indexSet addIndex:idx];
  138. }
  139. }
  140. }];
  141. if (indexSet.count > 1) {
  142. NSRange firstRange = self.internalCacheFragments[indexSet.firstIndex].rangeValue;
  143. NSRange lastRange = self.internalCacheFragments[indexSet.lastIndex].rangeValue;
  144. NSInteger location = MIN(firstRange.location, fragment.location);
  145. NSInteger endOffset = MAX(lastRange.location + lastRange.length, fragment.location + fragment.length);
  146. NSRange combineRange = NSMakeRange(location, endOffset - location);
  147. [internalCacheFragments removeObjectsAtIndexes:indexSet];
  148. [internalCacheFragments insertObject:[NSValue valueWithRange:combineRange] atIndex:indexSet.firstIndex];
  149. } else if (indexSet.count == 1) {
  150. NSRange firstRange = self.internalCacheFragments[indexSet.firstIndex].rangeValue;
  151. NSRange expandFirstRange = NSMakeRange(firstRange.location, firstRange.length + 1);
  152. NSRange expandFragmentRange = NSMakeRange(fragment.location, fragment.length + 1);
  153. NSRange intersectionRange = NSIntersectionRange(expandFirstRange, expandFragmentRange);
  154. if (intersectionRange.length > 0) { // Should combine
  155. NSInteger location = MIN(firstRange.location, fragment.location);
  156. NSInteger endOffset = MAX(firstRange.location + firstRange.length, fragment.location + fragment.length);
  157. NSRange combineRange = NSMakeRange(location, endOffset - location);
  158. [internalCacheFragments removeObjectAtIndex:indexSet.firstIndex];
  159. [internalCacheFragments insertObject:[NSValue valueWithRange:combineRange] atIndex:indexSet.firstIndex];
  160. } else {
  161. if (firstRange.location > fragment.location) {
  162. [internalCacheFragments insertObject:fragmentValue atIndex:[indexSet lastIndex]];
  163. } else {
  164. [internalCacheFragments insertObject:fragmentValue atIndex:[indexSet lastIndex] + 1];
  165. }
  166. }
  167. }
  168. }
  169. self.internalCacheFragments = [internalCacheFragments copy];
  170. }
  171. }
  172. - (void)addDownloadedBytes:(long long)bytes spent:(NSTimeInterval)time {
  173. @synchronized (self.downloadInfo) {
  174. self.downloadInfo = [self.downloadInfo arrayByAddingObject:@[@(bytes), @(time)]];
  175. }
  176. }
  177. @end
  178. @implementation VICacheConfiguration (VIConvenient)
  179. + (BOOL)createAndSaveDownloadedConfigurationForURL:(NSURL *)url error:(NSError **)error {
  180. NSString *filePath = [VICacheManager cachedFilePathForURL:url];
  181. NSFileManager *fileManager = [NSFileManager defaultManager];
  182. NSDictionary<NSFileAttributeKey, id> *attributes = [fileManager attributesOfItemAtPath:filePath error:error];
  183. if (!attributes) {
  184. return NO;
  185. }
  186. NSUInteger fileSize = (NSUInteger)attributes.fileSize;
  187. NSRange range = NSMakeRange(0, fileSize);
  188. VICacheConfiguration *configuration = [VICacheConfiguration configurationWithFilePath:filePath];
  189. configuration.url = url;
  190. VIContentInfo *contentInfo = [VIContentInfo new];
  191. NSString *fileExtension = [url pathExtension];
  192. NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
  193. NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
  194. if (!contentType) {
  195. contentType = @"application/octet-stream";
  196. }
  197. contentInfo.contentType = contentType;
  198. contentInfo.contentLength = fileSize;
  199. contentInfo.byteRangeAccessSupported = YES;
  200. contentInfo.downloadedContentLength = fileSize;
  201. configuration.contentInfo = contentInfo;
  202. [configuration addCacheFragment:range];
  203. [configuration save];
  204. return YES;
  205. }
  206. @end