├── .gitignore ├── Framework ├── Info.plist ├── KTVHTTPCache.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── KTVHTTPCache.xcscheme └── module.modulemap ├── KTVHTTPCache.podspec ├── KTVHTTPCache ├── Classes │ ├── KTVHCCommon │ │ ├── KTVHCError.h │ │ ├── KTVHCError.m │ │ ├── KTVHCLog.h │ │ ├── KTVHCLog.m │ │ ├── KTVHCRange.h │ │ └── KTVHCRange.m │ ├── KTVHCDataStorage │ │ ├── KTVHCData+Internal.h │ │ ├── KTVHCDataCacheItem.h │ │ ├── KTVHCDataCacheItem.m │ │ ├── KTVHCDataCacheItemZone.h │ │ ├── KTVHCDataCacheItemZone.m │ │ ├── KTVHCDataCallback.h │ │ ├── KTVHCDataCallback.m │ │ ├── KTVHCDataFileSource.h │ │ ├── KTVHCDataFileSource.m │ │ ├── KTVHCDataLoader.h │ │ ├── KTVHCDataLoader.m │ │ ├── KTVHCDataNetworkSource.h │ │ ├── KTVHCDataNetworkSource.m │ │ ├── KTVHCDataReader.h │ │ ├── KTVHCDataReader.m │ │ ├── KTVHCDataRequest.h │ │ ├── KTVHCDataRequest.m │ │ ├── KTVHCDataResponse.h │ │ ├── KTVHCDataResponse.m │ │ ├── KTVHCDataSource.h │ │ ├── KTVHCDataSourceManager.h │ │ ├── KTVHCDataSourceManager.m │ │ ├── KTVHCDataStorage.h │ │ ├── KTVHCDataStorage.m │ │ ├── KTVHCDataUnit.h │ │ ├── KTVHCDataUnit.m │ │ ├── KTVHCDataUnitItem.h │ │ ├── KTVHCDataUnitItem.m │ │ ├── KTVHCDataUnitPool.h │ │ ├── KTVHCDataUnitPool.m │ │ ├── KTVHCDataUnitQueue.h │ │ └── KTVHCDataUnitQueue.m │ ├── KTVHCDownload │ │ ├── KTVHCDownload.h │ │ └── KTVHCDownload.m │ ├── KTVHCHTTPServer │ │ ├── KTVHCHTTPConnection.h │ │ ├── KTVHCHTTPConnection.m │ │ ├── KTVHCHTTPHLSResponse.h │ │ ├── KTVHCHTTPHLSResponse.m │ │ ├── KTVHCHTTPHeader.h │ │ ├── KTVHCHTTPResponse.h │ │ ├── KTVHCHTTPResponse.m │ │ ├── KTVHCHTTPServer.h │ │ └── KTVHCHTTPServer.m │ └── KTVHCTools │ │ ├── KTVHCPathTool.h │ │ ├── KTVHCPathTool.m │ │ ├── KTVHCURLTool.h │ │ └── KTVHCURLTool.m ├── CocoaHTTPServer │ ├── Categories │ │ ├── DDData.h │ │ ├── DDData.m │ │ ├── DDNumber.h │ │ ├── DDNumber.m │ │ ├── DDRange.h │ │ └── DDRange.m │ ├── HTTPAuthenticationRequest.h │ ├── HTTPAuthenticationRequest.m │ ├── HTTPConnection.h │ ├── HTTPConnection.m │ ├── HTTPLogging.h │ ├── HTTPLogging.m │ ├── HTTPMessage.h │ ├── HTTPMessage.m │ ├── HTTPResponse.h │ ├── HTTPServer.h │ ├── HTTPServer.m │ ├── Mime │ │ ├── MultipartFormDataParser.h │ │ ├── MultipartFormDataParser.m │ │ ├── MultipartMessageHeader.h │ │ ├── MultipartMessageHeader.m │ │ ├── MultipartMessageHeaderField.h │ │ └── MultipartMessageHeaderField.m │ ├── Responses │ │ ├── HTTPAsyncFileResponse.h │ │ ├── HTTPAsyncFileResponse.m │ │ ├── HTTPDataResponse.h │ │ ├── HTTPDataResponse.m │ │ ├── HTTPDynamicFileResponse.h │ │ ├── HTTPDynamicFileResponse.m │ │ ├── HTTPErrorResponse.h │ │ ├── HTTPErrorResponse.m │ │ ├── HTTPFileResponse.h │ │ ├── HTTPFileResponse.m │ │ ├── HTTPRedirectResponse.h │ │ └── HTTPRedirectResponse.m │ ├── WebSocket.h │ └── WebSocket.m ├── KTVHTTPCache.h └── KTVHTTPCache.m ├── LICENSE ├── README.md ├── README_CN.md ├── Vendors └── CocoaAsyncSocket │ ├── GCDAsyncSocket.h │ └── GCDAsyncSocket.m ├── demo ├── KTVHTTPCacheDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── KTVHTTPCacheDemo.xcscheme └── KTVHTTPCacheDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ ├── SGCacheViewController │ ├── CacheItemView.h │ ├── SGCacheItemView.h │ ├── SGCacheItemView.m │ ├── SGCacheItemZoneCell.h │ ├── SGCacheItemZoneCell.m │ ├── SGCacheViewController.h │ └── SGCacheViewController.m │ ├── SGMediaViewController │ ├── SGMediaItem.h │ ├── SGMediaItem.m │ ├── SGMediaItemCell.h │ ├── SGMediaItemCell.m │ ├── SGMediaViewController.h │ └── SGMediaViewController.m │ ├── SGPlayerViewController │ ├── SGPlayerViewController.h │ └── SGPlayerViewController.m │ ├── SGPreloadViewController │ ├── SGPreloadItemCell.h │ ├── SGPreloadItemCell.m │ ├── SGPreloadViewController.h │ └── SGPreloadViewController.m │ └── main.m └── documents └── flow-chart.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.pbxuser 3 | !default.pbxuser 4 | *.mode1v3 5 | !default.mode1v3 6 | *.mode2v3 7 | !default.mode2v3 8 | *.perspectivev3 9 | !default.perspectivev3 10 | xcuserdata 11 | *.xccheckout 12 | *.moved-aside 13 | DerivedData 14 | *.hmap 15 | *.ipa 16 | *.xcuserstate 17 | .DS_Store -------------------------------------------------------------------------------- /Framework/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 3.0.2 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | LSApplicationCategoryType 22 | 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Framework/KTVHTTPCache.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Framework/KTVHTTPCache.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Framework/KTVHTTPCache.xcodeproj/xcshareddata/xcschemes/KTVHTTPCache.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Framework/module.modulemap: -------------------------------------------------------------------------------- 1 | framework module KTVHTTPCache { 2 | umbrella header "KTVHTTPCache.h" 3 | export * 4 | module * { export * } 5 | } 6 | -------------------------------------------------------------------------------- /KTVHTTPCache.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "KTVHTTPCache" 3 | s.version = "3.0.2" 4 | s.summary = "A powerful media cache framework." 5 | s.homepage = "https://github.com/ChangbaDevs/KTVHTTPCache" 6 | s.license = { :type => "MIT", :file => "LICENSE" } 7 | s.author = { "Single" => "libobjc@gmail.com" } 8 | s.social_media_url = "https://weibo.com/3118550737" 9 | s.platform = :ios, "12.0" 10 | s.source = { :git => "https://github.com/ChangbaDevs/KTVHTTPCache.git", :tag => "#{s.version}" } 11 | s.source_files = "KTVHTTPCache", "KTVHTTPCache/**/*.{h,m}" 12 | s.public_header_files = 13 | "KTVHTTPCache/KTVHTTPCache.h", 14 | "KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.h", 15 | "KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.h", 16 | "KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataReader.h", 17 | "KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.h", 18 | "KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.h", 19 | "KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.h", 20 | "KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.h", 21 | "KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.h" 22 | s.frameworks = "UIKit", "Foundation" 23 | s.requires_arc = true 24 | s.dependency 'CocoaAsyncSocket' 25 | end 26 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCError.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/17. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #if defined(__cplusplus) 12 | #define KTVHTTPCACHE_EXTERN extern "C" 13 | #else 14 | #define KTVHTTPCACHE_EXTERN extern 15 | #endif 16 | 17 | KTVHTTPCACHE_EXTERN NSString * const KTVHCErrorUserInfoKeyURL; 18 | KTVHTTPCACHE_EXTERN NSString * const KTVHCErrorUserInfoKeyRequest; 19 | KTVHTTPCACHE_EXTERN NSString * const KTVHCErrorUserInfoKeyResponse; 20 | 21 | typedef NS_ENUM(NSInteger, KTVHCErrorCode) { 22 | KTVHCErrorCodeException = -192700, 23 | KTVHCErrorCodeNotEnoughDiskSpace = -192701, 24 | KTVHCErrorCodeResponseClass = -192702, 25 | KTVHCErrorCodeResponseStatusCode = -192703, 26 | KTVHCErrorCodeResponseContentType = -192704, 27 | KTVHCErrorCodeResponseContentLength = -192705, 28 | }; 29 | 30 | @interface KTVHCError : NSObject 31 | 32 | + (NSError *)errorForException:(NSException *)exception; 33 | 34 | + (NSError *)errorForNotEnoughDiskSpace:(long long)totlaContentLength 35 | request:(long long)currentContentLength 36 | totalCacheLength:(long long)totalCacheLength 37 | maxCacheLength:(long long)maxCacheLength; 38 | 39 | + (NSError *)errorForResponseClass:(NSURL *)URL 40 | request:(NSURLRequest *)request 41 | response:(NSURLResponse *)response; 42 | 43 | + (NSError *)errorForResponseStatusCode:(NSURL *)URL 44 | request:(NSURLRequest *)request 45 | response:(NSURLResponse *)response; 46 | 47 | + (NSError *)errorForResponseContentType:(NSURL *)URL 48 | request:(NSURLRequest *)request 49 | response:(NSURLResponse *)response; 50 | 51 | + (NSError *)errorForResponseContentLength:(NSURL *)URL 52 | request:(NSURLRequest *)request 53 | response:(NSURLResponse *)response; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCCommon/KTVHCError.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCError.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/17. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCError.h" 10 | 11 | NSString * const KTVHCErrorUserInfoKeyURL = @"KTVHCErrorUserInfoKeyURL"; 12 | NSString * const KTVHCErrorUserInfoKeyRequest = @"KTVHCErrorUserInfoKeyRequest"; 13 | NSString * const KTVHCErrorUserInfoKeyResponse = @"KTVHCErrorUserInfoKeyResponse"; 14 | 15 | @implementation KTVHCError 16 | 17 | + (NSError *)errorForException:(NSException *)exception 18 | { 19 | NSError *error = [NSError errorWithDomain:@"KTVHTTPCache error" 20 | code:KTVHCErrorCodeException 21 | userInfo:exception.userInfo]; 22 | return error; 23 | } 24 | 25 | + (NSError *)errorForNotEnoughDiskSpace:(long long)totlaContentLength 26 | request:(long long)currentContentLength 27 | totalCacheLength:(long long)totalCacheLength 28 | maxCacheLength:(long long)maxCacheLength 29 | { 30 | NSError *error = [NSError errorWithDomain:@"KTVHTTPCache error" 31 | code:KTVHCErrorCodeNotEnoughDiskSpace 32 | userInfo:@{@"totlaContentLength" : @(totlaContentLength), 33 | @"currentContentLength" : @(currentContentLength), 34 | @"totalCacheLength" : @(totalCacheLength), 35 | @"maxCacheLength" : @(maxCacheLength)}]; 36 | return error; 37 | } 38 | 39 | + (NSError *)errorForResponseClass:(NSURL *)URL 40 | request:(NSURLRequest *)request 41 | response:(NSURLResponse *)response 42 | { 43 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 44 | if (URL) { 45 | [userInfo setObject:URL forKey:KTVHCErrorUserInfoKeyURL]; 46 | } 47 | if (request) { 48 | [userInfo setObject:request forKey:KTVHCErrorUserInfoKeyRequest]; 49 | } 50 | if (response) { 51 | [userInfo setObject:response forKey:KTVHCErrorUserInfoKeyResponse]; 52 | } 53 | NSError *error = [NSError errorWithDomain:@"KTVHTTPCache error" 54 | code:KTVHCErrorCodeResponseClass 55 | userInfo:userInfo]; 56 | return error; 57 | } 58 | 59 | + (NSError *)errorForResponseStatusCode:(NSURL *)URL 60 | request:(NSURLRequest *)request 61 | response:(NSURLResponse *)response 62 | { 63 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 64 | if (URL) { 65 | [userInfo setObject:URL forKey:KTVHCErrorUserInfoKeyURL]; 66 | } 67 | if (request) { 68 | [userInfo setObject:request forKey:KTVHCErrorUserInfoKeyRequest]; 69 | } 70 | if (response) { 71 | [userInfo setObject:response forKey:KTVHCErrorUserInfoKeyResponse]; 72 | } 73 | NSError *error = [NSError errorWithDomain:@"KTVHTTPCache error" 74 | code:KTVHCErrorCodeResponseStatusCode 75 | userInfo:userInfo]; 76 | return error; 77 | } 78 | 79 | + (NSError *)errorForResponseContentType:(NSURL *)URL 80 | request:(NSURLRequest *)request 81 | response:(NSURLResponse *)response 82 | { 83 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 84 | if (URL) { 85 | [userInfo setObject:URL forKey:KTVHCErrorUserInfoKeyURL]; 86 | } 87 | if (request) { 88 | [userInfo setObject:request forKey:KTVHCErrorUserInfoKeyRequest]; 89 | } 90 | if (response) { 91 | [userInfo setObject:response forKey:KTVHCErrorUserInfoKeyResponse]; 92 | } 93 | NSError *error = [NSError errorWithDomain:@"KTVHTTPCache error" 94 | code:KTVHCErrorCodeResponseContentType 95 | userInfo:userInfo]; 96 | return error; 97 | } 98 | 99 | + (NSError *)errorForResponseContentLength:(NSURL *)URL 100 | request:(NSURLRequest *)request 101 | response:(NSURLResponse *)response 102 | { 103 | NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 104 | if (URL) { 105 | [userInfo setObject:URL forKey:KTVHCErrorUserInfoKeyURL]; 106 | } 107 | if (request) { 108 | [userInfo setObject:request forKey:KTVHCErrorUserInfoKeyRequest]; 109 | } 110 | if (response) { 111 | [userInfo setObject:response forKey:KTVHCErrorUserInfoKeyResponse]; 112 | } 113 | NSError *error = [NSError errorWithDomain:@"KTVHTTPCache error" 114 | code:KTVHCErrorCodeResponseContentLength 115 | userInfo:userInfo]; 116 | return error; 117 | } 118 | 119 | @end 120 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCCommon/KTVHCLog.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCLog.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/17. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCLog.h" 10 | #import "KTVHCPathTool.h" 11 | 12 | #import 13 | 14 | @interface KTVHCLog () 15 | 16 | @property (nonatomic, strong) NSLock *lock; 17 | @property (nonatomic, strong) NSFileHandle *writingHandle; 18 | @property (nonatomic, strong) NSMutableDictionary *internalErrors; 19 | 20 | @end 21 | 22 | @implementation KTVHCLog 23 | 24 | + (instancetype)log 25 | { 26 | static KTVHCLog *obj = nil; 27 | static dispatch_once_t onceToken; 28 | dispatch_once(&onceToken, ^{ 29 | obj = [[self alloc] init]; 30 | }); 31 | return obj; 32 | } 33 | 34 | - (instancetype)init 35 | { 36 | if (self = [super init]) { 37 | self.consoleLogEnable = NO; 38 | self.recordLogEnable = NO; 39 | self.lock = [[NSLock alloc] init]; 40 | self.internalErrors = [NSMutableDictionary dictionary]; 41 | } 42 | return self; 43 | } 44 | 45 | - (void)addRecordLog:(NSString *)log 46 | { 47 | if (!self.recordLogEnable) { 48 | return; 49 | } 50 | if (log.length <= 0) { 51 | return; 52 | } 53 | [self.lock lock]; 54 | NSString *string = [NSString stringWithFormat:@"%@ %@\n", [NSDate date], log]; 55 | NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding]; 56 | if (!self.writingHandle) { 57 | [KTVHCPathTool deleteFileAtPath:[KTVHCPathTool logPath]]; 58 | [KTVHCPathTool createFileAtPath:[KTVHCPathTool logPath]]; 59 | self.writingHandle = [NSFileHandle fileHandleForWritingAtPath:[KTVHCPathTool logPath]]; 60 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil]; 61 | } 62 | [self.writingHandle writeData:data]; 63 | [self.lock unlock]; 64 | } 65 | 66 | - (NSURL *)recordLogFileURL 67 | { 68 | NSURL *URL = nil; 69 | [self.lock lock]; 70 | long long size = [KTVHCPathTool sizeAtPath:[KTVHCPathTool logPath]]; 71 | if (size > 0) { 72 | URL = [NSURL fileURLWithPath:[KTVHCPathTool logPath]]; 73 | } 74 | [self.lock unlock]; 75 | return URL; 76 | } 77 | 78 | - (void)deleteRecordLogFile 79 | { 80 | [self.lock lock]; 81 | [self.writingHandle synchronizeFile]; 82 | [self.writingHandle closeFile]; 83 | self.writingHandle = nil; 84 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil]; 85 | [self.lock unlock]; 86 | } 87 | 88 | - (void)addError:(NSError *)error forURL:(NSURL *)URL 89 | { 90 | if (!URL || ![error isKindOfClass:[NSError class]]) { 91 | return; 92 | } 93 | [self.lock lock]; 94 | [self.internalErrors setObject:error forKey:URL]; 95 | [self.lock unlock]; 96 | } 97 | 98 | - (NSDictionary *)errors 99 | { 100 | [self.lock lock]; 101 | NSDictionary *ret = [self.internalErrors copy]; 102 | [self.lock unlock]; 103 | return ret; 104 | } 105 | 106 | - (NSError *)errorForURL:(NSURL *)URL 107 | { 108 | if (!URL) { 109 | return nil; 110 | } 111 | [self.lock lock]; 112 | NSError *ret = [self.internalErrors objectForKey:URL]; 113 | [self.lock unlock]; 114 | return ret; 115 | } 116 | 117 | - (void)cleanErrorForURL:(NSURL *)URL 118 | { 119 | [self.lock lock]; 120 | [self.internalErrors removeObjectForKey:URL]; 121 | [self.lock unlock]; 122 | } 123 | 124 | #pragma mark - UIApplicationWillTerminateNotification 125 | 126 | - (void)applicationWillTerminate:(NSNotification *)notification 127 | { 128 | [self deleteRecordLogFile]; 129 | } 130 | 131 | @end 132 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCRange.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2018/5/20. 6 | // Copyright © 2018年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef struct KTVHCRange { 12 | long long start; 13 | long long end; 14 | } KTVHCRange; 15 | 16 | static const long long KTVHCNotFound = LONG_LONG_MAX; 17 | 18 | BOOL KTVHCRangeIsFull(KTVHCRange range); 19 | BOOL KTVHCRangeIsVaild(KTVHCRange range); 20 | BOOL KTVHCRangeIsInvaild(KTVHCRange range); 21 | BOOL KTVHCEqualRanges(KTVHCRange range1, KTVHCRange range2); 22 | long long KTVHCRangeGetLength(KTVHCRange range); 23 | NSString * KTVHCStringFromRange(KTVHCRange range); 24 | NSDictionary * KTVHCRangeFillToRequestHeaders(KTVHCRange range, NSDictionary *headers); 25 | NSDictionary * KTVHCRangeFillToRequestHeadersIfNeeded(KTVHCRange range, NSDictionary *headers); 26 | NSDictionary * KTVHCRangeFillToResponseHeaders(KTVHCRange range, NSDictionary *headers, long long totalLength); 27 | 28 | KTVHCRange KTVHCMakeRange(long long start, long long end); 29 | KTVHCRange KTVHCRangeZero(void); 30 | KTVHCRange KTVHCRangeFull(void); 31 | KTVHCRange KTVHCRangeInvaild(void); 32 | KTVHCRange KTVHCRangeWithSeparateValue(NSString *value); 33 | KTVHCRange KTVHCRangeWithRequestHeaderValue(NSString *value); 34 | KTVHCRange KTVHCRangeWithResponseHeaderValue(NSString *value, long long *totalLength); 35 | KTVHCRange KTVHCRangeWithEnsureLength(KTVHCRange range, long long ensureLength); 36 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCCommon/KTVHCRange.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCRange.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2018/5/20. 6 | // Copyright © 2018年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCRange.h" 10 | 11 | BOOL KTVHCRangeIsFull(KTVHCRange range) 12 | { 13 | return KTVHCEqualRanges(range, KTVHCRangeFull()); 14 | } 15 | 16 | BOOL KTVHCRangeIsVaild(KTVHCRange range) 17 | { 18 | return !KTVHCRangeIsInvaild(range); 19 | } 20 | 21 | BOOL KTVHCRangeIsInvaild(KTVHCRange range) 22 | { 23 | return KTVHCEqualRanges(range, KTVHCRangeInvaild()); 24 | } 25 | 26 | BOOL KTVHCEqualRanges(KTVHCRange range1, KTVHCRange range2) 27 | { 28 | return range1.start == range2.start && range1.end == range2.end; 29 | } 30 | 31 | long long KTVHCRangeGetLength(KTVHCRange range) 32 | { 33 | if (range.start == KTVHCNotFound || range.end == KTVHCNotFound) { 34 | return KTVHCNotFound; 35 | } 36 | return range.end - range.start + 1; 37 | } 38 | 39 | NSString *KTVHCStringFromRange(KTVHCRange range) 40 | { 41 | return [NSString stringWithFormat:@"Range : {%lld, %lld}", range.start, range.end]; 42 | } 43 | 44 | NSString *KTVHCRangeGetHeaderString(KTVHCRange range) 45 | { 46 | NSMutableString *string = [NSMutableString stringWithFormat:@"bytes="]; 47 | if (range.start != KTVHCNotFound) { 48 | [string appendFormat:@"%lld", range.start]; 49 | } 50 | [string appendFormat:@"-"]; 51 | if (range.end != KTVHCNotFound) { 52 | [string appendFormat:@"%lld", range.end]; 53 | } 54 | return [string copy]; 55 | } 56 | 57 | NSDictionary *KTVHCRangeFillToRequestHeaders(KTVHCRange range, NSDictionary *headers) 58 | { 59 | NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithDictionary:headers]; 60 | [ret setObject:KTVHCRangeGetHeaderString(range) forKey:@"Range"]; 61 | return ret; 62 | } 63 | 64 | NSDictionary *KTVHCRangeFillToRequestHeadersIfNeeded(KTVHCRange range, NSDictionary *headers) 65 | { 66 | if ([headers objectForKey:@"Range"]) { 67 | return headers; 68 | } 69 | return KTVHCRangeFillToRequestHeaders(range, headers); 70 | } 71 | 72 | NSDictionary *KTVHCRangeFillToResponseHeaders(KTVHCRange range, NSDictionary *headers, long long totalLength) 73 | { 74 | NSMutableDictionary *ret = [NSMutableDictionary dictionaryWithDictionary:headers]; 75 | long long currentLength = KTVHCRangeGetLength(range); 76 | [ret setObject:[NSString stringWithFormat:@"%lld", currentLength] forKey:@"Content-Length"]; 77 | [ret setObject:[NSString stringWithFormat:@"bytes %lld-%lld/%lld", range.start, range.end, totalLength] forKey:@"Content-Range"]; 78 | return ret; 79 | } 80 | 81 | KTVHCRange KTVHCMakeRange(long long start, long long end) 82 | { 83 | KTVHCRange range = {start, end}; 84 | return range; 85 | } 86 | 87 | KTVHCRange KTVHCRangeZero(void) 88 | { 89 | return KTVHCMakeRange(0, 0); 90 | } 91 | 92 | KTVHCRange KTVHCRangeFull(void) 93 | { 94 | return KTVHCMakeRange(0, KTVHCNotFound); 95 | } 96 | 97 | KTVHCRange KTVHCRangeInvaild(void) 98 | { 99 | return KTVHCMakeRange(KTVHCNotFound, KTVHCNotFound); 100 | } 101 | 102 | KTVHCRange KTVHCRangeWithSeparateValue(NSString *value) 103 | { 104 | KTVHCRange range = KTVHCRangeInvaild(); 105 | if (value.length > 0) { 106 | NSArray *components = [value componentsSeparatedByString:@","]; 107 | if (components.count == 1) { 108 | components = [components.firstObject componentsSeparatedByString:@"-"]; 109 | if (components.count == 2) { 110 | NSString *startString = [components objectAtIndex:0]; 111 | NSInteger startValue = [startString integerValue]; 112 | NSString *endString = [components objectAtIndex:1]; 113 | NSInteger endValue = [endString integerValue]; 114 | if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { 115 | // The second 500 bytes: "500-999" 116 | range.start = startValue; 117 | range.end = endValue; 118 | } else if (startString.length && (startValue >= 0)) { 119 | // The bytes after 9500 bytes: "9500-" 120 | range.start = startValue; 121 | range.end = KTVHCNotFound; 122 | } else if (endString.length && (endValue > 0)) { 123 | // The final 500 bytes: "-500" 124 | range.start = KTVHCNotFound; 125 | range.end = endValue; 126 | } 127 | } 128 | } 129 | } 130 | return range; 131 | } 132 | 133 | KTVHCRange KTVHCRangeWithRequestHeaderValue(NSString *value) 134 | { 135 | if ([value hasPrefix:@"bytes="]) { 136 | NSString *rangeString = [value substringFromIndex:6]; 137 | return KTVHCRangeWithSeparateValue(rangeString); 138 | } 139 | return KTVHCRangeInvaild(); 140 | } 141 | 142 | KTVHCRange KTVHCRangeWithResponseHeaderValue(NSString *value, long long *totalLength) 143 | { 144 | if ([value hasPrefix:@"bytes "]) { 145 | value = [value stringByReplacingOccurrencesOfString:@"bytes " withString:@""]; 146 | NSRange range = [value rangeOfString:@"/"]; 147 | if (range.location != NSNotFound) { 148 | NSString *rangeString = [value substringToIndex:range.location]; 149 | NSString *totalLengthString = [value substringFromIndex:range.location + range.length]; 150 | *totalLength = totalLengthString.longLongValue; 151 | return KTVHCRangeWithSeparateValue(rangeString); 152 | } 153 | } 154 | return KTVHCRangeInvaild(); 155 | } 156 | 157 | KTVHCRange KTVHCRangeWithEnsureLength(KTVHCRange range, long long ensureLength) 158 | { 159 | if (ensureLength > 0 && 160 | (range.end == KTVHCNotFound || range.end >= ensureLength)) { 161 | return KTVHCMakeRange(range.start, ensureLength - 1); 162 | } 163 | return range; 164 | } 165 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCData+Internal.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCData+Internal.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataCacheItemZone.h" 11 | #import "KTVHCDataCacheItem.h" 12 | #import "KTVHCDataResponse.h" 13 | #import "KTVHCDataRequest.h" 14 | #import "KTVHCDataReader.h" 15 | #import "KTVHCDataLoader.h" 16 | 17 | #pragma mark - KTVHCDataReader 18 | 19 | @interface KTVHCDataReader () 20 | 21 | - (instancetype)initWithRequest:(KTVHCDataRequest *)request NS_DESIGNATED_INITIALIZER; 22 | 23 | @end 24 | 25 | #pragma mark - KTVHCDataLoader 26 | 27 | @interface KTVHCDataLoader () 28 | 29 | - (instancetype)initWithRequest:(KTVHCDataRequest *)request NS_DESIGNATED_INITIALIZER; 30 | 31 | @end 32 | 33 | #pragma mark - KTVHCDataRequest 34 | 35 | @interface KTVHCDataRequest () 36 | 37 | - (KTVHCDataRequest *)newRequestWithRange:(KTVHCRange)range; 38 | - (KTVHCDataRequest *)newRequestWithTotalLength:(long long)totalLength; 39 | 40 | @end 41 | 42 | #pragma mark - KTVHCDataResponse 43 | 44 | @interface KTVHCDataResponse () 45 | 46 | - (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers NS_DESIGNATED_INITIALIZER; 47 | 48 | @end 49 | 50 | #pragma mark - KTVHCDataCacheItem 51 | 52 | @interface KTVHCDataCacheItem () 53 | 54 | - (instancetype)initWithURL:(NSURL *)URL 55 | zones:(NSArray *)zones 56 | totalLength:(long long)totalLength 57 | cacheLength:(long long)cacheLength 58 | vaildLength:(long long)vaildLength NS_DESIGNATED_INITIALIZER; 59 | 60 | @end 61 | 62 | #pragma mark - KTVHCDataCacheItemZone 63 | 64 | @interface KTVHCDataCacheItemZone () 65 | 66 | - (instancetype)initWithOffset:(long long)offset length:(long long)length NS_DESIGNATED_INITIALIZER; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataCacheItem.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class KTVHCDataCacheItemZone; 12 | 13 | @interface KTVHCDataCacheItem : NSObject 14 | 15 | + (instancetype)new NS_UNAVAILABLE; 16 | - (instancetype)init NS_UNAVAILABLE; 17 | 18 | @property (nonatomic, copy, readonly) NSURL *URL; 19 | @property (nonatomic, copy, readonly) NSArray *zones; 20 | 21 | @property (nonatomic, readonly) long long totalLength; 22 | @property (nonatomic, readonly) long long cacheLength; 23 | @property (nonatomic, readonly) long long vaildLength; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataCacheItem.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataCacheItem.h" 10 | #import "KTVHCData+Internal.h" 11 | 12 | @implementation KTVHCDataCacheItem 13 | 14 | - (instancetype)initWithURL:(NSURL *)URL 15 | zones:(NSArray *)zones 16 | totalLength:(long long)totalLength 17 | cacheLength:(long long)cacheLength 18 | vaildLength:(long long)vaildLength 19 | { 20 | if (self = [super init]) { 21 | self->_URL = [URL copy]; 22 | self->_zones = [zones copy]; 23 | self->_totalLength = totalLength; 24 | self->_cacheLength = cacheLength; 25 | self->_vaildLength = vaildLength; 26 | } 27 | return self; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataCacheItemZone.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KTVHCDataCacheItemZone : NSObject 12 | 13 | + (instancetype)new NS_UNAVAILABLE; 14 | - (instancetype)init NS_UNAVAILABLE; 15 | 16 | @property (nonatomic, readonly) long long offset; 17 | @property (nonatomic, readonly) long long length; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCacheItemZone.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataCacheItemZone.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataCacheItemZone.h" 10 | #import "KTVHCData+Internal.h" 11 | 12 | @implementation KTVHCDataCacheItemZone 13 | 14 | - (instancetype)initWithOffset:(long long)offset length:(long long)length 15 | { 16 | if (self = [super init]) { 17 | self->_offset = offset; 18 | self->_length = length; 19 | } 20 | return self; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCallback.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataCallback.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/12. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KTVHCDataCallback : NSObject 12 | 13 | + (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block; // Default is async. 14 | + (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block async:(BOOL)async; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataCallback.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataCallback.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/12. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataCallback.h" 10 | 11 | @implementation KTVHCDataCallback 12 | 13 | + (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block 14 | { 15 | [self callbackWithQueue:queue block:block async:YES]; 16 | } 17 | 18 | + (void)callbackWithQueue:(dispatch_queue_t)queue block:(void (^)(void))block async:(BOOL)async 19 | { 20 | if (!queue) { 21 | return; 22 | } 23 | if (!block) { 24 | return; 25 | } 26 | if (async) { 27 | dispatch_async(queue, ^{ 28 | if (block) { 29 | block(); 30 | } 31 | }); 32 | } else { 33 | dispatch_sync(queue, ^{ 34 | if (block) { 35 | block(); 36 | } 37 | }); 38 | } 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataFileSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataFileSource.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataSource.h" 11 | 12 | @class KTVHCDataFileSource; 13 | 14 | @protocol KTVHCDataFileSourceDelegate 15 | 16 | - (void)ktv_fileSourceDidPrepare:(KTVHCDataFileSource *)fileSource; 17 | - (void)ktv_fileSource:(KTVHCDataFileSource *)fileSource didFailWithError:(NSError *)error; 18 | 19 | @end 20 | 21 | @interface KTVHCDataFileSource : NSObject 22 | 23 | + (instancetype)new NS_UNAVAILABLE; 24 | - (instancetype)init NS_UNAVAILABLE; 25 | 26 | - (instancetype)initWithPath:(NSString *)path range:(KTVHCRange)range readRange:(KTVHCRange)readRange NS_DESIGNATED_INITIALIZER; 27 | 28 | @property (nonatomic, copy, readonly) NSString *path; 29 | @property (nonatomic, readonly) KTVHCRange readRange; 30 | 31 | @property (nonatomic, weak, readonly) id delegate; 32 | @property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue; 33 | 34 | - (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataFileSource.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataFileSource.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataFileSource.h" 10 | #import "KTVHCDataCallback.h" 11 | #import "KTVHCError.h" 12 | #import "KTVHCLog.h" 13 | 14 | @interface KTVHCDataFileSource () 15 | 16 | @property (nonatomic, strong) NSLock *coreLock; 17 | @property (nonatomic, strong) NSFileHandle *readingHandle; 18 | 19 | @end 20 | 21 | @implementation KTVHCDataFileSource 22 | 23 | @synthesize error = _error; 24 | @synthesize range = _range; 25 | @synthesize closed = _closed; 26 | @synthesize prepared = _prepared; 27 | @synthesize finished = _finished; 28 | @synthesize readedLength = _readedLength; 29 | 30 | - (instancetype)initWithPath:(NSString *)path range:(KTVHCRange)range readRange:(KTVHCRange)readRange 31 | { 32 | if (self = [super init]) 33 | { 34 | KTVHCLogAlloc(self); 35 | self->_path = path; 36 | self->_range = range; 37 | self->_readRange = readRange; 38 | self.coreLock = [[NSLock alloc] init]; 39 | KTVHCLogDataFileSource(@"%p, Create file source\npath : %@\nrange : %@\nreadRange : %@", self, path, KTVHCStringFromRange(range), KTVHCStringFromRange(readRange)); 40 | } 41 | return self; 42 | } 43 | 44 | - (void)dealloc 45 | { 46 | KTVHCLogDealloc(self); 47 | } 48 | 49 | - (void)prepare 50 | { 51 | [self lock]; 52 | if (self.isPrepared) { 53 | [self unlock]; 54 | return; 55 | } 56 | KTVHCLogDataFileSource(@"%p, Call prepare", self); 57 | self.readingHandle = [NSFileHandle fileHandleForReadingAtPath:self.path]; 58 | @try { 59 | [self.readingHandle seekToFileOffset:self.readRange.start]; 60 | self->_prepared = YES; 61 | if ([self.delegate respondsToSelector:@selector(ktv_fileSourceDidPrepare:)]) { 62 | KTVHCLogDataFileSource(@"%p, Callback for prepared - Begin", self); 63 | [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{ 64 | KTVHCLogDataFileSource(@"%p, Callback for prepared - End", self); 65 | [self.delegate ktv_fileSourceDidPrepare:self]; 66 | }]; 67 | } 68 | } @catch (NSException *exception) { 69 | KTVHCLogDataFileSource(@"%p, Seek file exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo); 70 | NSError *error = [KTVHCError errorForException:exception]; 71 | [self callbackForFailed:error]; 72 | } 73 | [self unlock]; 74 | } 75 | 76 | - (void)close 77 | { 78 | [self lock]; 79 | if (self.isClosed) { 80 | [self unlock]; 81 | return; 82 | } 83 | self->_closed = YES; 84 | KTVHCLogDataFileSource(@"%p, Call close", self); 85 | [self destoryReadingHandle]; 86 | [self unlock]; 87 | } 88 | 89 | - (NSData *)readDataOfLength:(NSUInteger)length 90 | { 91 | [self lock]; 92 | if (self.isClosed) { 93 | [self unlock]; 94 | return nil; 95 | } 96 | if (self.isFinished) { 97 | [self unlock]; 98 | return nil; 99 | } 100 | NSData *data = nil; 101 | @try { 102 | long long readLength = KTVHCRangeGetLength(self.readRange); 103 | length = (NSUInteger)MIN(readLength - self.readedLength, length); 104 | data = [self.readingHandle readDataOfLength:length]; 105 | self->_readedLength += data.length; 106 | if (data.length > 0) { 107 | KTVHCLogDataFileSource(@"%p, Read data : %lld, %lld, %lld", self, (long long)data.length, self.readedLength, readLength); 108 | } 109 | if (self.readedLength >= readLength) { 110 | KTVHCLogDataFileSource(@"%p, Read data did finished", self); 111 | [self destoryReadingHandle]; 112 | self->_finished = YES; 113 | } 114 | } @catch (NSException *exception) { 115 | KTVHCLogDataFileSource(@"%p, Read exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo); 116 | NSError *error = [KTVHCError errorForException:exception]; 117 | [self callbackForFailed:error]; 118 | } 119 | [self unlock]; 120 | return data; 121 | } 122 | 123 | - (void)destoryReadingHandle 124 | { 125 | if (self.readingHandle) { 126 | @try { 127 | [self.readingHandle closeFile]; 128 | } @catch (NSException *exception) { 129 | KTVHCLogDataFileSource(@"%p, Close exception\nname : %@\nreason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo); 130 | } 131 | self.readingHandle = nil; 132 | } 133 | } 134 | 135 | - (void)callbackForFailed:(NSError *)error 136 | { 137 | if (!error) { 138 | return; 139 | } 140 | if (self.error) { 141 | return; 142 | } 143 | self->_error = error; 144 | if ([self.delegate respondsToSelector:@selector(ktv_fileSource:didFailWithError:)]) { 145 | KTVHCLogDataFileSource(@"%p, Callback for prepared - Begin", self); 146 | [KTVHCDataCallback callbackWithQueue:self.delegateQueue block:^{ 147 | KTVHCLogDataFileSource(@"%p, Callback for prepared - End", self); 148 | [self.delegate ktv_fileSource:self didFailWithError:self.error]; 149 | }]; 150 | } 151 | } 152 | 153 | - (void)setDelegate:(id )delegate delegateQueue:(dispatch_queue_t)delegateQueue 154 | { 155 | self->_delegate = delegate; 156 | self->_delegateQueue = delegateQueue; 157 | } 158 | 159 | - (void)lock 160 | { 161 | [self.coreLock lock]; 162 | } 163 | 164 | - (void)unlock 165 | { 166 | [self.coreLock unlock]; 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataLoader.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2018/6/7. 6 | // Copyright © 2018 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class KTVHCDataLoader; 12 | @class KTVHCDataRequest; 13 | @class KTVHCDataResponse; 14 | 15 | @protocol KTVHCDataLoaderDelegate 16 | 17 | - (void)ktv_loaderDidFinish:(KTVHCDataLoader *)loader; 18 | - (void)ktv_loader:(KTVHCDataLoader *)loader didFailWithError:(NSError *)error; 19 | - (void)ktv_loader:(KTVHCDataLoader *)loader didChangeProgress:(double)progress; 20 | 21 | @end 22 | 23 | @interface KTVHCDataLoader : NSObject 24 | 25 | + (instancetype)new NS_UNAVAILABLE; 26 | - (instancetype)init NS_UNAVAILABLE; 27 | 28 | @property (nonatomic, weak) id delegate; 29 | @property (nonatomic, strong) id object; 30 | 31 | @property (nonatomic, strong, readonly) KTVHCDataRequest *request; 32 | @property (nonatomic, strong, readonly) KTVHCDataResponse *response; 33 | 34 | @property (nonatomic, copy, readonly) NSError *error; 35 | 36 | @property (nonatomic, readonly, getter=isFinished) BOOL finished; 37 | @property (nonatomic, readonly, getter=isClosed) BOOL closed; 38 | 39 | @property (nonatomic, readonly) long long loadedLength; 40 | @property (nonatomic, readonly) double progress; 41 | 42 | - (void)prepare; 43 | - (void)close; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataLoader.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataLoader.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2018/6/7. 6 | // Copyright © 2018 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataLoader.h" 10 | #import "KTVHCData+Internal.h" 11 | #import "KTVHCLog.h" 12 | 13 | @interface KTVHCDataLoader () 14 | 15 | @property (nonatomic, strong) KTVHCDataReader *reader; 16 | 17 | @end 18 | 19 | @implementation KTVHCDataLoader 20 | 21 | - (instancetype)initWithRequest:(KTVHCDataRequest *)request 22 | { 23 | if (self = [super init]) { 24 | KTVHCLogAlloc(self); 25 | self.reader = [[KTVHCDataReader alloc] initWithRequest:request]; 26 | self.reader.delegate = self; 27 | KTVHCLogDataLoader(@"%p, Create loader\norignalRequest : %@\nreader : %@", self, request, self.reader); 28 | } 29 | return self; 30 | } 31 | 32 | - (void)dealloc 33 | { 34 | KTVHCLogDealloc(self); 35 | [self close]; 36 | KTVHCLogDataLoader(@"%p, Destory reader\nError : %@\nprogress : %f", self, self.error, self.progress); 37 | } 38 | 39 | - (void)prepare 40 | { 41 | KTVHCLogDataLoader(@"%p, Call prepare", self); 42 | [self.reader prepare]; 43 | } 44 | 45 | - (void)close 46 | { 47 | KTVHCLogDataLoader(@"%p, Call close", self); 48 | [self.reader close]; 49 | } 50 | 51 | - (KTVHCDataRequest *)request 52 | { 53 | return self.reader.request; 54 | } 55 | 56 | - (KTVHCDataResponse *)response 57 | { 58 | return self.reader.response; 59 | } 60 | 61 | - (NSError *)error 62 | { 63 | return self.reader.error; 64 | } 65 | 66 | - (BOOL)isFinished 67 | { 68 | return self.reader.isFinished; 69 | } 70 | 71 | - (BOOL)isClosed 72 | { 73 | return self.reader.isClosed; 74 | } 75 | 76 | #pragma mark - KTVHCDataReaderDelegate 77 | 78 | - (void)ktv_readerDidPrepare:(KTVHCDataReader *)reader 79 | { 80 | [self readData]; 81 | } 82 | 83 | - (void)ktv_readerHasAvailableData:(KTVHCDataReader *)reader 84 | { 85 | [self readData]; 86 | } 87 | 88 | - (void)ktv_reader:(KTVHCDataReader *)reader didFailWithError:(NSError *)error 89 | { 90 | KTVHCLogDataLoader(@"%p, Callback for failed", self); 91 | if ([self.delegate respondsToSelector:@selector(ktv_loader:didFailWithError:)]) { 92 | [self.delegate ktv_loader:self didFailWithError:error]; 93 | } 94 | } 95 | 96 | - (void)readData 97 | { 98 | while (YES) { 99 | @autoreleasepool { 100 | NSData *data = [self.reader readDataOfLength:1024 * 1024 * 1]; 101 | if (self.reader.isFinished) { 102 | self->_loadedLength = self.reader.readedLength; 103 | self->_progress = 1.0f; 104 | if ([self.delegate respondsToSelector:@selector(ktv_loader:didChangeProgress:)]) { 105 | [self.delegate ktv_loader:self didChangeProgress:self.progress]; 106 | } 107 | KTVHCLogDataLoader(@"%p, Callback finished", self); 108 | if ([self.delegate respondsToSelector:@selector(ktv_loaderDidFinish:)]) { 109 | [self.delegate ktv_loaderDidFinish:self]; 110 | } 111 | } else if (data) { 112 | self->_loadedLength = self.reader.readedLength; 113 | if (self.response.contentLength > 0) { 114 | self->_progress = (double)self.reader.readedLength / (double)self.response.contentLength; 115 | } 116 | if ([self.delegate respondsToSelector:@selector(ktv_loader:didChangeProgress:)]) { 117 | [self.delegate ktv_loader:self didChangeProgress:self.progress]; 118 | } 119 | KTVHCLogDataLoader(@"%p, read data progress %f", self, self.progress); 120 | continue; 121 | } 122 | KTVHCLogDataLoader(@"%p, read data break", self); 123 | break; 124 | } 125 | } 126 | } 127 | 128 | @end 129 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataNetworkSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataNetworkSource.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataSource.h" 11 | #import "KTVHCDataRequest.h" 12 | #import "KTVHCDataResponse.h" 13 | 14 | @class KTVHCDataNetworkSource; 15 | 16 | @protocol KTVHCDataNetworkSourceDelegate 17 | 18 | - (void)ktv_networkSourceDidPrepare:(KTVHCDataNetworkSource *)networkSource; 19 | - (void)ktv_networkSourceHasAvailableData:(KTVHCDataNetworkSource *)networkSource; 20 | - (void)ktv_networkSourceDidFinisheDownload:(KTVHCDataNetworkSource *)networkSource; 21 | - (void)ktv_networkSource:(KTVHCDataNetworkSource *)networkSource didFailWithError:(NSError *)error; 22 | 23 | @end 24 | 25 | @interface KTVHCDataNetworkSource : NSObject 26 | 27 | + (instancetype)new NS_UNAVAILABLE; 28 | - (instancetype)init NS_UNAVAILABLE; 29 | 30 | - (instancetype)initWithRequest:(KTVHCDataRequest *)reqeust NS_DESIGNATED_INITIALIZER; 31 | 32 | @property (nonatomic, strong, readonly) KTVHCDataRequest *request; 33 | @property (nonatomic, strong, readonly) KTVHCDataResponse *response; 34 | 35 | @property (nonatomic, weak, readonly) id delegate; 36 | @property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue; 37 | 38 | - (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataReader.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataReader.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class KTVHCDataReader; 12 | @class KTVHCDataRequest; 13 | @class KTVHCDataResponse; 14 | 15 | @protocol KTVHCDataReaderDelegate 16 | 17 | - (void)ktv_readerDidPrepare:(KTVHCDataReader *)reader; 18 | - (void)ktv_readerHasAvailableData:(KTVHCDataReader *)reader; 19 | - (void)ktv_reader:(KTVHCDataReader *)reader didFailWithError:(NSError *)error; 20 | 21 | @end 22 | 23 | @interface KTVHCDataReader : NSObject 24 | 25 | + (instancetype)new NS_UNAVAILABLE; 26 | - (instancetype)init NS_UNAVAILABLE; 27 | 28 | @property (nonatomic, weak) id delegate; 29 | @property (nonatomic, strong) id object; 30 | 31 | @property (nonatomic, strong, readonly) KTVHCDataRequest *request; 32 | @property (nonatomic, strong, readonly) KTVHCDataResponse *response; 33 | 34 | @property (nonatomic, copy, readonly) NSError *error; 35 | 36 | @property (nonatomic, readonly, getter=isPrepared) BOOL prepared; 37 | @property (nonatomic, readonly, getter=isFinished) BOOL finished; 38 | @property (nonatomic, readonly, getter=isClosed) BOOL closed; 39 | 40 | @property (nonatomic, readonly) long long readedLength; 41 | @property (nonatomic, readonly) double progress; 42 | 43 | - (void)prepare; 44 | - (void)close; 45 | 46 | - (NSData *)readDataOfLength:(NSUInteger)length; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataRequest.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface KTVHCDataRequest : NSObject 13 | 14 | + (instancetype)new NS_UNAVAILABLE; 15 | - (instancetype)init NS_UNAVAILABLE; 16 | 17 | - (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers NS_DESIGNATED_INITIALIZER; 18 | 19 | @property (nonatomic, copy, readonly) NSURL *URL; 20 | @property (nonatomic, copy, readonly) NSDictionary *headers; 21 | @property (nonatomic, readonly) KTVHCRange range; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataRequest.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataRequest.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataRequest.h" 10 | #import "KTVHCData+Internal.h" 11 | #import "KTVHCLog.h" 12 | 13 | @implementation KTVHCDataRequest 14 | 15 | - (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers 16 | { 17 | if (self = [super init]) { 18 | KTVHCLogAlloc(self); 19 | self->_URL = URL; 20 | self->_headers = KTVHCRangeFillToRequestHeadersIfNeeded(KTVHCRangeFull(), headers); 21 | self->_range = KTVHCRangeWithRequestHeaderValue([self.headers objectForKey:@"Range"]); 22 | KTVHCLogDataRequest(@"%p Create data request\nURL : %@\nHeaders : %@\nRange : %@", self, self.URL, self.headers, KTVHCStringFromRange(self.range)); 23 | } 24 | return self; 25 | } 26 | 27 | - (void)dealloc 28 | { 29 | KTVHCLogDealloc(self); 30 | } 31 | 32 | - (KTVHCDataRequest *)newRequestWithRange:(KTVHCRange)range 33 | { 34 | NSDictionary *headers = KTVHCRangeFillToRequestHeaders(range, self.headers); 35 | KTVHCDataRequest *obj = [[KTVHCDataRequest alloc] initWithURL:self.URL headers:headers]; 36 | return obj; 37 | } 38 | 39 | - (KTVHCDataRequest *)newRequestWithTotalLength:(long long)totalLength 40 | { 41 | KTVHCRange range = KTVHCRangeWithEnsureLength(self.range, totalLength); 42 | return [self newRequestWithRange:range]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataResponse.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/24. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface KTVHCDataResponse : NSObject 13 | 14 | + (instancetype)new NS_UNAVAILABLE; 15 | - (instancetype)init NS_UNAVAILABLE; 16 | 17 | @property (nonatomic, copy, readonly) NSURL *URL; 18 | @property (nonatomic, copy, readonly) NSDictionary *headers; 19 | @property (nonatomic, copy, readonly) NSString *contentType; 20 | @property (nonatomic, copy, readonly) NSString *contentRangeString; 21 | @property (nonatomic, readonly) KTVHCRange contentRange; 22 | @property (nonatomic, readonly) long long contentLength; 23 | @property (nonatomic, readonly) long long totalLength; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataResponse.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/24. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataResponse.h" 10 | #import "KTVHCData+Internal.h" 11 | #import "KTVHCLog.h" 12 | 13 | @implementation KTVHCDataResponse 14 | 15 | - (instancetype)initWithURL:(NSURL *)URL headers:(NSDictionary *)headers 16 | { 17 | if (self = [super init]) { 18 | KTVHCLogAlloc(self); 19 | self->_URL = URL; 20 | self->_headers = headers; 21 | self->_contentType = [self headerValueWithKey:@"Content-Type"]; 22 | self->_contentRangeString = [self headerValueWithKey:@"Content-Range"]; 23 | self->_contentLength = [self headerValueWithKey:@"Content-Length"].longLongValue; 24 | self->_contentRange = KTVHCRangeWithResponseHeaderValue(self.contentRangeString, &self->_totalLength); 25 | KTVHCLogDataResponse(@"%p Create data response\nURL : %@\nHeaders : %@\ncontentType : %@\ntotalLength : %lld\ncurrentLength : %lld", self, self.URL, self.headers, self.contentType, self.totalLength, self.contentLength); 26 | } 27 | return self; 28 | } 29 | 30 | - (void)dealloc 31 | { 32 | KTVHCLogDealloc(self); 33 | } 34 | 35 | - (NSString *)headerValueWithKey:(NSString *)key 36 | { 37 | NSString *value = [self.headers objectForKey:key]; 38 | if (!value) { 39 | value = [self.headers objectForKey:[key lowercaseString]]; 40 | } 41 | return value; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSource.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataSource.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCRange.h" 11 | 12 | @protocol KTVHCDataSource 13 | 14 | @property (nonatomic, copy, readonly) NSError *error; 15 | 16 | @property (nonatomic, readonly, getter=isPrepared) BOOL prepared; 17 | @property (nonatomic, readonly, getter=isFinished) BOOL finished; 18 | @property (nonatomic, readonly, getter=isClosed) BOOL closed; 19 | 20 | @property (nonatomic, readonly) KTVHCRange range; 21 | @property (nonatomic, readonly) long long readedLength; 22 | 23 | - (void)prepare; 24 | - (void)close; 25 | 26 | - (NSData *)readDataOfLength:(NSUInteger)length; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataSourceManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataSourceManager.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataNetworkSource.h" 11 | #import "KTVHCDataFileSource.h" 12 | 13 | @class KTVHCDataSourceManager; 14 | 15 | @protocol KTVHCDataSourceManagerDelegate 16 | 17 | - (void)ktv_sourceManagerDidPrepare:(KTVHCDataSourceManager *)sourceManager; 18 | - (void)ktv_sourceManagerHasAvailableData:(KTVHCDataSourceManager *)sourceManager; 19 | - (void)ktv_sourceManager:(KTVHCDataSourceManager *)sourceManager didFailWithError:(NSError *)error; 20 | - (void)ktv_sourceManager:(KTVHCDataSourceManager *)sourceManager didReceiveResponse:(KTVHCDataResponse *)response; 21 | 22 | @end 23 | 24 | @interface KTVHCDataSourceManager : NSObject 25 | 26 | + (instancetype)new NS_UNAVAILABLE; 27 | - (instancetype)init NS_UNAVAILABLE; 28 | 29 | - (instancetype)initWithSources:(NSArray> *)sources 30 | delegate:(id )delegate 31 | delegateQueue:(dispatch_queue_t)delegateQueue NS_DESIGNATED_INITIALIZER; 32 | 33 | @property (nonatomic, weak, readonly) id delegate; 34 | @property (nonatomic, strong, readonly) dispatch_queue_t delegateQueue; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataStorage.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataManager.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataReader.h" 11 | #import "KTVHCDataLoader.h" 12 | #import "KTVHCDataRequest.h" 13 | #import "KTVHCDataResponse.h" 14 | #import "KTVHCDataCacheItem.h" 15 | 16 | @interface KTVHCDataStorage : NSObject 17 | 18 | + (instancetype)new NS_UNAVAILABLE; 19 | - (instancetype)init NS_UNAVAILABLE; 20 | 21 | + (instancetype)storage; 22 | 23 | /** 24 | * Return file path if the content did finished cache. 25 | */ 26 | - (NSURL *)completeFileURLWithURL:(NSURL *)URL; 27 | 28 | /** 29 | * Reader for certain request. 30 | */ 31 | - (KTVHCDataReader *)readerWithRequest:(KTVHCDataRequest *)request; 32 | 33 | /** 34 | * Loader for certain request. 35 | */ 36 | - (KTVHCDataLoader *)loaderWithRequest:(KTVHCDataRequest *)request; 37 | 38 | /** 39 | * Get cache item. 40 | */ 41 | - (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL; 42 | - (NSArray *)allCacheItems; 43 | 44 | /** 45 | * Get cache length, default is 500m.. 46 | */ 47 | @property (nonatomic) long long maxCacheLength; 48 | - (long long)totalCacheLength; 49 | 50 | /** 51 | * Delete cache. 52 | */ 53 | - (void)deleteCacheWithURL:(NSURL *)URL; 54 | - (void)deleteAllCaches; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataStorage.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataManager.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataStorage.h" 10 | #import "KTVHCData+Internal.h" 11 | #import "KTVHCDataUnitPool.h" 12 | #import "KTVHCLog.h" 13 | 14 | @implementation KTVHCDataStorage 15 | 16 | + (instancetype)storage 17 | { 18 | static KTVHCDataStorage *obj = nil; 19 | static dispatch_once_t onceToken; 20 | dispatch_once(&onceToken, ^{ 21 | obj = [[self alloc] init]; 22 | }); 23 | return obj; 24 | } 25 | 26 | - (instancetype)init 27 | { 28 | if (self = [super init]) { 29 | self.maxCacheLength = 500 * 1024 * 1024; 30 | } 31 | return self; 32 | } 33 | 34 | - (NSURL *)completeFileURLWithURL:(NSURL *)URL 35 | { 36 | KTVHCDataUnit *unit = [[KTVHCDataUnitPool pool] unitWithURL:URL]; 37 | NSURL *completeURL = unit.completeURL; 38 | [unit workingRelease]; 39 | return completeURL; 40 | } 41 | 42 | - (KTVHCDataReader *)readerWithRequest:(KTVHCDataRequest *)request 43 | { 44 | if (!request || request.URL.absoluteString.length <= 0) { 45 | KTVHCLogDataStorage(@"Invaild reader request, %@", request.URL); 46 | return nil; 47 | } 48 | KTVHCDataReader *reader = [[KTVHCDataReader alloc] initWithRequest:request]; 49 | return reader; 50 | } 51 | 52 | - (KTVHCDataLoader *)loaderWithRequest:(KTVHCDataRequest *)request 53 | { 54 | if (!request || request.URL.absoluteString.length <= 0) { 55 | KTVHCLogDataStorage(@"Invaild loader request, %@", request.URL); 56 | return nil; 57 | } 58 | KTVHCDataLoader *loader = [[KTVHCDataLoader alloc] initWithRequest:request]; 59 | return loader; 60 | } 61 | 62 | - (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL 63 | { 64 | return [[KTVHCDataUnitPool pool] cacheItemWithURL:URL]; 65 | } 66 | 67 | - (NSArray *)allCacheItems 68 | { 69 | return [[KTVHCDataUnitPool pool] allCacheItem]; 70 | } 71 | 72 | - (long long)totalCacheLength 73 | { 74 | return [[KTVHCDataUnitPool pool] totalCacheLength]; 75 | } 76 | 77 | - (void)deleteCacheWithURL:(NSURL *)URL 78 | { 79 | [[KTVHCDataUnitPool pool] deleteUnitWithURL:URL]; 80 | } 81 | 82 | - (void)deleteAllCaches 83 | { 84 | [[KTVHCDataUnitPool pool] deleteAllUnits]; 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnit.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataUnit.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataUnitItem.h" 11 | 12 | @class KTVHCDataUnit; 13 | 14 | @protocol KTVHCDataUnitDelegate 15 | 16 | - (void)ktv_unitDidChangeMetadata:(KTVHCDataUnit *)unit; 17 | 18 | @end 19 | 20 | @interface KTVHCDataUnit : NSObject 21 | 22 | + (instancetype)new NS_UNAVAILABLE; 23 | - (instancetype)init NS_UNAVAILABLE; 24 | 25 | - (instancetype)initWithURL:(NSURL *)URL; 26 | 27 | @property (nonatomic, copy, readonly) NSError *error; 28 | 29 | @property (nonatomic, copy, readonly) NSURL *URL; 30 | @property (nonatomic, copy, readonly) NSURL *completeURL; 31 | @property (nonatomic, copy, readonly) NSString *key; // Unique Identifier. 32 | @property (nonatomic, copy, readonly) NSDictionary *responseHeaders; 33 | @property (nonatomic, readonly) NSTimeInterval createTimeInterval; 34 | @property (nonatomic, readonly) NSTimeInterval lastItemCreateInterval; 35 | @property (nonatomic, readonly) long long totalLength; 36 | @property (nonatomic, readonly) long long cacheLength; 37 | @property (nonatomic, readonly) long long validLength; 38 | 39 | /** 40 | * Unit Item 41 | */ 42 | - (NSArray *)unitItems; 43 | - (void)insertUnitItem:(KTVHCDataUnitItem *)unitItem; 44 | 45 | /** 46 | * Info Sync 47 | */ 48 | - (void)updateResponseHeaders:(NSDictionary *)responseHeaders totalLength:(long long)totalLength; 49 | 50 | /** 51 | * Working 52 | */ 53 | @property (nonatomic, readonly) NSInteger workingCount; 54 | 55 | - (void)workingRetain; 56 | - (void)workingRelease; 57 | 58 | /** 59 | * File Control 60 | */ 61 | @property (nonatomic, weak) id delegate; 62 | 63 | - (void)deleteFiles; 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataUnitItem.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KTVHCDataUnitItem : NSObject 12 | 13 | + (instancetype)new NS_UNAVAILABLE; 14 | 15 | - (instancetype)initWithPath:(NSString *)path; 16 | - (instancetype)initWithPath:(NSString *)path offset:(uint64_t)offset; 17 | 18 | @property (nonatomic, copy, readonly) NSString *relativePath; 19 | @property (nonatomic, copy, readonly) NSString *absolutePath; 20 | @property (nonatomic, readonly) NSTimeInterval createTimeInterval; 21 | @property (nonatomic, readonly) long long offset; 22 | @property (nonatomic, readonly) long long length; 23 | 24 | - (void)updateLength:(long long)length; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataUnitItem.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataUnitItem.h" 10 | #import "KTVHCPathTool.h" 11 | #import "KTVHCLog.h" 12 | 13 | @interface KTVHCDataUnitItem () 14 | 15 | @property (nonatomic, strong) NSRecursiveLock *coreLock; 16 | 17 | @end 18 | 19 | @implementation KTVHCDataUnitItem 20 | 21 | - (id)copyWithZone:(NSZone *)zone 22 | { 23 | [self lock]; 24 | KTVHCDataUnitItem *obj = [[KTVHCDataUnitItem alloc] init]; 25 | obj->_relativePath = self.relativePath; 26 | obj->_absolutePath = self.absolutePath; 27 | obj->_createTimeInterval = self.createTimeInterval; 28 | obj->_offset = self.offset; 29 | obj->_length = self.length; 30 | [self unlock]; 31 | return obj; 32 | } 33 | 34 | - (instancetype)initWithPath:(NSString *)path 35 | { 36 | return [self initWithPath:path offset:0]; 37 | } 38 | 39 | - (instancetype)initWithPath:(NSString *)path offset:(uint64_t)offset 40 | { 41 | if (self = [super init]) { 42 | self->_createTimeInterval = [NSDate date].timeIntervalSince1970; 43 | self->_relativePath = [KTVHCPathTool converToRelativePath:path]; 44 | self->_absolutePath = [KTVHCPathTool converToAbsoultePath:path]; 45 | self->_offset = offset; 46 | self->_length = [KTVHCPathTool sizeAtPath:self.absolutePath]; 47 | [self commonInit]; 48 | } 49 | return self; 50 | } 51 | 52 | - (instancetype)initWithCoder:(NSCoder *)aDecoder 53 | { 54 | if (self = [super init]) { 55 | self->_createTimeInterval = [[aDecoder decodeObjectForKey:@"createTimeInterval"] doubleValue]; 56 | self->_relativePath = [aDecoder decodeObjectForKey:@"relativePath"]; 57 | self->_absolutePath = [KTVHCPathTool converToAbsoultePath:self.relativePath]; 58 | self->_offset = [[aDecoder decodeObjectForKey:@"offset"] longLongValue]; 59 | self->_length = [KTVHCPathTool sizeAtPath:self.absolutePath]; 60 | [self commonInit]; 61 | } 62 | return self; 63 | } 64 | 65 | - (void)encodeWithCoder:(NSCoder *)aCoder 66 | { 67 | [aCoder encodeObject:@(self.createTimeInterval) forKey:@"createTimeInterval"]; 68 | [aCoder encodeObject:self.relativePath forKey:@"relativePath"]; 69 | [aCoder encodeObject:@(self.offset) forKey:@"offset"]; 70 | } 71 | 72 | - (void)dealloc 73 | { 74 | KTVHCLogDealloc(self); 75 | } 76 | 77 | - (void)commonInit 78 | { 79 | KTVHCLogAlloc(self); 80 | self.coreLock = [[NSRecursiveLock alloc] init]; 81 | KTVHCLogDataUnitItem(@"%p, Create Unit Item\nabsolutePath : %@\nrelativePath : %@\nOffset : %lld\nLength : %lld", self, self.absolutePath, self.relativePath, self.offset, self.length); 82 | } 83 | 84 | - (void)updateLength:(long long)length 85 | { 86 | [self lock]; 87 | self->_length = length; 88 | KTVHCLogDataUnitItem(@"%p, Set length : %lld", self, length); 89 | [self unlock]; 90 | } 91 | 92 | - (void)lock 93 | { 94 | [self.coreLock lock]; 95 | } 96 | 97 | - (void)unlock 98 | { 99 | [self.coreLock unlock]; 100 | } 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitPool.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataUnitPool.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataUnit.h" 11 | #import "KTVHCDataCacheItem.h" 12 | 13 | @interface KTVHCDataUnitPool : NSObject 14 | 15 | + (instancetype)new NS_UNAVAILABLE; 16 | - (instancetype)init NS_UNAVAILABLE; 17 | 18 | + (instancetype)pool; 19 | 20 | - (KTVHCDataUnit *)unitWithURL:(NSURL *)URL; 21 | 22 | - (long long)totalCacheLength; 23 | 24 | - (NSArray *)allCacheItem; 25 | - (KTVHCDataCacheItem *)cacheItemWithURL:(NSURL *)URL; 26 | 27 | - (void)deleteUnitWithURL:(NSURL *)URL; 28 | - (void)deleteUnitsWithLength:(long long)length; 29 | - (void)deleteAllUnits; 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataUnitQueue.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataUnit.h" 11 | 12 | @interface KTVHCDataUnitQueue : NSObject 13 | 14 | + (instancetype)new NS_UNAVAILABLE; 15 | - (instancetype)init NS_UNAVAILABLE; 16 | 17 | - (instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER; 18 | 19 | - (NSArray *)allUnits; 20 | - (KTVHCDataUnit *)unitWithKey:(NSString *)key; 21 | 22 | - (void)putUnit:(KTVHCDataUnit *)unit; 23 | - (void)popUnit:(KTVHCDataUnit *)unit; 24 | 25 | - (void)archive; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDataStorage/KTVHCDataUnitQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataUnitQueue.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/11. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCDataUnitQueue.h" 10 | #import "KTVHCLog.h" 11 | 12 | @interface KTVHCDataUnitQueue () 13 | 14 | @property (nonatomic, copy) NSString *path; 15 | @property (nonatomic, strong) NSMutableArray *unitArray; 16 | 17 | @end 18 | 19 | @implementation KTVHCDataUnitQueue 20 | 21 | - (instancetype)initWithPath:(NSString *)path 22 | { 23 | if (self = [super init]) { 24 | self.path = path; 25 | NSMutableArray *unitArray = nil; 26 | @try { 27 | unitArray = [NSKeyedUnarchiver unarchiveObjectWithFile:self.path]; 28 | } @catch (NSException *exception) { 29 | KTVHCLogDataUnitQueue(@"%p, Init exception\nname : %@\breason : %@\nuserInfo : %@", self, exception.name, exception.reason, exception.userInfo); 30 | } 31 | self.unitArray = [NSMutableArray array]; 32 | for (KTVHCDataUnit *obj in unitArray) { 33 | if (obj.error) { 34 | [obj deleteFiles]; 35 | } else { 36 | [self.unitArray addObject:obj]; 37 | } 38 | } 39 | } 40 | return self; 41 | } 42 | 43 | - (NSArray *)allUnits 44 | { 45 | if (self.unitArray.count <= 0) { 46 | return nil; 47 | } 48 | return [self.unitArray copy]; 49 | } 50 | 51 | - (KTVHCDataUnit *)unitWithKey:(NSString *)key 52 | { 53 | if (key.length <= 0) { 54 | return nil; 55 | } 56 | KTVHCDataUnit *unit = nil; 57 | for (KTVHCDataUnit *obj in self.unitArray) { 58 | if ([obj.key isEqualToString:key]) { 59 | unit = obj; 60 | break; 61 | } 62 | } 63 | return unit; 64 | } 65 | 66 | - (void)putUnit:(KTVHCDataUnit *)unit 67 | { 68 | if (!unit) { 69 | return; 70 | } 71 | if (![self.unitArray containsObject:unit]) { 72 | [self.unitArray addObject:unit]; 73 | } 74 | } 75 | 76 | - (void)popUnit:(KTVHCDataUnit *)unit 77 | { 78 | if (!unit) { 79 | return; 80 | } 81 | if ([self.unitArray containsObject:unit]) { 82 | [self.unitArray removeObject:unit]; 83 | } 84 | } 85 | 86 | - (void)archive 87 | { 88 | KTVHCLogDataUnitQueue(@"%p, Archive - Begin, %ld", self, (long)self.unitArray.count); 89 | [NSKeyedArchiver archiveRootObject:self.unitArray toFile:self.path]; 90 | KTVHCLogDataUnitQueue(@"%p, Archive - End , %ld", self, (long)self.unitArray.count); 91 | } 92 | 93 | @end 94 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCDownload/KTVHCDownload.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCDataDownload.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/12. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCDataResponse.h" 11 | #import "KTVHCDataRequest.h" 12 | 13 | #if defined(__cplusplus) 14 | #define KTVHTTPCACHE_EXTERN extern "C" 15 | #else 16 | #define KTVHTTPCACHE_EXTERN extern 17 | #endif 18 | 19 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeText; 20 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeVideo; 21 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeAudio; 22 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeAppleHLS1; 23 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeAppleHLS2; 24 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeApplicationMPEG4; 25 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeApplicationOctetStream; 26 | KTVHTTPCACHE_EXTERN NSString * const KTVHCContentTypeBinaryOctetStream; 27 | 28 | @class KTVHCDownload; 29 | 30 | @protocol KTVHCDownloadDelegate 31 | 32 | - (void)ktv_download:(KTVHCDownload *)download didCompleteWithError:(NSError *)error; 33 | - (void)ktv_download:(KTVHCDownload *)download didReceiveResponse:(KTVHCDataResponse *)response; 34 | - (void)ktv_download:(KTVHCDownload *)download didReceiveData:(NSData *)data; 35 | 36 | @end 37 | 38 | @interface KTVHCDownload : NSObject 39 | 40 | + (instancetype)new NS_UNAVAILABLE; 41 | - (instancetype)init NS_UNAVAILABLE; 42 | 43 | + (instancetype)download; 44 | 45 | @property (nonatomic) NSTimeInterval timeoutInterval; 46 | 47 | /** 48 | * Header Fields 49 | */ 50 | @property (nonatomic, copy) NSArray *whitelistHeaderKeys; 51 | @property (nonatomic, copy) NSDictionary *additionalHeaders; 52 | 53 | /** 54 | * Content-Type 55 | */ 56 | @property (nonatomic, copy) NSArray *acceptableContentTypes; 57 | @property (nonatomic, copy) BOOL (^unacceptableContentTypeDisposer)(NSURL *URL, NSString *contentType); 58 | 59 | - (NSURLRequest *)requestWithDataRequest:(KTVHCDataRequest *)request; 60 | - (NSURLSessionTask *)downloadWithRequest:(KTVHCDataRequest *)request delegate:(id)delegate; 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPConnection.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCHTTPHeader.h" 11 | 12 | @interface KTVHCHTTPConnection : HTTPConnection 13 | 14 | + (NSString *)URITokenPing; 15 | + (NSString *)URITokenPlaceHolder; 16 | + (NSString *)URITokenLastPathComponent; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPConnection.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPConnection.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCHTTPConnection.h" 10 | #import "KTVHCHTTPHLSResponse.h" 11 | #import "KTVHCHTTPResponse.h" 12 | #import "KTVHCDataStorage.h" 13 | #import "KTVHCHTTPHeader.h" 14 | #import "KTVHCURLTool.h" 15 | #import "KTVHCLog.h" 16 | 17 | @implementation KTVHCHTTPConnection 18 | 19 | + (NSString *)URITokenPing 20 | { 21 | return @"KTVHTTPCachePing"; 22 | } 23 | 24 | + (NSString *)URITokenPlaceHolder 25 | { 26 | return @"KTVHTTPCachePlaceHolder"; 27 | } 28 | 29 | + (NSString *)URITokenLastPathComponent 30 | { 31 | return @"KTVHTTPCacheLastPathComponent"; 32 | } 33 | 34 | - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig 35 | { 36 | if (self = [super initWithAsyncSocket:newSocket configuration:aConfig]) { 37 | KTVHCLogAlloc(self); 38 | } 39 | return self; 40 | } 41 | 42 | - (void)dealloc 43 | { 44 | KTVHCLogDealloc(self); 45 | } 46 | 47 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path 48 | { 49 | KTVHCLogHTTPConnection(@"%p, Receive request\nmethod : %@\npath : %@\nURL : %@", self, method, path, request.url); 50 | if ([path containsString:[self.class URITokenPing]]) { 51 | return [[HTTPDataResponse alloc] initWithData:[@"ping" dataUsingEncoding:NSUTF8StringEncoding]]; 52 | } 53 | NSMutableArray *components = [path componentsSeparatedByString:@"/"].mutableCopy; 54 | if (components.count < 3) { 55 | return [[HTTPErrorResponse alloc] initWithErrorCode:404]; 56 | } 57 | NSString *URLString = [[KTVHCURLTool tool] URLDecode:components[1]]; 58 | if (![URLString hasPrefix:@"http"]) { 59 | return [[HTTPErrorResponse alloc] initWithErrorCode:404]; 60 | } 61 | NSURL *URL = nil; 62 | if ([path containsString:[self.class URITokenLastPathComponent]]) { 63 | URL = [NSURL URLWithString:URLString]; 64 | } else { 65 | [components removeObjectAtIndex:0]; 66 | [components removeObjectAtIndex:0]; 67 | URLString = URLString.stringByDeletingLastPathComponent; 68 | if ([path containsString:[self.class URITokenPlaceHolder]]) { 69 | [components removeObjectAtIndex:0]; 70 | } else { 71 | URLString = URLString.stringByDeletingLastPathComponent; 72 | } 73 | NSString *lastPathComponent = [components componentsJoinedByString:@"/"]; 74 | if ([lastPathComponent hasPrefix:@"http"]) { 75 | URLString = lastPathComponent; 76 | } else { 77 | URLString = [URLString stringByAppendingPathComponent:lastPathComponent]; 78 | } 79 | URL = [NSURL URLWithString:URLString]; 80 | KTVHCLogHTTPConnection(@"%p, Receive redirect request\nURL : %@", self, URLString); 81 | } 82 | KTVHCLogHTTPConnection(@"%p, Accept request\nURL : %@", self, URL); 83 | KTVHCDataRequest *dataRequest = [[KTVHCDataRequest alloc] initWithURL:URL headers:request.allHeaderFields]; 84 | if ([URLString containsString:@".m3u"]) { 85 | return [[KTVHCHTTPHLSResponse alloc] initWithConnection:self dataRequest:dataRequest]; 86 | } 87 | return [[KTVHCHTTPResponse alloc] initWithConnection:self dataRequest:dataRequest]; 88 | } 89 | 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPHLSResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPHLSResponse.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Gary Zhao on 2024/1/7. 6 | // Copyright © 2024 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCHTTPHeader.h" 11 | 12 | @class KTVHCHTTPConnection; 13 | @class KTVHCDataRequest; 14 | 15 | @interface KTVHCHTTPHLSResponse : NSObject 16 | 17 | + (instancetype)new NS_UNAVAILABLE; 18 | - (instancetype)init NS_UNAVAILABLE; 19 | 20 | - (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection dataRequest:(KTVHCDataRequest *)dataRequest NS_DESIGNATED_INITIALIZER; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPHLSResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPHLSResponse.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Gary Zhao on 2024/1/7. 6 | // Copyright © 2024 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCHTTPHLSResponse.h" 10 | #import "KTVHCHTTPConnection.h" 11 | #import "KTVHCDataUnitPool.h" 12 | #import "KTVHCDataStorage.h" 13 | #import "KTVHCDownload.h" 14 | #import "KTVHCPathTool.h" 15 | #import "KTVHCLog.h" 16 | 17 | 18 | @interface KTVHCHTTPHLSResponse () 19 | 20 | @property (nonatomic, weak) KTVHCHTTPConnection *connection; 21 | 22 | @property (nonatomic) UInt64 readedLength; 23 | @property (nonatomic, strong) NSData *data; 24 | @property (nonatomic, strong) KTVHCDataUnit *unit; 25 | @property (nonatomic, strong) NSURLSessionDataTask *task; 26 | 27 | @end 28 | 29 | @implementation KTVHCHTTPHLSResponse 30 | 31 | - (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection dataRequest:(KTVHCDataRequest *)dataRequest 32 | { 33 | if (self = [super init]) { 34 | KTVHCLogAlloc(self); 35 | KTVHCLogHTTPHLSResponse(@"%p, Create response\nrequest : %@", self, dataRequest); 36 | self.connection = connection; 37 | self.unit = [[KTVHCDataUnitPool pool] unitWithURL:dataRequest.URL]; 38 | if (self.unit.totalLength == 0) { 39 | static NSURLSession *session = nil; 40 | static dispatch_once_t onceToken; 41 | dispatch_once(&onceToken, ^{ 42 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 43 | configuration.timeoutIntervalForRequest = 3; 44 | session = [NSURLSession sessionWithConfiguration:configuration]; 45 | }); 46 | __weak typeof(self) weakSelf = self; 47 | NSURLRequest *request = [[KTVHCDownload download] requestWithDataRequest:dataRequest]; 48 | self.task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 49 | __strong typeof(weakSelf) strongSelf = weakSelf; 50 | [strongSelf handleResponeWithData:data response:response error:error]; 51 | }]; 52 | [self.task resume]; 53 | } else { 54 | self.data = [NSData dataWithContentsOfURL:self.unit.completeURL]; 55 | } 56 | } 57 | return self; 58 | } 59 | 60 | - (void)dealloc 61 | { 62 | [self.unit workingRelease]; 63 | [self.task cancel]; 64 | KTVHCLogDealloc(self); 65 | } 66 | 67 | #pragma mark - HTTPResponse 68 | 69 | - (void)handleResponeWithData:(NSData *)data response:(NSURLResponse *)response error:(NSError *)error 70 | { 71 | if (error || data.length == 0 || ![response isKindOfClass:[NSHTTPURLResponse class]]) { 72 | [self.connection responseDidAbort:self]; 73 | KTVHCLogHTTPHLSResponse(@"%p, Handle response error: %@\nresponse : %@", self, error, response); 74 | } else { 75 | NSString *path = [KTVHCPathTool filePathWithURL:self.unit.URL offset:0]; 76 | data = [self handleResponeWithData:data]; 77 | if ([data writeToFile:path atomically:YES]) { 78 | self.data = data; 79 | KTVHCDataUnitItem *unitItem = [[KTVHCDataUnitItem alloc] initWithPath:path offset:0]; 80 | [unitItem updateLength:data.length]; 81 | [self.unit insertUnitItem:unitItem]; 82 | [self.unit updateResponseHeaders:((NSHTTPURLResponse *)response).allHeaderFields totalLength:data.length]; 83 | [self.connection responseHasAvailableData:self]; 84 | } else { 85 | [self.connection responseDidAbort:self]; 86 | } 87 | } 88 | } 89 | 90 | - (NSData *)handleResponeWithData:(NSData *)data 91 | { 92 | NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 93 | KTVHCLogHTTPHLSResponse(@"%p, Handle response data : %@", self, string); 94 | if ([string containsString:@"\nhttp"]) { 95 | NSMutableArray *array = [string componentsSeparatedByString:@"\n"].mutableCopy; 96 | for (NSUInteger index = 0; index < array.count; index++) { 97 | NSString *line = array[index]; 98 | if ([line hasPrefix:@"http"]) { 99 | line = [@"./" stringByAppendingString:line]; 100 | [array replaceObjectAtIndex:index withObject:line]; 101 | } 102 | } 103 | string = [array componentsJoinedByString:@"\n"]; 104 | data = [string dataUsingEncoding:NSUTF8StringEncoding]; 105 | KTVHCLogHTTPHLSResponse(@"%p, Handle response data changed : %@", self, string); 106 | } 107 | return data; 108 | } 109 | 110 | - (NSData *)readDataOfLength:(NSUInteger)length 111 | { 112 | KTVHCLogHTTPHLSResponse(@"%p, Read data : %lld", self, (long long)self.data.length); 113 | self.readedLength = self.data.length; 114 | return self.data; 115 | } 116 | 117 | - (BOOL)delayResponseHeaders 118 | { 119 | KTVHCLogHTTPHLSResponse(@"%p, Delay response : %d", self, self.self.data.length == 0); 120 | return self.data.length == 0; 121 | } 122 | 123 | - (UInt64)contentLength 124 | { 125 | KTVHCLogHTTPHLSResponse(@"%p, Conetnt length : %lld", self, self.unit.totalLength); 126 | return self.data.length; 127 | } 128 | 129 | - (NSDictionary *)httpHeaders 130 | { 131 | KTVHCLogHTTPHLSResponse(@"%p, Header\n%@", self, self.unit.responseHeaders); 132 | return self.unit.responseHeaders; 133 | } 134 | 135 | - (UInt64)offset 136 | { 137 | KTVHCLogHTTPHLSResponse(@"%p, Offset : %lld", self, self.readedLength); 138 | return self.readedLength; 139 | } 140 | 141 | - (void)setOffset:(UInt64)offset 142 | { 143 | KTVHCLogHTTPHLSResponse(@"%p, Set offset : %lld, %ld", self, offset, self.data.length); 144 | } 145 | 146 | - (BOOL)isDone 147 | { 148 | KTVHCLogHTTPHLSResponse(@"%p, Check done : %lld", self, self.unit.totalLength); 149 | return self.readedLength > 0; 150 | } 151 | 152 | - (void)connectionDidClose 153 | { 154 | KTVHCLogHTTPHLSResponse(@"%p, Connection did closed : %lld, %lld", self, self.unit.totalLength, self.unit.cacheLength); 155 | [self.task cancel]; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPHeader.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/12/5. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #ifndef KTVHCHTTPHeader_h 10 | #define KTVHCHTTPHeader_h 11 | 12 | #import 13 | 14 | #import "HTTPServer.h" 15 | #import "HTTPConnection.h" 16 | #import "HTTPMessage.h" 17 | #import "HTTPResponse.h" 18 | #import "HTTPDataResponse.h" 19 | #import "HTTPErrorResponse.h" 20 | 21 | 22 | #endif /* KTVHCHTTPHeader_h */ 23 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPResponse.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "KTVHCHTTPHeader.h" 11 | 12 | @class KTVHCHTTPConnection; 13 | @class KTVHCDataRequest; 14 | 15 | @interface KTVHCHTTPResponse : NSObject 16 | 17 | + (instancetype)new NS_UNAVAILABLE; 18 | - (instancetype)init NS_UNAVAILABLE; 19 | 20 | - (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection dataRequest:(KTVHCDataRequest *)dataRequest NS_DESIGNATED_INITIALIZER; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPResponse.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCHTTPResponse.h" 10 | #import "KTVHCHTTPConnection.h" 11 | #import "KTVHCDataStorage.h" 12 | #import "KTVHCLog.h" 13 | 14 | @interface KTVHCHTTPResponse () 15 | 16 | @property (nonatomic) BOOL waitingResponse; 17 | @property (nonatomic, strong) KTVHCDataReader *reader; 18 | @property (nonatomic, weak) KTVHCHTTPConnection *connection; 19 | 20 | @end 21 | 22 | @implementation KTVHCHTTPResponse 23 | 24 | - (instancetype)initWithConnection:(KTVHCHTTPConnection *)connection dataRequest:(KTVHCDataRequest *)dataRequest 25 | { 26 | if (self = [super init]) { 27 | KTVHCLogAlloc(self); 28 | self.connection = connection; 29 | self.reader = [[KTVHCDataStorage storage] readerWithRequest:dataRequest]; 30 | self.reader.delegate = self; 31 | [self.reader prepare]; 32 | KTVHCLogHTTPResponse(@"%p, Create response\nrequest : %@", self, dataRequest); 33 | } 34 | return self; 35 | } 36 | 37 | - (void)dealloc 38 | { 39 | [self.reader close]; 40 | KTVHCLogDealloc(self); 41 | } 42 | 43 | #pragma mark - HTTPResponse 44 | 45 | - (NSData *)readDataOfLength:(NSUInteger)length 46 | { 47 | NSData *data = [self.reader readDataOfLength:length]; 48 | KTVHCLogHTTPResponse(@"%p, Read data : %lld", self, (long long)data.length); 49 | if (self.reader.isFinished) { 50 | KTVHCLogHTTPResponse(@"%p, Read data did finished", self); 51 | [self.reader close]; 52 | [self.connection responseDidAbort:self]; 53 | } 54 | return data; 55 | } 56 | 57 | - (BOOL)delayResponseHeaders 58 | { 59 | BOOL waiting = !self.reader.isPrepared; 60 | self.waitingResponse = waiting; 61 | KTVHCLogHTTPResponse(@"%p, Delay response : %d", self, self.waitingResponse); 62 | return waiting; 63 | } 64 | 65 | - (UInt64)contentLength 66 | { 67 | KTVHCLogHTTPResponse(@"%p, Conetnt length : %lld", self, self.reader.response.totalLength); 68 | return self.reader.response.totalLength; 69 | } 70 | 71 | - (NSDictionary *)httpHeaders 72 | { 73 | NSMutableDictionary *headers = [self.reader.response.headers mutableCopy]; 74 | [headers removeObjectForKey:@"Content-Range"]; 75 | [headers removeObjectForKey:@"content-range"]; 76 | [headers removeObjectForKey:@"Content-Length"]; 77 | [headers removeObjectForKey:@"content-length"]; 78 | KTVHCLogHTTPResponse(@"%p, Header\n%@", self, headers); 79 | return headers; 80 | } 81 | 82 | - (UInt64)offset 83 | { 84 | KTVHCLogHTTPResponse(@"%p, Offset : %lld", self, self.reader.readedLength); 85 | return self.reader.readedLength; 86 | } 87 | 88 | - (void)setOffset:(UInt64)offset 89 | { 90 | KTVHCLogHTTPResponse(@"%p, Set offset : %lld, %lld", self, offset, self.reader.readedLength); 91 | } 92 | 93 | - (BOOL)isDone 94 | { 95 | KTVHCLogHTTPResponse(@"%p, Check done : %d", self, self.reader.isFinished); 96 | return self.reader.isFinished; 97 | } 98 | 99 | - (void)connectionDidClose 100 | { 101 | KTVHCLogHTTPResponse(@"%p, Connection did closed : %lld, %lld", self, self.reader.response.contentLength, self.reader.readedLength); 102 | [self.reader close]; 103 | } 104 | 105 | #pragma mark - KTVHCDataReaderDelegate 106 | 107 | - (void)ktv_readerDidPrepare:(KTVHCDataReader *)reader 108 | { 109 | KTVHCLogHTTPResponse(@"%p, Prepared", self); 110 | if (self.reader.isPrepared && self.waitingResponse == YES) { 111 | KTVHCLogHTTPResponse(@"%p, Call connection did prepared", self); 112 | [self.connection responseHasAvailableData:self]; 113 | } 114 | } 115 | 116 | - (void)ktv_readerHasAvailableData:(KTVHCDataReader *)reader 117 | { 118 | KTVHCLogHTTPResponse(@"%p, Has available data", self); 119 | [self.connection responseHasAvailableData:self]; 120 | } 121 | 122 | - (void)ktv_reader:(KTVHCDataReader *)reader didFailWithError:(NSError *)error 123 | { 124 | KTVHCLogHTTPResponse(@"%p, Failed\nError : %@", self, error); 125 | [self.reader close]; 126 | [self.connection responseDidAbort:self]; 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPServer.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KTVHCHTTPServer : NSObject 12 | 13 | + (instancetype)new NS_UNAVAILABLE; 14 | - (instancetype)init NS_UNAVAILABLE; 15 | 16 | + (instancetype)server; 17 | 18 | @property (nonatomic, readonly, getter=isRunning) BOOL running; 19 | 20 | - (BOOL)setPort:(UInt16)port; 21 | 22 | - (BOOL)start:(NSError **)error; 23 | - (void)stop; 24 | 25 | - (NSURL *)URLWithOriginalURL:(NSURL *)URL; 26 | - (NSURL *)URLWithOriginalURL:(NSURL *)URL bindToLocalhost:(BOOL)bindToLocalhost; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCHTTPServer/KTVHCHTTPServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCHTTPServer.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCHTTPServer.h" 10 | #import "KTVHCHTTPConnection.h" 11 | #import "KTVHCHTTPHeader.h" 12 | #import "KTVHCURLTool.h" 13 | #import "KTVHCLog.h" 14 | 15 | #import 16 | #import 17 | #import 18 | 19 | @interface KTVHCHTTPServer () 20 | 21 | @property (nonatomic) BOOL wantsRunning; 22 | @property (nonatomic, strong) HTTPServer *server; 23 | @property (nonatomic, strong) NSCondition *pingCondition; 24 | @property (nonatomic, strong) dispatch_queue_t pingQueue; 25 | @property (nonatomic, strong) NSURLSessionDataTask *pingTask; 26 | 27 | @end 28 | 29 | @implementation KTVHCHTTPServer 30 | 31 | + (instancetype)server 32 | { 33 | static KTVHCHTTPServer *obj = nil; 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | obj = [[self alloc] init]; 37 | }); 38 | return obj; 39 | } 40 | 41 | - (instancetype)init 42 | { 43 | if (self = [super init]) { 44 | KTVHCLogAlloc(self); 45 | self.server = [[HTTPServer alloc] init]; 46 | [self.server setConnectionClass:[KTVHCHTTPConnection class]]; 47 | [self.server setType:@"_http._tcp."]; 48 | self.pingCondition = [[NSCondition alloc] init]; 49 | self.pingQueue = dispatch_queue_create("KTVHCHTTPServer_pingQueue", DISPATCH_QUEUE_SERIAL); 50 | [[NSNotificationCenter defaultCenter] addObserver:self 51 | selector:@selector(applicationWillEnterForeground) 52 | name:UIApplicationWillEnterForegroundNotification 53 | object:nil]; 54 | } 55 | return self; 56 | } 57 | 58 | - (void)dealloc 59 | { 60 | KTVHCLogDealloc(self); 61 | [self stopInternal]; 62 | } 63 | 64 | - (BOOL)setPort:(UInt16)port 65 | { 66 | if (self.isRunning) { 67 | return NO; 68 | } 69 | [self.server setPort:port]; 70 | return YES; 71 | } 72 | 73 | - (BOOL)isRunning 74 | { 75 | return self.server.isRunning; 76 | } 77 | 78 | - (BOOL)start:(NSError **)error 79 | { 80 | self.wantsRunning = YES; 81 | return [self startInternal:error]; 82 | } 83 | 84 | - (void)stop 85 | { 86 | self.wantsRunning = NO; 87 | [self stopInternal]; 88 | } 89 | 90 | - (NSURL *)URLWithOriginalURL:(NSURL *)URL 91 | { 92 | return [self URLWithOriginalURL:URL bindToLocalhost:YES]; 93 | } 94 | 95 | - (NSURL *)URLWithOriginalURL:(NSURL *)URL bindToLocalhost:(BOOL)bindToLocalhost 96 | { 97 | if (!URL || URL.isFileURL || URL.absoluteString.length == 0) { 98 | return URL; 99 | } 100 | if (!self.isRunning) { 101 | return URL; 102 | } 103 | NSString *URLString = [NSString stringWithFormat:@"http://%@:%d/%@/%@/%@%@", 104 | bindToLocalhost ? @"localhost" : [self getPrimaryIPAddress], 105 | self.server.listeningPort, 106 | [[KTVHCURLTool tool] URLEncode:URL.absoluteString], 107 | [KTVHCHTTPConnection URITokenPlaceHolder], 108 | [KTVHCHTTPConnection URITokenLastPathComponent], 109 | URL.pathExtension.length > 0 ? [NSString stringWithFormat:@".%@", URL.pathExtension] : @""]; 110 | URL = [NSURL URLWithString:URLString]; 111 | KTVHCLogHTTPServer(@"%p, Return URL\nURL : %@", self, URL); 112 | return URL; 113 | } 114 | 115 | #pragma mark - Internal 116 | 117 | - (BOOL)startInternal:(NSError **)error 118 | { 119 | BOOL ret = [self.server start:error]; 120 | if (ret) { 121 | KTVHCLogHTTPServer(@"%p, Start server success", self); 122 | } else { 123 | KTVHCLogHTTPServer(@"%p, Start server failed", self); 124 | } 125 | return ret; 126 | } 127 | 128 | - (void)stopInternal 129 | { 130 | [self.server stop]; 131 | } 132 | 133 | - (BOOL)ping 134 | { 135 | [self.pingCondition lock]; 136 | static NSURLSession *session = nil; 137 | static dispatch_once_t onceToken; 138 | dispatch_once(&onceToken, ^{ 139 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 140 | configuration.timeoutIntervalForRequest = 3; 141 | session = [NSURLSession sessionWithConfiguration:configuration]; 142 | }); 143 | __block BOOL result = NO; 144 | __weak typeof(self) weakSelf = self; 145 | NSURL *URL = [self URLWithOriginalURL:[NSURL URLWithString:[KTVHCHTTPConnection URITokenPing]]]; 146 | self.pingTask = [session dataTaskWithURL:URL completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { 147 | if (!error && data.length > 0) { 148 | result = YES; 149 | } 150 | [weakSelf.pingCondition lock]; 151 | [weakSelf.pingCondition broadcast]; 152 | [weakSelf.pingCondition unlock]; 153 | }]; 154 | [self.pingTask resume]; 155 | [self.pingCondition wait]; 156 | self.pingTask = nil; 157 | [self.pingCondition unlock]; 158 | return result; 159 | } 160 | 161 | - (NSString *)getPrimaryIPAddress 162 | { 163 | NSString *address = @"localhost"; 164 | struct ifaddrs *list; 165 | if (getifaddrs(&list) >= 0) { 166 | for (struct ifaddrs *ifap = list; ifap; ifap = ifap->ifa_next) { 167 | if (strcmp(ifap->ifa_name, "en0")) { 168 | continue; 169 | } 170 | if ((ifap->ifa_flags & IFF_UP) && (ifap->ifa_addr->sa_family == AF_INET)) { 171 | address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)ifap->ifa_addr)->sin_addr)]; 172 | break; 173 | } 174 | } 175 | freeifaddrs(list); 176 | } 177 | return address; 178 | } 179 | 180 | #pragma mark - Background Task 181 | 182 | - (void)applicationWillEnterForeground 183 | { 184 | dispatch_async(self.pingQueue, ^{ 185 | if (self.wantsRunning) { 186 | KTVHCLogHTTPServer(@"%p, ping server", self); 187 | if (![self ping]) { 188 | [self stopInternal]; 189 | NSError *error = nil; 190 | if (![self startInternal:&error]) { 191 | KTVHCLogHTTPServer(@"%p, restart server error %@", self, error); 192 | }; 193 | } 194 | } 195 | }); 196 | } 197 | 198 | @end 199 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCTools/KTVHCPathTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCPathTool.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/12. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KTVHCPathTool : NSObject 12 | 13 | + (NSString *)logPath; 14 | + (NSString *)archivePath; 15 | + (NSString *)directoryPathWithURL:(NSURL *)URL; 16 | + (NSString *)completeFilePathWithURL:(NSURL *)URL; 17 | + (NSString *)filePathWithURL:(NSURL *)URL offset:(long long)offset; 18 | + (NSString *)converToRelativePath:(NSString *)path; 19 | + (NSString *)converToAbsoultePath:(NSString *)path; 20 | 21 | + (void)createFileAtPath:(NSString *)path; 22 | + (void)createDirectoryAtPath:(NSString *)path; 23 | + (NSError *)deleteFileAtPath:(NSString *)path; 24 | + (NSError *)deleteDirectoryAtPath:(NSString *)path; 25 | 26 | + (long long)sizeAtPath:(NSString *)path; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCTools/KTVHCPathTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCPathTool.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/12. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCPathTool.h" 10 | #import "KTVHCURLTool.h" 11 | 12 | @implementation KTVHCPathTool 13 | 14 | + (NSString *)rootDirectory 15 | { 16 | static NSString *obj = @"KTVHTTPCache"; 17 | [self createDirectoryAtPath:obj]; 18 | return obj; 19 | } 20 | 21 | + (NSString *)logPath 22 | { 23 | NSString *path = [[self rootDirectory] stringByAppendingPathComponent:@"KTVHTTPCache.log"]; 24 | return [self converToAbsoultePath:path]; 25 | } 26 | 27 | + (NSString *)archivePath 28 | { 29 | NSString *path = [[self rootDirectory] stringByAppendingPathComponent:@"KTVHTTPCache.archive"]; 30 | return [self converToAbsoultePath:path]; 31 | } 32 | 33 | + (NSString *)directoryPathWithURL:(NSURL *)URL 34 | { 35 | NSString *name = [[KTVHCURLTool tool] keyWithURL:URL]; 36 | NSString *path = [[self rootDirectory] stringByAppendingPathComponent:name]; 37 | [self createDirectoryAtPath:path]; 38 | return [self converToAbsoultePath:path]; 39 | } 40 | 41 | + (NSString *)completeFilePathWithURL:(NSURL *)URL 42 | { 43 | NSString *fileName = [[[KTVHCURLTool tool] keyWithURL:URL] stringByAppendingPathExtension:URL.pathExtension]; 44 | NSString *directoryPath = [self directoryPathWithURL:URL]; 45 | NSString *filePath = [directoryPath stringByAppendingPathComponent:fileName]; 46 | return [self converToAbsoultePath:filePath]; 47 | } 48 | 49 | + (NSString *)filePathWithURL:(NSURL *)URL offset:(long long)offset 50 | { 51 | NSString *baseFileName = [[KTVHCURLTool tool] keyWithURL:URL]; 52 | NSString *directoryPath = [self directoryPathWithURL:URL]; 53 | int number = 0; 54 | NSString *filePath = nil; 55 | while (!filePath) { 56 | NSString *fileName = [NSString stringWithFormat:@"%@_%lld_%d", baseFileName, offset, number]; 57 | NSString *currentFilePath = [directoryPath stringByAppendingPathComponent:fileName]; 58 | if (![[NSFileManager defaultManager] fileExistsAtPath:currentFilePath]) { 59 | [[NSFileManager defaultManager] createFileAtPath:currentFilePath contents:nil attributes:nil]; 60 | filePath = currentFilePath; 61 | } 62 | number++; 63 | } 64 | return [self converToAbsoultePath:filePath];; 65 | } 66 | 67 | + (NSString *)basePath 68 | { 69 | return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; 70 | } 71 | 72 | + (BOOL)isRelativePath:(NSString *)path 73 | { 74 | return ![path hasPrefix:[self basePath]]; 75 | } 76 | 77 | + (BOOL)isAbsolutePath:(NSString *)path 78 | { 79 | return [path hasPrefix:[self basePath]]; 80 | } 81 | 82 | + (NSString *)converToRelativePath:(NSString *)path 83 | { 84 | if ([self isAbsolutePath:path]) { 85 | path = [path stringByReplacingOccurrencesOfString:[self basePath] withString:@""]; 86 | } 87 | return path; 88 | } 89 | 90 | + (NSString *)converToAbsoultePath:(NSString *)path 91 | { 92 | if ([self isRelativePath:path]) { 93 | path = [[self basePath] stringByAppendingPathComponent:path];; 94 | } 95 | return path; 96 | } 97 | 98 | + (void)createFileAtPath:(NSString *)path 99 | { 100 | if (path.length <= 0) { 101 | return; 102 | } 103 | path = [self converToAbsoultePath:path]; 104 | BOOL isDirectory = NO; 105 | BOOL isExists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; 106 | if (!isExists || isDirectory) { 107 | [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil]; 108 | } 109 | } 110 | 111 | + (void)createDirectoryAtPath:(NSString *)path 112 | { 113 | if (path.length <= 0) { 114 | return; 115 | } 116 | path = [self converToAbsoultePath:path]; 117 | BOOL isDirectory = NO; 118 | BOOL isExists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; 119 | if (!isExists || !isDirectory) { 120 | [[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil]; 121 | } 122 | } 123 | 124 | + (NSError *)deleteFileAtPath:(NSString *)path 125 | { 126 | if (path.length <= 0) { 127 | return nil; 128 | } 129 | path = [self converToAbsoultePath:path]; 130 | NSError *error = nil; 131 | BOOL isDirectory = NO; 132 | BOOL result = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; 133 | if (result && !isDirectory) { 134 | result = [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; 135 | } 136 | return error; 137 | } 138 | 139 | + (NSError *)deleteDirectoryAtPath:(NSString *)path 140 | { 141 | if (path.length <= 0) { 142 | return nil; 143 | } 144 | path = [self converToAbsoultePath:path]; 145 | NSError *error = nil; 146 | BOOL isDirectory = NO; 147 | BOOL result = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; 148 | if (result && isDirectory) { 149 | result = [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; 150 | } 151 | return error; 152 | } 153 | 154 | + (long long)sizeAtPath:(NSString *)path 155 | { 156 | if (path.length <= 0) { 157 | return 0; 158 | } 159 | path = [self converToAbsoultePath:path]; 160 | NSError *error = nil; 161 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; 162 | if (!error || attributes.count > 0) { 163 | NSNumber *size = [attributes objectForKey:NSFileSize]; 164 | return size.longLongValue; 165 | } 166 | return 0; 167 | } 168 | 169 | @end 170 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCTools/KTVHCURLTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCURLTool.h 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface KTVHCURLTool : NSObject 12 | 13 | + (instancetype)new NS_UNAVAILABLE; 14 | - (instancetype)init NS_UNAVAILABLE; 15 | 16 | + (instancetype)tool; 17 | 18 | @property (nonatomic, copy) NSURL * (^URLConverter)(NSURL *URL); 19 | 20 | - (NSString *)keyWithURL:(NSURL *)URL; 21 | - (NSString *)URLEncode:(NSString *)URLString; 22 | - (NSString *)URLDecode:(NSString *)URLString; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /KTVHTTPCache/Classes/KTVHCTools/KTVHCURLTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHCURLTool.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHCURLTool.h" 10 | #import 11 | 12 | @interface KTVHCURLTool () 13 | 14 | @property (nonatomic, strong) NSLock *lock; 15 | @property (nonatomic, strong) NSMutableDictionary *md5s; 16 | 17 | @end 18 | 19 | @implementation KTVHCURLTool 20 | 21 | + (instancetype)tool 22 | { 23 | static KTVHCURLTool *obj = nil; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | obj = [[self alloc] init]; 27 | }); 28 | return obj; 29 | } 30 | 31 | - (instancetype)init 32 | { 33 | if (self = [super init]) { 34 | self.lock = [[NSLock alloc] init]; 35 | self.md5s = [NSMutableDictionary dictionary]; 36 | } 37 | return self; 38 | } 39 | 40 | - (NSString *)keyWithURL:(NSURL *)URL 41 | { 42 | if (self.URLConverter && URL.absoluteString.length > 0) { 43 | NSURL *newURL = self.URLConverter(URL); 44 | if (newURL.absoluteString.length > 0) { 45 | URL = newURL; 46 | } 47 | } 48 | return [self md5:URL.absoluteString]; 49 | } 50 | 51 | - (NSString *)md5:(NSString *)URLString 52 | { 53 | [self.lock lock]; 54 | NSString *result = [self.md5s objectForKey:URLString]; 55 | if (!result || result.length == 0) { 56 | const char *value = [URLString UTF8String]; 57 | unsigned char outputBuffer[CC_MD5_DIGEST_LENGTH]; 58 | CC_MD5(value, (CC_LONG)strlen(value), outputBuffer); 59 | NSMutableString *outputString = [[NSMutableString alloc] initWithCapacity:CC_MD5_DIGEST_LENGTH *2]; 60 | for (NSInteger count = 0; count < CC_MD5_DIGEST_LENGTH; count++) { 61 | [outputString appendFormat:@"%02x", outputBuffer[count]]; 62 | } 63 | result = outputString; 64 | [self.md5s setObject:result forKey:URLString]; 65 | } 66 | [self.lock unlock]; 67 | return result; 68 | } 69 | 70 | - (NSString *)URLEncode:(NSString *)URLString 71 | { 72 | static NSString *characters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 73 | NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:characters]; 74 | return [URLString stringByAddingPercentEncodingWithAllowedCharacters:characterSet]; 75 | } 76 | 77 | - (NSString *)URLDecode:(NSString *)URLString 78 | { 79 | return [URLString stringByRemovingPercentEncoding]; 80 | } 81 | 82 | - (NSDictionary *)parseQuery:(NSString *)query 83 | { 84 | NSMutableDictionary *parameters = [NSMutableDictionary dictionary]; 85 | NSScanner *scanner = [[NSScanner alloc] initWithString:query]; 86 | [scanner setCharactersToBeSkipped:nil]; 87 | while (1) { 88 | NSString *key = nil; 89 | if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) { 90 | break; 91 | } 92 | [scanner setScanLocation:([scanner scanLocation] + 1)]; 93 | NSString *value = nil; 94 | [scanner scanUpToString:@"&" intoString:&value]; 95 | if (value == nil) { 96 | value = @""; 97 | } 98 | key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 99 | NSString *unescapedKey = key ? [self URLDecode:key] : nil; 100 | value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; 101 | NSString *unescapedValue = value ? [self URLDecode:value] : nil; 102 | if (unescapedKey && unescapedValue) { 103 | [parameters setObject:unescapedValue forKey:unescapedKey]; 104 | } 105 | if ([scanner isAtEnd]) { 106 | break; 107 | } 108 | [scanner setScanLocation:([scanner scanLocation] + 1)]; 109 | } 110 | return parameters; 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Categories/DDData.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSData (DDData) 4 | 5 | - (NSData *)md5Digest; 6 | 7 | - (NSData *)sha1Digest; 8 | 9 | - (NSString *)hexStringValue; 10 | 11 | - (NSString *)base64Encoded; 12 | - (NSData *)base64Decoded; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Categories/DDData.m: -------------------------------------------------------------------------------- 1 | #import "DDData.h" 2 | #import 3 | 4 | 5 | @implementation NSData (DDData) 6 | 7 | static char encodingTable[64] = { 8 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P', 9 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f', 10 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v', 11 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' }; 12 | 13 | - (NSData *)md5Digest 14 | { 15 | unsigned char result[CC_MD5_DIGEST_LENGTH]; 16 | 17 | CC_MD5([self bytes], (CC_LONG)[self length], result); 18 | return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH]; 19 | } 20 | 21 | - (NSData *)sha1Digest 22 | { 23 | unsigned char result[CC_SHA1_DIGEST_LENGTH]; 24 | 25 | CC_SHA1([self bytes], (CC_LONG)[self length], result); 26 | return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH]; 27 | } 28 | 29 | - (NSString *)hexStringValue 30 | { 31 | NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)]; 32 | 33 | const unsigned char *dataBuffer = [self bytes]; 34 | int i; 35 | 36 | for (i = 0; i < [self length]; ++i) 37 | { 38 | [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]]; 39 | } 40 | 41 | return [stringBuffer copy]; 42 | } 43 | 44 | - (NSString *)base64Encoded 45 | { 46 | const unsigned char *bytes = [self bytes]; 47 | NSMutableString *result = [NSMutableString stringWithCapacity:[self length]]; 48 | unsigned long ixtext = 0; 49 | unsigned long lentext = [self length]; 50 | long ctremaining = 0; 51 | unsigned char inbuf[3], outbuf[4]; 52 | unsigned short i = 0; 53 | unsigned short charsonline = 0, ctcopy = 0; 54 | unsigned long ix = 0; 55 | 56 | while( YES ) 57 | { 58 | ctremaining = lentext - ixtext; 59 | if( ctremaining <= 0 ) break; 60 | 61 | for( i = 0; i < 3; i++ ) { 62 | ix = ixtext + i; 63 | if( ix < lentext ) inbuf[i] = bytes[ix]; 64 | else inbuf [i] = 0; 65 | } 66 | 67 | outbuf [0] = (inbuf [0] & 0xFC) >> 2; 68 | outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4); 69 | outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6); 70 | outbuf [3] = inbuf [2] & 0x3F; 71 | ctcopy = 4; 72 | 73 | switch( ctremaining ) 74 | { 75 | case 1: 76 | ctcopy = 2; 77 | break; 78 | case 2: 79 | ctcopy = 3; 80 | break; 81 | } 82 | 83 | for( i = 0; i < ctcopy; i++ ) 84 | [result appendFormat:@"%c", encodingTable[outbuf[i]]]; 85 | 86 | for( i = ctcopy; i < 4; i++ ) 87 | [result appendString:@"="]; 88 | 89 | ixtext += 3; 90 | charsonline += 4; 91 | } 92 | 93 | return [NSString stringWithString:result]; 94 | } 95 | 96 | - (NSData *)base64Decoded 97 | { 98 | const unsigned char *bytes = [self bytes]; 99 | NSMutableData *result = [NSMutableData dataWithCapacity:[self length]]; 100 | 101 | unsigned long ixtext = 0; 102 | unsigned long lentext = [self length]; 103 | unsigned char ch = 0; 104 | unsigned char inbuf[4] = {0, 0, 0, 0}; 105 | unsigned char outbuf[3] = {0, 0, 0}; 106 | short i = 0, ixinbuf = 0; 107 | BOOL flignore = NO; 108 | BOOL flendtext = NO; 109 | 110 | while( YES ) 111 | { 112 | if( ixtext >= lentext ) break; 113 | ch = bytes[ixtext++]; 114 | flignore = NO; 115 | 116 | if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A'; 117 | else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26; 118 | else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52; 119 | else if( ch == '+' ) ch = 62; 120 | else if( ch == '=' ) flendtext = YES; 121 | else if( ch == '/' ) ch = 63; 122 | else flignore = YES; 123 | 124 | if( ! flignore ) 125 | { 126 | short ctcharsinbuf = 3; 127 | BOOL flbreak = NO; 128 | 129 | if( flendtext ) 130 | { 131 | if( ! ixinbuf ) break; 132 | if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1; 133 | else ctcharsinbuf = 2; 134 | ixinbuf = 3; 135 | flbreak = YES; 136 | } 137 | 138 | inbuf [ixinbuf++] = ch; 139 | 140 | if( ixinbuf == 4 ) 141 | { 142 | ixinbuf = 0; 143 | outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 ); 144 | outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 ); 145 | outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F ); 146 | 147 | for( i = 0; i < ctcharsinbuf; i++ ) 148 | [result appendBytes:&outbuf[i] length:1]; 149 | } 150 | 151 | if( flbreak ) break; 152 | } 153 | } 154 | 155 | return [NSData dataWithData:result]; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Categories/DDNumber.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSNumber (DDNumber) 5 | 6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum; 7 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum; 8 | 9 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum; 10 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Categories/DDNumber.m: -------------------------------------------------------------------------------- 1 | #import "DDNumber.h" 2 | 3 | 4 | @implementation NSNumber (DDNumber) 5 | 6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum 7 | { 8 | if(str == nil) 9 | { 10 | *pNum = 0; 11 | return NO; 12 | } 13 | 14 | errno = 0; 15 | 16 | // On both 32-bit and 64-bit machines, long long = 64 bit 17 | 18 | *pNum = strtoll([str UTF8String], NULL, 10); 19 | 20 | if(errno != 0) 21 | return NO; 22 | else 23 | return YES; 24 | } 25 | 26 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum 27 | { 28 | if(str == nil) 29 | { 30 | *pNum = 0; 31 | return NO; 32 | } 33 | 34 | errno = 0; 35 | 36 | // On both 32-bit and 64-bit machines, unsigned long long = 64 bit 37 | 38 | *pNum = strtoull([str UTF8String], NULL, 10); 39 | 40 | if(errno != 0) 41 | return NO; 42 | else 43 | return YES; 44 | } 45 | 46 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum 47 | { 48 | if(str == nil) 49 | { 50 | *pNum = 0; 51 | return NO; 52 | } 53 | 54 | errno = 0; 55 | 56 | // On LP64, NSInteger = long = 64 bit 57 | // Otherwise, NSInteger = int = long = 32 bit 58 | 59 | *pNum = strtol([str UTF8String], NULL, 10); 60 | 61 | if(errno != 0) 62 | return NO; 63 | else 64 | return YES; 65 | } 66 | 67 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum 68 | { 69 | if(str == nil) 70 | { 71 | *pNum = 0; 72 | return NO; 73 | } 74 | 75 | errno = 0; 76 | 77 | // On LP64, NSUInteger = unsigned long = 64 bit 78 | // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit 79 | 80 | *pNum = strtoul([str UTF8String], NULL, 10); 81 | 82 | if(errno != 0) 83 | return NO; 84 | else 85 | return YES; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Categories/DDRange.h: -------------------------------------------------------------------------------- 1 | /** 2 | * DDRange is the functional equivalent of a 64 bit NSRange. 3 | * The HTTP Server is designed to support very large files. 4 | * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers. 5 | * This only supports a range of up to 4 gigabytes. 6 | * By defining our own variant, we can support a range up to 16 exabytes. 7 | * 8 | * All effort is given such that DDRange functions EXACTLY the same as NSRange. 9 | **/ 10 | 11 | #import 12 | #import 13 | 14 | @class NSString; 15 | 16 | typedef struct _DDRange { 17 | UInt64 location; 18 | UInt64 length; 19 | } DDRange; 20 | 21 | typedef DDRange *DDRangePointer; 22 | 23 | NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) { 24 | DDRange r; 25 | r.location = loc; 26 | r.length = len; 27 | return r; 28 | } 29 | 30 | NS_INLINE UInt64 DDMaxRange(DDRange range) { 31 | return (range.location + range.length); 32 | } 33 | 34 | NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) { 35 | return (loc - range.location < range.length); 36 | } 37 | 38 | NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) { 39 | return ((range1.location == range2.location) && (range1.length == range2.length)); 40 | } 41 | 42 | FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2); 43 | FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2); 44 | FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range); 45 | FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString); 46 | 47 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2); 48 | 49 | @interface NSValue (NSValueDDRangeExtensions) 50 | 51 | + (NSValue *)valueWithDDRange:(DDRange)range; 52 | - (DDRange)ddrangeValue; 53 | 54 | - (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Categories/DDRange.m: -------------------------------------------------------------------------------- 1 | #import "DDRange.h" 2 | #import "DDNumber.h" 3 | 4 | DDRange DDUnionRange(DDRange range1, DDRange range2) 5 | { 6 | DDRange result; 7 | 8 | result.location = MIN(range1.location, range2.location); 9 | result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location; 10 | 11 | return result; 12 | } 13 | 14 | DDRange DDIntersectionRange(DDRange range1, DDRange range2) 15 | { 16 | DDRange result; 17 | 18 | if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location)) 19 | { 20 | return DDMakeRange(0, 0); 21 | } 22 | 23 | result.location = MAX(range1.location, range2.location); 24 | result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location; 25 | 26 | return result; 27 | } 28 | 29 | NSString *DDStringFromRange(DDRange range) 30 | { 31 | return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length]; 32 | } 33 | 34 | DDRange DDRangeFromString(NSString *aString) 35 | { 36 | DDRange result = DDMakeRange(0, 0); 37 | 38 | // NSRange will ignore '-' characters, but not '+' characters 39 | NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"]; 40 | 41 | NSScanner *scanner = [NSScanner scannerWithString:aString]; 42 | [scanner setCharactersToBeSkipped:[cset invertedSet]]; 43 | 44 | NSString *str1 = nil; 45 | NSString *str2 = nil; 46 | 47 | BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1]; 48 | BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2]; 49 | 50 | if(found1) [NSNumber parseString:str1 intoUInt64:&result.location]; 51 | if(found2) [NSNumber parseString:str2 intoUInt64:&result.length]; 52 | 53 | return result; 54 | } 55 | 56 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2) 57 | { 58 | // Comparison basis: 59 | // Which range would you encouter first if you started at zero, and began walking towards infinity. 60 | // If you encouter both ranges at the same time, which range would end first. 61 | 62 | if(pDDRange1->location < pDDRange2->location) 63 | { 64 | return NSOrderedAscending; 65 | } 66 | if(pDDRange1->location > pDDRange2->location) 67 | { 68 | return NSOrderedDescending; 69 | } 70 | if(pDDRange1->length < pDDRange2->length) 71 | { 72 | return NSOrderedAscending; 73 | } 74 | if(pDDRange1->length > pDDRange2->length) 75 | { 76 | return NSOrderedDescending; 77 | } 78 | 79 | return NSOrderedSame; 80 | } 81 | 82 | @implementation NSValue (NSValueDDRangeExtensions) 83 | 84 | + (NSValue *)valueWithDDRange:(DDRange)range 85 | { 86 | return [NSValue valueWithBytes:&range objCType:@encode(DDRange)]; 87 | } 88 | 89 | - (DDRange)ddrangeValue 90 | { 91 | DDRange result; 92 | [self getValue:&result]; 93 | return result; 94 | } 95 | 96 | - (NSInteger)ddrangeCompare:(NSValue *)other 97 | { 98 | DDRange r1 = [self ddrangeValue]; 99 | DDRange r2 = [other ddrangeValue]; 100 | 101 | return DDRangeCompare(&r1, &r2); 102 | } 103 | 104 | @end 105 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPAuthenticationRequest.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #if TARGET_OS_IPHONE 4 | // Note: You may need to add the CFNetwork Framework to your project 5 | #import 6 | #endif 7 | 8 | @class HTTPMessage; 9 | 10 | 11 | @interface HTTPAuthenticationRequest : NSObject 12 | { 13 | BOOL isBasic; 14 | BOOL isDigest; 15 | 16 | NSString *base64Credentials; 17 | 18 | NSString *username; 19 | NSString *realm; 20 | NSString *nonce; 21 | NSString *uri; 22 | NSString *qop; 23 | NSString *nc; 24 | NSString *cnonce; 25 | NSString *response; 26 | } 27 | - (id)initWithRequest:(HTTPMessage *)request; 28 | 29 | - (BOOL)isBasic; 30 | - (BOOL)isDigest; 31 | 32 | // Basic 33 | - (NSString *)base64Credentials; 34 | 35 | // Digest 36 | - (NSString *)username; 37 | - (NSString *)realm; 38 | - (NSString *)nonce; 39 | - (NSString *)uri; 40 | - (NSString *)qop; 41 | - (NSString *)nc; 42 | - (NSString *)cnonce; 43 | - (NSString *)response; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPAuthenticationRequest.m: -------------------------------------------------------------------------------- 1 | #import "HTTPAuthenticationRequest.h" 2 | #import "HTTPMessage.h" 3 | 4 | #if ! __has_feature(objc_arc) 5 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 6 | #endif 7 | 8 | @interface HTTPAuthenticationRequest (PrivateAPI) 9 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; 10 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header; 11 | @end 12 | 13 | 14 | @implementation HTTPAuthenticationRequest 15 | 16 | - (id)initWithRequest:(HTTPMessage *)request 17 | { 18 | if ((self = [super init])) 19 | { 20 | NSString *authInfo = [request headerField:@"Authorization"]; 21 | 22 | isBasic = NO; 23 | if ([authInfo length] >= 6) 24 | { 25 | isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame; 26 | } 27 | 28 | isDigest = NO; 29 | if ([authInfo length] >= 7) 30 | { 31 | isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame; 32 | } 33 | 34 | if (isBasic) 35 | { 36 | NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy]; 37 | CFStringTrimWhitespace((__bridge CFMutableStringRef)temp); 38 | 39 | base64Credentials = [temp copy]; 40 | } 41 | 42 | if (isDigest) 43 | { 44 | username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo]; 45 | realm = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo]; 46 | nonce = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo]; 47 | uri = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo]; 48 | 49 | // It appears from RFC 2617 that the qop is to be given unquoted 50 | // Tests show that Firefox performs this way, but Safari does not 51 | // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote 52 | qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; 53 | if(qop && ([qop characterAtIndex:0] == '"')) 54 | { 55 | qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo]; 56 | } 57 | 58 | nc = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo]; 59 | cnonce = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo]; 60 | response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo]; 61 | } 62 | } 63 | return self; 64 | } 65 | 66 | 67 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 68 | #pragma mark Accessors: 69 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 70 | 71 | - (BOOL)isBasic { 72 | return isBasic; 73 | } 74 | 75 | - (BOOL)isDigest { 76 | return isDigest; 77 | } 78 | 79 | - (NSString *)base64Credentials { 80 | return base64Credentials; 81 | } 82 | 83 | - (NSString *)username { 84 | return username; 85 | } 86 | 87 | - (NSString *)realm { 88 | return realm; 89 | } 90 | 91 | - (NSString *)nonce { 92 | return nonce; 93 | } 94 | 95 | - (NSString *)uri { 96 | return uri; 97 | } 98 | 99 | - (NSString *)qop { 100 | return qop; 101 | } 102 | 103 | - (NSString *)nc { 104 | return nc; 105 | } 106 | 107 | - (NSString *)cnonce { 108 | return cnonce; 109 | } 110 | 111 | - (NSString *)response { 112 | return response; 113 | } 114 | 115 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 116 | #pragma mark Private API: 117 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 118 | 119 | /** 120 | * Retrieves a "Sub Header Field Value" from a given header field value. 121 | * The sub header field is expected to be quoted. 122 | * 123 | * In the following header field: 124 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" 125 | * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa". 126 | **/ 127 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header 128 | { 129 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]]; 130 | if(startRange.location == NSNotFound) 131 | { 132 | // The param was not found anywhere in the header 133 | return nil; 134 | } 135 | 136 | NSUInteger postStartRangeLocation = startRange.location + startRange.length; 137 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; 138 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); 139 | 140 | NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange]; 141 | if(endRange.location == NSNotFound) 142 | { 143 | // The ending double-quote was not found anywhere in the header 144 | return nil; 145 | } 146 | 147 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); 148 | return [header substringWithRange:subHeaderRange]; 149 | } 150 | 151 | /** 152 | * Retrieves a "Sub Header Field Value" from a given header field value. 153 | * The sub header field is expected to not be quoted. 154 | * 155 | * In the following header field: 156 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939" 157 | * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth". 158 | **/ 159 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header 160 | { 161 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]]; 162 | if(startRange.location == NSNotFound) 163 | { 164 | // The param was not found anywhere in the header 165 | return nil; 166 | } 167 | 168 | NSUInteger postStartRangeLocation = startRange.location + startRange.length; 169 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation; 170 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength); 171 | 172 | NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange]; 173 | if(endRange.location == NSNotFound) 174 | { 175 | // The ending comma was not found anywhere in the header 176 | // However, if the nonquoted param is at the end of the string, there would be no comma 177 | // This is only possible if there are no spaces anywhere 178 | NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange]; 179 | if(endRange2.location != NSNotFound) 180 | { 181 | return nil; 182 | } 183 | else 184 | { 185 | return [header substringWithRange:postStartRange]; 186 | } 187 | } 188 | else 189 | { 190 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation); 191 | return [header substringWithRange:subHeaderRange]; 192 | } 193 | } 194 | 195 | @end 196 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPConnection.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class GCDAsyncSocket; 4 | @class HTTPMessage; 5 | @class HTTPServer; 6 | @class WebSocket; 7 | @protocol HTTPResponse; 8 | 9 | 10 | #define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie" 11 | 12 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | #pragma mark - 14 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 15 | 16 | @interface HTTPConfig : NSObject 17 | { 18 | HTTPServer __unsafe_unretained *server; 19 | NSString __strong *documentRoot; 20 | dispatch_queue_t queue; 21 | } 22 | 23 | - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot; 24 | - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q; 25 | 26 | @property (nonatomic, unsafe_unretained, readonly) HTTPServer *server; 27 | @property (nonatomic, strong, readonly) NSString *documentRoot; 28 | @property (nonatomic, readonly) dispatch_queue_t queue; 29 | 30 | @end 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | #pragma mark - 34 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | @interface HTTPConnection : NSObject 37 | { 38 | dispatch_queue_t connectionQueue; 39 | GCDAsyncSocket *asyncSocket; 40 | HTTPConfig *config; 41 | 42 | BOOL started; 43 | 44 | HTTPMessage *request; 45 | unsigned int numHeaderLines; 46 | 47 | BOOL sentResponseHeaders; 48 | 49 | NSString *nonce; 50 | long lastNC; 51 | 52 | NSObject *httpResponse; 53 | 54 | NSMutableArray *ranges; 55 | NSMutableArray *ranges_headers; 56 | NSString *ranges_boundry; 57 | int rangeIndex; 58 | 59 | UInt64 requestContentLength; 60 | UInt64 requestContentLengthReceived; 61 | UInt64 requestChunkSize; 62 | UInt64 requestChunkSizeReceived; 63 | 64 | NSMutableArray *responseDataSizes; 65 | } 66 | 67 | - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig; 68 | 69 | - (void)start; 70 | - (void)stop; 71 | 72 | - (void)startConnection; 73 | 74 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path; 75 | - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path; 76 | 77 | - (BOOL)isSecureServer; 78 | - (NSArray *)sslIdentityAndCertificates; 79 | 80 | - (BOOL)isPasswordProtected:(NSString *)path; 81 | - (BOOL)useDigestAccessAuthentication; 82 | - (NSString *)realm; 83 | - (NSString *)passwordForUser:(NSString *)username; 84 | 85 | - (NSDictionary *)parseParams:(NSString *)query; 86 | - (NSDictionary *)parseGetParams; 87 | 88 | - (NSString *)requestURI; 89 | 90 | - (NSArray *)directoryIndexFileNames; 91 | - (NSString *)filePathForURI:(NSString *)path; 92 | - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory; 93 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path; 94 | - (WebSocket *)webSocketForURI:(NSString *)path; 95 | 96 | - (void)prepareForBodyWithSize:(UInt64)contentLength; 97 | - (void)processBodyData:(NSData *)postDataChunk; 98 | - (void)finishBody; 99 | 100 | - (void)handleVersionNotSupported:(NSString *)version; 101 | - (void)handleAuthenticationFailed; 102 | - (void)handleResourceNotFound; 103 | - (void)handleInvalidRequest:(NSData *)data; 104 | - (void)handleUnknownMethod:(NSString *)method; 105 | 106 | - (NSData *)preprocessResponse:(HTTPMessage *)response; 107 | - (NSData *)preprocessErrorResponse:(HTTPMessage *)response; 108 | 109 | - (void)finishResponse; 110 | 111 | - (BOOL)shouldDie; 112 | - (void)die; 113 | 114 | @end 115 | 116 | @interface HTTPConnection (AsynchronousHTTPResponse) 117 | - (void)responseHasAvailableData:(NSObject *)sender; 118 | - (void)responseDidAbort:(NSObject *)sender; 119 | @end 120 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPLogging.h: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPLogging.h 3 | // CocoaHTTPServer 4 | // 5 | // Created by Single on 2018/5/18. 6 | // Copyright © 2018年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #define KTVCHSLog(level, frmt, ...) [HTTPLogging log:level format:frmt, ##__VA_ARGS__] 12 | 13 | #define THIS_FILE self 14 | #define THIS_METHOD NSStringFromSelector(_cmd) 15 | 16 | #define HTTP_LOG_FLAG_ERROR (1 << 0) 17 | #define HTTP_LOG_FLAG_WARN (1 << 1) 18 | #define HTTP_LOG_FLAG_INFO (1 << 2) 19 | #define HTTP_LOG_FLAG_VERBOSE (1 << 3) 20 | #define HTTP_LOG_FLAG_TRACE (1 << 4) 21 | 22 | #define HTTP_LOG_LEVEL_OFF 0 23 | #define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) 24 | #define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) 25 | #define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) 26 | #define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) 27 | 28 | #define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR) 29 | #define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN) 30 | #define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO) 31 | #define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE) 32 | #define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE) 33 | 34 | #define HTTPLogError(frmt, ...) KTVCHSLog(HTTP_LOG_ERROR, frmt, ##__VA_ARGS__) 35 | #define HTTPLogWarn(frmt, ...) KTVCHSLog(HTTP_LOG_WARN, frmt, ##__VA_ARGS__) 36 | #define HTTPLogInfo(frmt, ...) KTVCHSLog(HTTP_LOG_INFO, frmt, ##__VA_ARGS__) 37 | #define HTTPLogVerbose(frmt, ...) KTVCHSLog(HTTP_LOG_VERBOSE, frmt, ##__VA_ARGS__) 38 | #define HTTPLogTrace() KTVCHSLog(HTTP_LOG_TRACE, @"%@ : %@", THIS_FILE, THIS_METHOD) 39 | #define HTTPLogTrace2(frmt, ...) KTVCHSLog(HTTP_LOG_TRACE, frmt, ##__VA_ARGS__) 40 | 41 | 42 | @interface HTTPLogging : NSObject 43 | 44 | + (void)log:(int)level format:(NSString *)format, ...; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPLogging.m: -------------------------------------------------------------------------------- 1 | // 2 | // HTTPLogging.h 3 | // CocoaHTTPServer 4 | // 5 | // Created by Single on 2018/5/18. 6 | // Copyright © 2018年 Single. All rights reserved. 7 | // 8 | 9 | #import "HTTPLogging.h" 10 | 11 | @implementation HTTPLogging 12 | 13 | + (void)log:(int)level format:(NSString *)format, ... 14 | { 15 | 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPMessage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class. 3 | **/ 4 | 5 | #import 6 | 7 | #if TARGET_OS_IPHONE 8 | // Note: You may need to add the CFNetwork Framework to your project 9 | #import 10 | #endif 11 | 12 | #define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0) 13 | #define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1) 14 | 15 | 16 | @interface HTTPMessage : NSObject 17 | { 18 | CFHTTPMessageRef message; 19 | } 20 | 21 | - (id)initEmptyRequest; 22 | 23 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version; 24 | 25 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version; 26 | 27 | - (BOOL)appendData:(NSData *)data; 28 | 29 | - (BOOL)isHeaderComplete; 30 | 31 | - (NSString *)version; 32 | 33 | - (NSString *)method; 34 | - (NSURL *)url; 35 | 36 | - (NSInteger)statusCode; 37 | 38 | - (NSDictionary *)allHeaderFields; 39 | - (NSString *)headerField:(NSString *)headerField; 40 | 41 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue; 42 | 43 | - (NSData *)messageData; 44 | 45 | - (NSData *)body; 46 | - (void)setBody:(NSData *)body; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/HTTPMessage.m: -------------------------------------------------------------------------------- 1 | #import "HTTPMessage.h" 2 | 3 | #if ! __has_feature(objc_arc) 4 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 5 | #endif 6 | 7 | 8 | @implementation HTTPMessage 9 | 10 | - (id)initEmptyRequest 11 | { 12 | if ((self = [super init])) 13 | { 14 | message = CFHTTPMessageCreateEmpty(NULL, YES); 15 | } 16 | return self; 17 | } 18 | 19 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version 20 | { 21 | if ((self = [super init])) 22 | { 23 | message = CFHTTPMessageCreateRequest(NULL, 24 | (__bridge CFStringRef)method, 25 | (__bridge CFURLRef)url, 26 | (__bridge CFStringRef)version); 27 | } 28 | return self; 29 | } 30 | 31 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version 32 | { 33 | if ((self = [super init])) 34 | { 35 | message = CFHTTPMessageCreateResponse(NULL, 36 | (CFIndex)code, 37 | (__bridge CFStringRef)description, 38 | (__bridge CFStringRef)version); 39 | } 40 | return self; 41 | } 42 | 43 | - (void)dealloc 44 | { 45 | if (message) 46 | { 47 | CFRelease(message); 48 | } 49 | } 50 | 51 | - (BOOL)appendData:(NSData *)data 52 | { 53 | return CFHTTPMessageAppendBytes(message, [data bytes], [data length]); 54 | } 55 | 56 | - (BOOL)isHeaderComplete 57 | { 58 | return CFHTTPMessageIsHeaderComplete(message); 59 | } 60 | 61 | - (NSString *)version 62 | { 63 | return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message); 64 | } 65 | 66 | - (NSString *)method 67 | { 68 | return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message); 69 | } 70 | 71 | - (NSURL *)url 72 | { 73 | return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message); 74 | } 75 | 76 | - (NSInteger)statusCode 77 | { 78 | return (NSInteger)CFHTTPMessageGetResponseStatusCode(message); 79 | } 80 | 81 | - (NSDictionary *)allHeaderFields 82 | { 83 | return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message); 84 | } 85 | 86 | - (NSString *)headerField:(NSString *)headerField 87 | { 88 | return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField); 89 | } 90 | 91 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue 92 | { 93 | CFHTTPMessageSetHeaderFieldValue(message, 94 | (__bridge CFStringRef)headerField, 95 | (__bridge CFStringRef)headerFieldValue); 96 | } 97 | 98 | - (NSData *)messageData 99 | { 100 | return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message); 101 | } 102 | 103 | - (NSData *)body 104 | { 105 | return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message); 106 | } 107 | 108 | - (void)setBody:(NSData *)body 109 | { 110 | CFHTTPMessageSetBody(message, (__bridge CFDataRef)body); 111 | } 112 | 113 | @end 114 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Mime/MultipartFormDataParser.h: -------------------------------------------------------------------------------- 1 | 2 | #import "MultipartMessageHeader.h" 3 | 4 | /* 5 | Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies) 6 | Part two: http://tools.ietf.org/html/rfc2046 (Media Types) 7 | Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text) 8 | Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures) 9 | Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples) 10 | 11 | Internet message format: http://tools.ietf.org/html/rfc2822 12 | 13 | Multipart/form-data http://tools.ietf.org/html/rfc2388 14 | */ 15 | 16 | @class MultipartFormDataParser; 17 | 18 | //----------------------------------------------------------------- 19 | // protocol MultipartFormDataParser 20 | //----------------------------------------------------------------- 21 | 22 | @protocol MultipartFormDataParserDelegate 23 | @optional 24 | - (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header; 25 | - (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header; 26 | - (void) processPreambleData:(NSData*) data; 27 | - (void) processEpilogueData:(NSData*) data; 28 | - (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header; 29 | @end 30 | 31 | //----------------------------------------------------------------- 32 | // interface MultipartFormDataParser 33 | //----------------------------------------------------------------- 34 | 35 | @interface MultipartFormDataParser : NSObject { 36 | NSMutableData* pendingData; 37 | NSData* boundaryData; 38 | MultipartMessageHeader* currentHeader; 39 | 40 | BOOL waitingForCRLF; 41 | BOOL reachedEpilogue; 42 | BOOL processedPreamble; 43 | BOOL checkForContentEnd; 44 | 45 | #if __has_feature(objc_arc_weak) 46 | __weak id delegate; 47 | #else 48 | __unsafe_unretained id delegate; 49 | #endif 50 | int currentEncoding; 51 | NSStringEncoding formEncoding; 52 | } 53 | 54 | - (BOOL) appendData:(NSData*) data; 55 | 56 | - (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding; 57 | 58 | #if __has_feature(objc_arc_weak) 59 | @property(weak, readwrite) id delegate; 60 | #else 61 | @property(unsafe_unretained, readwrite) id delegate; 62 | #endif 63 | @property(readwrite) NSStringEncoding formEncoding; 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Mime/MultipartMessageHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MultipartMessagePart.h 3 | // HttpServer 4 | // 5 | // Created by Валерий Гаврилов on 29.03.12. 6 | // Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | //----------------------------------------------------------------- 13 | // interface MultipartMessageHeader 14 | //----------------------------------------------------------------- 15 | enum { 16 | contentTransferEncoding_unknown, 17 | contentTransferEncoding_7bit, 18 | contentTransferEncoding_8bit, 19 | contentTransferEncoding_binary, 20 | contentTransferEncoding_base64, 21 | contentTransferEncoding_quotedPrintable, 22 | }; 23 | 24 | @interface MultipartMessageHeader : NSObject { 25 | NSMutableDictionary* fields; 26 | int encoding; 27 | NSString* contentDispositionName; 28 | } 29 | @property (strong,readonly) NSDictionary* fields; 30 | @property (readonly) int encoding; 31 | 32 | - (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding; 33 | @end 34 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Mime/MultipartMessageHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MultipartMessagePart.m 3 | // HttpServer 4 | // 5 | // Created by Валерий Гаврилов on 29.03.12. 6 | // Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved. 7 | 8 | #import "MultipartMessageHeader.h" 9 | #import "MultipartMessageHeaderField.h" 10 | 11 | #import "HTTPLogging.h" 12 | 13 | //----------------------------------------------------------------- 14 | #pragma mark log level 15 | 16 | #ifdef DEBUG 17 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 18 | #else 19 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 20 | #endif 21 | 22 | //----------------------------------------------------------------- 23 | // implementation MultipartMessageHeader 24 | //----------------------------------------------------------------- 25 | 26 | 27 | @implementation MultipartMessageHeader 28 | @synthesize fields,encoding; 29 | 30 | 31 | - (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding { 32 | if( nil == (self = [super init]) ) { 33 | return self; 34 | } 35 | 36 | fields = [[NSMutableDictionary alloc] initWithCapacity:1]; 37 | 38 | // In case encoding is not mentioned, 39 | encoding = contentTransferEncoding_unknown; 40 | 41 | char* bytes = (char*)data.bytes; 42 | NSUInteger length = data.length; 43 | int offset = 0; 44 | 45 | // split header into header fields, separated by \r\n 46 | uint16_t fields_separator = 0x0A0D; // \r\n 47 | while( offset < length - 2 ) { 48 | 49 | // the !isspace condition is to support header unfolding 50 | if( (*(uint16_t*) (bytes+offset) == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) { 51 | NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO]; 52 | MultipartMessageHeaderField* field = [[MultipartMessageHeaderField alloc] initWithData: fieldData contentEncoding:formEncoding]; 53 | if( field ) { 54 | [fields setObject:field forKey:field.name]; 55 | HTTPLogVerbose(@"MultipartFormDataParser: Processed Header field '%@'",field.name); 56 | } 57 | else { 58 | NSString* fieldStr = [[NSString alloc] initWithData:fieldData encoding:NSASCIIStringEncoding]; 59 | HTTPLogWarn(@"MultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr); 60 | } 61 | 62 | // move to the next header field 63 | bytes += offset + 2; 64 | length -= offset + 2; 65 | offset = 0; 66 | continue; 67 | } 68 | ++ offset; 69 | } 70 | 71 | if( !fields.count ) { 72 | // it was an empty header. 73 | // we have to set default values. 74 | // default header. 75 | [fields setObject:@"text/plain" forKey:@"Content-Type"]; 76 | } 77 | 78 | return self; 79 | } 80 | 81 | - (NSString *)description { 82 | return [NSString stringWithFormat:@"%@",fields]; 83 | } 84 | 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Mime/MultipartMessageHeaderField.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | //----------------------------------------------------------------- 5 | // interface MultipartMessageHeaderField 6 | //----------------------------------------------------------------- 7 | 8 | @interface MultipartMessageHeaderField : NSObject { 9 | NSString* name; 10 | NSString* value; 11 | NSMutableDictionary* params; 12 | } 13 | 14 | @property (strong, readonly) NSString* value; 15 | @property (strong, readonly) NSDictionary* params; 16 | @property (strong, readonly) NSString* name; 17 | 18 | //- (id) initWithLine:(NSString*) line; 19 | //- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue; 20 | 21 | - (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Mime/MultipartMessageHeaderField.m: -------------------------------------------------------------------------------- 1 | 2 | #import "MultipartMessageHeaderField.h" 3 | #import "HTTPLogging.h" 4 | 5 | //----------------------------------------------------------------- 6 | #pragma mark log level 7 | 8 | #ifdef DEBUG 9 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 10 | #else 11 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; 12 | #endif 13 | 14 | 15 | // helpers 16 | int findChar(const char* str,NSUInteger length, char c); 17 | NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding); 18 | 19 | //----------------------------------------------------------------- 20 | // interface MultipartMessageHeaderField (private) 21 | //----------------------------------------------------------------- 22 | 23 | 24 | @interface MultipartMessageHeaderField (private) 25 | -(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding; 26 | @end 27 | 28 | 29 | //----------------------------------------------------------------- 30 | // implementation MultipartMessageHeaderField 31 | //----------------------------------------------------------------- 32 | 33 | @implementation MultipartMessageHeaderField 34 | @synthesize name,value,params; 35 | 36 | - (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding { 37 | params = [[NSMutableDictionary alloc] initWithCapacity:1]; 38 | 39 | char* bytes = (char*)data.bytes; 40 | NSUInteger length = data.length; 41 | 42 | int separatorOffset = findChar(bytes, length, ':'); 43 | if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) { 44 | HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header."); 45 | // tear down 46 | return nil; 47 | } 48 | 49 | // header name is always ascii encoded; 50 | name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding]; 51 | if( nil == name ) { 52 | HTTPLogError(@"MultipartFormDataParser: Bad MIME header name."); 53 | // tear down 54 | return nil; 55 | } 56 | 57 | // skip the separator and the next ' ' symbol 58 | bytes += separatorOffset + 2; 59 | length -= separatorOffset + 2; 60 | 61 | separatorOffset = findChar(bytes, length, ';'); 62 | if( separatorOffset == -1 ) { 63 | // couldn't find ';', means we don't have extra params here. 64 | value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding]; 65 | 66 | if( nil == value ) { 67 | HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name); 68 | // tear down 69 | return nil; 70 | } 71 | return self; 72 | } 73 | 74 | value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding]; 75 | HTTPLogVerbose(@"MultipartFormDataParser: Processing header field '%@' : '%@'",name,value); 76 | // skipe the separator and the next ' ' symbol 77 | bytes += separatorOffset + 2; 78 | length -= separatorOffset + 2; 79 | 80 | // parse the "params" part of the header 81 | if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) { 82 | NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding]; 83 | HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value); 84 | HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr); 85 | 86 | return nil; 87 | } 88 | return self; 89 | } 90 | 91 | -(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding { 92 | int offset = 0; 93 | NSString* currentParam = nil; 94 | BOOL insideQuote = NO; 95 | while( offset < length ) { 96 | if( bytes[offset] == '\"' ) { 97 | if( !offset || bytes[offset-1] != '\\' ) { 98 | insideQuote = !insideQuote; 99 | } 100 | } 101 | 102 | // skip quoted symbols 103 | if( insideQuote ) { 104 | ++ offset; 105 | continue; 106 | } 107 | if( bytes[offset] == '=' ) { 108 | if( currentParam ) { 109 | // found '=' before terminating previous param. 110 | return NO; 111 | } 112 | currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding]; 113 | 114 | bytes+=offset + 1; 115 | length -= offset + 1; 116 | offset = 0; 117 | continue; 118 | } 119 | if( bytes[offset] == ';' ) { 120 | if( !currentParam ) { 121 | // found ; before stating '='. 122 | HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header"); 123 | return NO; 124 | } 125 | NSString* paramValue = extractParamValue(bytes, offset,encoding); 126 | if( nil == paramValue ) { 127 | HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); 128 | } 129 | else { 130 | #ifdef DEBUG 131 | if( [params objectForKey:currentParam] ) { 132 | HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name); 133 | } 134 | #endif 135 | [params setObject:paramValue forKey:currentParam]; 136 | HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); 137 | } 138 | 139 | currentParam = nil; 140 | 141 | // ';' separator has ' ' following, skip them. 142 | bytes+=offset + 2; 143 | length -= offset + 2; 144 | offset = 0; 145 | } 146 | ++ offset; 147 | } 148 | 149 | // add last param 150 | if( insideQuote ) { 151 | HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name); 152 | // return YES; 153 | } 154 | if( currentParam ) { 155 | NSString* paramValue = extractParamValue(bytes, length, encoding); 156 | 157 | if( nil == paramValue ) { 158 | HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name); 159 | } 160 | 161 | #ifdef DEBUG 162 | if( [params objectForKey:currentParam] ) { 163 | HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam); 164 | } 165 | #endif 166 | [params setObject:paramValue forKey:currentParam]; 167 | HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue); 168 | currentParam = nil; 169 | } 170 | 171 | return YES; 172 | } 173 | 174 | - (NSString *)description { 175 | return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params]; 176 | } 177 | 178 | @end 179 | 180 | int findChar(const char* str, NSUInteger length, char c) { 181 | int offset = 0; 182 | while( offset < length ) { 183 | if( str[offset] == c ) 184 | return offset; 185 | ++ offset; 186 | } 187 | return -1; 188 | } 189 | 190 | NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding) { 191 | if( !length ) 192 | return nil; 193 | NSMutableString* value = nil; 194 | 195 | if( bytes[0] == '"' ) { 196 | // values may be quoted. Strip the quotes to get what we need. 197 | value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding]; 198 | } 199 | else { 200 | value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding]; 201 | } 202 | // restore escaped symbols 203 | NSRange range= [value rangeOfString:@"\\"]; 204 | while ( range.length ) { 205 | [value deleteCharactersInRange:range]; 206 | range.location ++; 207 | range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range]; 208 | } 209 | return value; 210 | } 211 | 212 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPAsyncFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | @class HTTPConnection; 5 | 6 | /** 7 | * This is an asynchronous version of HTTPFileResponse. 8 | * It reads data from the given file asynchronously via GCD. 9 | * 10 | * It may be overriden to allow custom post-processing of the data that has been read from the file. 11 | * An example of this is the HTTPDynamicFileResponse class. 12 | **/ 13 | 14 | @interface HTTPAsyncFileResponse : NSObject 15 | { 16 | HTTPConnection *connection; 17 | 18 | NSString *filePath; 19 | UInt64 fileLength; 20 | UInt64 fileOffset; // File offset as pertains to data given to connection 21 | UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection) 22 | 23 | BOOL aborted; 24 | 25 | NSData *data; 26 | 27 | int fileFD; 28 | void *readBuffer; 29 | NSUInteger readBufferSize; // Malloced size of readBuffer 30 | NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is 31 | NSUInteger readRequestLength; 32 | dispatch_queue_t readQueue; 33 | dispatch_source_t readSource; 34 | BOOL readSourceSuspended; 35 | } 36 | 37 | - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; 38 | - (NSString *)filePath; 39 | 40 | @end 41 | 42 | /** 43 | * Explanation of Variables (excluding those that are obvious) 44 | * 45 | * fileOffset 46 | * This is the number of bytes that have been returned to the connection via the readDataOfLength method. 47 | * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection, 48 | * then the fileOffset variable remains at zero. 49 | * This variable is used in the calculation of the isDone method. 50 | * Only after all data has been returned to the connection are we actually done. 51 | * 52 | * readOffset 53 | * Represents the offset of the file descriptor. 54 | * In other words, the file position indidcator for our read stream. 55 | * It might be easy to think of it as the total number of bytes that have been read from the file. 56 | * However, this isn't entirely accurate, as the setOffset: method may have caused us to 57 | * jump ahead in the file (lseek). 58 | * 59 | * readBuffer 60 | * Malloc'd buffer to hold data read from the file. 61 | * 62 | * readBufferSize 63 | * Total allocation size of malloc'd buffer. 64 | * 65 | * readBufferOffset 66 | * Represents the position in the readBuffer where we should store new bytes. 67 | * 68 | * readRequestLength 69 | * The total number of bytes that were requested from the connection. 70 | * It's OK if we return a lesser number of bytes to the connection. 71 | * It's NOT OK if we return a greater number of bytes to the connection. 72 | * Doing so would disrupt proper support for range requests. 73 | * If, however, the response is chunked then we don't need to worry about this. 74 | * Chunked responses inheritly don't support range requests. 75 | **/ 76 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPDataResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | 5 | @interface HTTPDataResponse : NSObject 6 | { 7 | NSUInteger offset; 8 | NSData *data; 9 | } 10 | 11 | - (id)initWithData:(NSData *)data; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPDataResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPDataResponse.h" 2 | #import "HTTPLogging.h" 3 | 4 | #if ! __has_feature(objc_arc) 5 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 6 | #endif 7 | 8 | // Log levels : off, error, warn, info, verbose 9 | // Other flags: trace 10 | static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; 11 | 12 | 13 | @implementation HTTPDataResponse 14 | 15 | - (id)initWithData:(NSData *)dataParam 16 | { 17 | if((self = [super init])) 18 | { 19 | HTTPLogTrace(); 20 | 21 | offset = 0; 22 | data = dataParam; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)dealloc 28 | { 29 | HTTPLogTrace(); 30 | 31 | } 32 | 33 | - (UInt64)contentLength 34 | { 35 | UInt64 result = (UInt64)[data length]; 36 | 37 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result); 38 | 39 | return result; 40 | } 41 | 42 | - (UInt64)offset 43 | { 44 | HTTPLogTrace(); 45 | 46 | return offset; 47 | } 48 | 49 | - (void)setOffset:(UInt64)offsetParam 50 | { 51 | HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset); 52 | 53 | offset = (NSUInteger)offsetParam; 54 | } 55 | 56 | - (NSData *)readDataOfLength:(NSUInteger)lengthParameter 57 | { 58 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter); 59 | 60 | NSUInteger remaining = [data length] - offset; 61 | NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining; 62 | 63 | void *bytes = (void *)([data bytes] + offset); 64 | 65 | offset += length; 66 | 67 | return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO]; 68 | } 69 | 70 | - (BOOL)isDone 71 | { 72 | BOOL result = (offset == [data length]); 73 | 74 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 75 | 76 | return result; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPDynamicFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | #import "HTTPAsyncFileResponse.h" 4 | 5 | /** 6 | * This class is designed to assist with dynamic content. 7 | * Imagine you have a file that you want to make dynamic: 8 | * 9 | * 10 | * 11 | *

ComputerName Control Panel

12 | * ... 13 | *
  • System Time: SysTime
  • 14 | * 15 | * 16 | * 17 | * Now you could generate the entire file in Objective-C, 18 | * but this would be a horribly tedious process. 19 | * Beside, you want to design the file with professional tools to make it look pretty. 20 | * 21 | * So all you have to do is escape your dynamic content like this: 22 | * 23 | * ... 24 | *

    %%ComputerName%% Control Panel

    25 | * ... 26 | *
  • System Time: %%SysTime%%
  • 27 | * 28 | * And then you create an instance of this class with: 29 | * 30 | * - separator = @"%%" 31 | * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" } 32 | * 33 | * This class will then perform the replacements for you, on the fly, as it reads the file data. 34 | * This class is also asynchronous, so it will perform the file IO using its own GCD queue. 35 | * 36 | * All keys for the replacementDictionary must be NSString's. 37 | * Values for the replacementDictionary may be NSString's, or any object that 38 | * returns what you want when its description method is invoked. 39 | **/ 40 | 41 | @interface HTTPDynamicFileResponse : HTTPAsyncFileResponse 42 | { 43 | NSData *separator; 44 | NSDictionary *replacementDict; 45 | } 46 | 47 | - (id)initWithFilePath:(NSString *)filePath 48 | forConnection:(HTTPConnection *)connection 49 | separator:(NSString *)separatorStr 50 | replacementDictionary:(NSDictionary *)dictionary; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPErrorResponse.h: -------------------------------------------------------------------------------- 1 | #import "HTTPResponse.h" 2 | 3 | @interface HTTPErrorResponse : NSObject { 4 | NSInteger _status; 5 | } 6 | 7 | - (id)initWithErrorCode:(int)httpErrorCode; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPErrorResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPErrorResponse.h" 2 | 3 | @implementation HTTPErrorResponse 4 | 5 | -(id)initWithErrorCode:(int)httpErrorCode 6 | { 7 | if ((self = [super init])) 8 | { 9 | _status = httpErrorCode; 10 | } 11 | 12 | return self; 13 | } 14 | 15 | - (UInt64) contentLength { 16 | return 0; 17 | } 18 | 19 | - (UInt64) offset { 20 | return 0; 21 | } 22 | 23 | - (void)setOffset:(UInt64)offset { 24 | ; 25 | } 26 | 27 | - (NSData*) readDataOfLength:(NSUInteger)length { 28 | return nil; 29 | } 30 | 31 | - (BOOL) isDone { 32 | return YES; 33 | } 34 | 35 | - (NSInteger) status { 36 | return _status; 37 | } 38 | @end 39 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPFileResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | @class HTTPConnection; 5 | 6 | 7 | @interface HTTPFileResponse : NSObject 8 | { 9 | HTTPConnection *connection; 10 | 11 | NSString *filePath; 12 | UInt64 fileLength; 13 | UInt64 fileOffset; 14 | 15 | BOOL aborted; 16 | 17 | int fileFD; 18 | void *buffer; 19 | NSUInteger bufferSize; 20 | } 21 | 22 | - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection; 23 | - (NSString *)filePath; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPFileResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPFileResponse.h" 2 | #import "HTTPConnection.h" 3 | #import "HTTPLogging.h" 4 | 5 | #import 6 | #import 7 | 8 | #if ! __has_feature(objc_arc) 9 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 10 | #endif 11 | 12 | // Log levels : off, error, warn, info, verbose 13 | // Other flags: trace 14 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE; 15 | 16 | #define NULL_FD -1 17 | 18 | 19 | @implementation HTTPFileResponse 20 | 21 | - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent 22 | { 23 | if((self = [super init])) 24 | { 25 | HTTPLogTrace(); 26 | 27 | connection = parent; // Parents retain children, children do NOT retain parents 28 | 29 | fileFD = NULL_FD; 30 | filePath = [[fpath copy] stringByResolvingSymlinksInPath]; 31 | if (filePath == nil) 32 | { 33 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE); 34 | 35 | return nil; 36 | } 37 | 38 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil]; 39 | if (fileAttributes == nil) 40 | { 41 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath); 42 | 43 | return nil; 44 | } 45 | 46 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue]; 47 | fileOffset = 0; 48 | 49 | aborted = NO; 50 | 51 | // We don't bother opening the file here. 52 | // If this is a HEAD request we only need to know the fileLength. 53 | } 54 | return self; 55 | } 56 | 57 | - (void)abort 58 | { 59 | HTTPLogTrace(); 60 | 61 | [connection responseDidAbort:self]; 62 | aborted = YES; 63 | } 64 | 65 | - (BOOL)openFile 66 | { 67 | HTTPLogTrace(); 68 | 69 | fileFD = open([filePath UTF8String], O_RDONLY); 70 | if (fileFD == NULL_FD) 71 | { 72 | HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath); 73 | 74 | [self abort]; 75 | return NO; 76 | } 77 | 78 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath); 79 | 80 | return YES; 81 | } 82 | 83 | - (BOOL)openFileIfNeeded 84 | { 85 | if (aborted) 86 | { 87 | // The file operation has been aborted. 88 | // This could be because we failed to open the file, 89 | // or the reading process failed. 90 | return NO; 91 | } 92 | 93 | if (fileFD != NULL_FD) 94 | { 95 | // File has already been opened. 96 | return YES; 97 | } 98 | 99 | return [self openFile]; 100 | } 101 | 102 | - (UInt64)contentLength 103 | { 104 | HTTPLogTrace(); 105 | 106 | return fileLength; 107 | } 108 | 109 | - (UInt64)offset 110 | { 111 | HTTPLogTrace(); 112 | 113 | return fileOffset; 114 | } 115 | 116 | - (void)setOffset:(UInt64)offset 117 | { 118 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset); 119 | 120 | if (![self openFileIfNeeded]) 121 | { 122 | // File opening failed, 123 | // or response has been aborted due to another error. 124 | return; 125 | } 126 | 127 | fileOffset = offset; 128 | 129 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET); 130 | if (result == -1) 131 | { 132 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath); 133 | 134 | [self abort]; 135 | } 136 | } 137 | 138 | - (NSData *)readDataOfLength:(NSUInteger)length 139 | { 140 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length); 141 | 142 | if (![self openFileIfNeeded]) 143 | { 144 | // File opening failed, 145 | // or response has been aborted due to another error. 146 | return nil; 147 | } 148 | 149 | // Determine how much data we should read. 150 | // 151 | // It is OK if we ask to read more bytes than exist in the file. 152 | // It is NOT OK to over-allocate the buffer. 153 | 154 | UInt64 bytesLeftInFile = fileLength - fileOffset; 155 | 156 | NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile); 157 | 158 | // Make sure buffer is big enough for read request. 159 | // Do not over-allocate. 160 | 161 | if (buffer == NULL || bufferSize < bytesToRead) 162 | { 163 | bufferSize = bytesToRead; 164 | buffer = reallocf(buffer, (size_t)bufferSize); 165 | 166 | if (buffer == NULL) 167 | { 168 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self); 169 | 170 | [self abort]; 171 | return nil; 172 | } 173 | } 174 | 175 | // Perform the read 176 | 177 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead); 178 | 179 | ssize_t result = read(fileFD, buffer, bytesToRead); 180 | 181 | // Check the results 182 | 183 | if (result < 0) 184 | { 185 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath); 186 | 187 | [self abort]; 188 | return nil; 189 | } 190 | else if (result == 0) 191 | { 192 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath); 193 | 194 | [self abort]; 195 | return nil; 196 | } 197 | else // (result > 0) 198 | { 199 | HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result); 200 | 201 | fileOffset += result; 202 | 203 | return [NSData dataWithBytes:buffer length:result]; 204 | } 205 | } 206 | 207 | - (BOOL)isDone 208 | { 209 | BOOL result = (fileOffset == fileLength); 210 | 211 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO")); 212 | 213 | return result; 214 | } 215 | 216 | - (NSString *)filePath 217 | { 218 | return filePath; 219 | } 220 | 221 | - (void)dealloc 222 | { 223 | HTTPLogTrace(); 224 | 225 | if (fileFD != NULL_FD) 226 | { 227 | HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD); 228 | 229 | close(fileFD); 230 | } 231 | 232 | if (buffer) 233 | free(buffer); 234 | 235 | } 236 | 237 | @end 238 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPRedirectResponse.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "HTTPResponse.h" 3 | 4 | 5 | @interface HTTPRedirectResponse : NSObject 6 | { 7 | NSString *redirectPath; 8 | } 9 | 10 | - (id)initWithPath:(NSString *)redirectPath; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/Responses/HTTPRedirectResponse.m: -------------------------------------------------------------------------------- 1 | #import "HTTPRedirectResponse.h" 2 | #import "HTTPLogging.h" 3 | 4 | #if ! __has_feature(objc_arc) 5 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 6 | #endif 7 | 8 | // Log levels : off, error, warn, info, verbose 9 | // Other flags: trace 10 | static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE; 11 | 12 | 13 | @implementation HTTPRedirectResponse 14 | 15 | - (id)initWithPath:(NSString *)path 16 | { 17 | if ((self = [super init])) 18 | { 19 | HTTPLogTrace(); 20 | 21 | redirectPath = [path copy]; 22 | } 23 | return self; 24 | } 25 | 26 | - (UInt64)contentLength 27 | { 28 | return 0; 29 | } 30 | 31 | - (UInt64)offset 32 | { 33 | return 0; 34 | } 35 | 36 | - (void)setOffset:(UInt64)offset 37 | { 38 | // Nothing to do 39 | } 40 | 41 | - (NSData *)readDataOfLength:(NSUInteger)length 42 | { 43 | HTTPLogTrace(); 44 | 45 | return nil; 46 | } 47 | 48 | - (BOOL)isDone 49 | { 50 | return YES; 51 | } 52 | 53 | - (NSDictionary *)httpHeaders 54 | { 55 | HTTPLogTrace(); 56 | 57 | return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"]; 58 | } 59 | 60 | - (NSInteger)status 61 | { 62 | HTTPLogTrace(); 63 | 64 | return 302; 65 | } 66 | 67 | - (void)dealloc 68 | { 69 | HTTPLogTrace(); 70 | 71 | } 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /KTVHTTPCache/CocoaHTTPServer/WebSocket.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class HTTPMessage; 4 | @class GCDAsyncSocket; 5 | 6 | 7 | #define WebSocketDidDieNotification @"WebSocketDidDie" 8 | 9 | @interface WebSocket : NSObject 10 | { 11 | dispatch_queue_t websocketQueue; 12 | 13 | HTTPMessage *request; 14 | GCDAsyncSocket *asyncSocket; 15 | 16 | NSData *term; 17 | 18 | BOOL isStarted; 19 | BOOL isOpen; 20 | BOOL isVersion76; 21 | 22 | id __unsafe_unretained delegate; 23 | } 24 | 25 | + (BOOL)isWebSocketRequest:(HTTPMessage *)request; 26 | 27 | - (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket; 28 | 29 | /** 30 | * Delegate option. 31 | * 32 | * In most cases it will be easier to subclass WebSocket, 33 | * but some circumstances may lead one to prefer standard delegate callbacks instead. 34 | **/ 35 | @property (/* atomic */ unsafe_unretained) id delegate; 36 | 37 | /** 38 | * The WebSocket class is thread-safe, generally via it's GCD queue. 39 | * All public API methods are thread-safe, 40 | * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue. 41 | **/ 42 | @property (nonatomic, readonly) dispatch_queue_t websocketQueue; 43 | 44 | /** 45 | * Public API 46 | * 47 | * These methods are automatically called by the HTTPServer. 48 | * You may invoke the stop method yourself to close the WebSocket manually. 49 | **/ 50 | - (void)start; 51 | - (void)stop; 52 | 53 | /** 54 | * Public API 55 | * 56 | * Sends a message over the WebSocket. 57 | * This method is thread-safe. 58 | **/ 59 | - (void)sendMessage:(NSString *)msg; 60 | 61 | /** 62 | * Public API 63 | * 64 | * Sends a message over the WebSocket. 65 | * This method is thread-safe. 66 | **/ 67 | - (void)sendData:(NSData *)msg; 68 | 69 | /** 70 | * Subclass API 71 | * 72 | * These methods are designed to be overriden by subclasses. 73 | **/ 74 | - (void)didOpen; 75 | - (void)didReceiveMessage:(NSString *)msg; 76 | - (void)didClose; 77 | 78 | @end 79 | 80 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 81 | #pragma mark - 82 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 83 | 84 | /** 85 | * There are two ways to create your own custom WebSocket: 86 | * 87 | * - Subclass it and override the methods you're interested in. 88 | * - Use traditional delegate paradigm along with your own custom class. 89 | * 90 | * They both exist to allow for maximum flexibility. 91 | * In most cases it will be easier to subclass WebSocket. 92 | * However some circumstances may lead one to prefer standard delegate callbacks instead. 93 | * One such example, you're already subclassing another class, so subclassing WebSocket isn't an option. 94 | **/ 95 | 96 | @protocol WebSocketDelegate 97 | @optional 98 | 99 | - (void)webSocketDidOpen:(WebSocket *)ws; 100 | 101 | - (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg; 102 | 103 | - (void)webSocketDidClose:(WebSocket *)ws; 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /KTVHTTPCache/KTVHTTPCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // KTVHTTPCache.m 3 | // KTVHTTPCache 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "KTVHTTPCache.h" 10 | #import "KTVHCDataStorage.h" 11 | #import "KTVHCHTTPServer.h" 12 | #import "KTVHCDownload.h" 13 | #import "KTVHCURLTool.h" 14 | #import "KTVHCLog.h" 15 | 16 | @implementation KTVHTTPCache 17 | 18 | #pragma mark - HTTP Server 19 | 20 | + (BOOL)proxyStart:(NSError **)error 21 | { 22 | return [[KTVHCHTTPServer server] start:error]; 23 | } 24 | 25 | + (void)proxyStop 26 | { 27 | [[KTVHCHTTPServer server] stop]; 28 | } 29 | 30 | + (void)proxySetPort:(UInt16)port 31 | { 32 | [[KTVHCHTTPServer server] setPort:port]; 33 | } 34 | 35 | + (BOOL)proxyIsRunning 36 | { 37 | return [KTVHCHTTPServer server].isRunning; 38 | } 39 | 40 | + (NSURL *)proxyURLWithOriginalURL:(NSURL *)URL 41 | { 42 | return [[KTVHCHTTPServer server] URLWithOriginalURL:URL]; 43 | } 44 | 45 | + (NSURL *)proxyURLWithOriginalURL:(NSURL *)URL bindToLocalhost:(BOOL)bindToLocalhost 46 | { 47 | return [[KTVHCHTTPServer server] URLWithOriginalURL:URL bindToLocalhost:bindToLocalhost]; 48 | } 49 | 50 | #pragma mark - Data Storage 51 | 52 | + (NSURL *)cacheCompleteFileURLWithURL:(NSURL *)URL 53 | { 54 | return [[KTVHCDataStorage storage] completeFileURLWithURL:URL]; 55 | } 56 | 57 | + (KTVHCDataReader *)cacheReaderWithRequest:(KTVHCDataRequest *)request 58 | { 59 | return [[KTVHCDataStorage storage] readerWithRequest:request]; 60 | } 61 | 62 | + (KTVHCDataLoader *)cacheLoaderWithRequest:(KTVHCDataRequest *)request 63 | { 64 | return [[KTVHCDataStorage storage] loaderWithRequest:request]; 65 | } 66 | 67 | + (void)cacheSetMaxCacheLength:(long long)maxCacheLength 68 | { 69 | [KTVHCDataStorage storage].maxCacheLength = maxCacheLength; 70 | } 71 | 72 | + (long long)cacheMaxCacheLength 73 | { 74 | return [KTVHCDataStorage storage].maxCacheLength; 75 | } 76 | 77 | + (long long)cacheTotalCacheLength 78 | { 79 | return [KTVHCDataStorage storage].totalCacheLength; 80 | } 81 | 82 | + (KTVHCDataCacheItem *)cacheCacheItemWithURL:(NSURL *)URL 83 | { 84 | return [[KTVHCDataStorage storage] cacheItemWithURL:URL]; 85 | } 86 | 87 | + (NSArray *)cacheAllCacheItems 88 | { 89 | return [[KTVHCDataStorage storage] allCacheItems]; 90 | } 91 | 92 | + (void)cacheDeleteCacheWithURL:(NSURL *)URL 93 | { 94 | [[KTVHCDataStorage storage] deleteCacheWithURL:URL]; 95 | } 96 | 97 | + (void)cacheDeleteAllCaches 98 | { 99 | [[KTVHCDataStorage storage] deleteAllCaches]; 100 | } 101 | 102 | #pragma mark - Encode 103 | 104 | + (void)encodeSetURLConverter:(NSURL * (^)(NSURL *URL))URLConverter; 105 | { 106 | [KTVHCURLTool tool].URLConverter = URLConverter; 107 | } 108 | 109 | #pragma mark - Download 110 | 111 | + (void)downloadSetTimeoutInterval:(NSTimeInterval)timeoutInterval 112 | { 113 | [KTVHCDownload download].timeoutInterval = timeoutInterval; 114 | } 115 | 116 | + (NSTimeInterval)downloadTimeoutInterval 117 | { 118 | return [KTVHCDownload download].timeoutInterval; 119 | } 120 | 121 | + (void)downloadSetWhitelistHeaderKeys:(NSArray *)whitelistHeaderKeys 122 | { 123 | [KTVHCDownload download].whitelistHeaderKeys = whitelistHeaderKeys; 124 | } 125 | 126 | + (NSArray *)downloadWhitelistHeaderKeys 127 | { 128 | return [KTVHCDownload download].whitelistHeaderKeys; 129 | } 130 | 131 | + (void)downloadSetAdditionalHeaders:(NSDictionary *)additionalHeaders 132 | { 133 | [KTVHCDownload download].additionalHeaders = additionalHeaders; 134 | } 135 | 136 | + (NSDictionary *)downloadAdditionalHeaders 137 | { 138 | return [KTVHCDownload download].additionalHeaders; 139 | } 140 | 141 | + (void)downloadSetAcceptableContentTypes:(NSArray *)acceptableContentTypes 142 | { 143 | [KTVHCDownload download].acceptableContentTypes = acceptableContentTypes; 144 | } 145 | 146 | + (NSArray *)downloadAcceptableContentTypes 147 | { 148 | return [KTVHCDownload download].acceptableContentTypes; 149 | } 150 | 151 | + (void)downloadSetUnacceptableContentTypeDisposer:(BOOL(^)(NSURL *URL, NSString *contentType))unacceptableContentTypeDisposer 152 | { 153 | [KTVHCDownload download].unacceptableContentTypeDisposer = unacceptableContentTypeDisposer; 154 | } 155 | 156 | #pragma mark - Log 157 | 158 | + (void)logAddLog:(NSString *)log 159 | { 160 | if (log.length > 0) { 161 | KTVHCLogCommon(@"%@", log); 162 | } 163 | } 164 | 165 | + (void)logSetConsoleLogEnable:(BOOL)consoleLogEnable 166 | { 167 | [KTVHCLog log].consoleLogEnable = consoleLogEnable; 168 | } 169 | 170 | + (BOOL)logConsoleLogEnable 171 | { 172 | return [KTVHCLog log].consoleLogEnable; 173 | } 174 | 175 | + (BOOL)logRecordLogEnable 176 | { 177 | return [KTVHCLog log].recordLogEnable; 178 | } 179 | 180 | + (NSURL *)logRecordLogFileURL 181 | { 182 | return [KTVHCLog log].recordLogFileURL; 183 | } 184 | 185 | + (void)logSetRecordLogEnable:(BOOL)recordLogEnable 186 | { 187 | [KTVHCLog log].recordLogEnable = recordLogEnable; 188 | } 189 | 190 | + (void)logDeleteRecordLogFile 191 | { 192 | [[KTVHCLog log] deleteRecordLogFile]; 193 | } 194 | 195 | + (NSDictionary *)logErrors 196 | { 197 | return [[KTVHCLog log] errors]; 198 | } 199 | 200 | + (void)logCleanErrorForURL:(NSURL *)URL 201 | { 202 | [[KTVHCLog log] cleanErrorForURL:URL]; 203 | } 204 | 205 | + (NSError *)logErrorForURL:(NSURL *)URL 206 | { 207 | return [[KTVHCLog log] errorForURL:URL]; 208 | } 209 | 210 | @end 211 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Single 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # KTVHTTPCache 2 | 3 | ![Cocoapods Version](https://img.shields.io/cocoapods/v/KTVHTTPCache) ![Cocoapods License](https://img.shields.io/cocoapods/l/KTVHTTPCache?color=red) ![Cocoapods platforms](https://img.shields.io/cocoapods/p/KTVHTTPCache?color=green) 4 | 5 | KTVHTTPCache 是一个强大的 HTTP 缓存框架,非常适合用于多媒体资源的缓存。 6 | 7 | - [English](https://github.com/ChangbaDevs/KTVHTTPCache/blob/master/README.md) 8 | 9 | 10 | ## Flow Chart 11 | 12 | ![KTVHTTPCache Flow Chart](https://github.com/ChangbaDevs/KTVHTTPCache/blob/master/documents/flow-chart.jpg?raw=true) 13 | 14 | 15 | ## Features 16 | 17 | - 边播放、边下载、边缓存 18 | - 支持预加载 19 | - 支持 HLS 20 | - 支持 AirPlay 21 | - 支持 URL 映射 22 | - 多并发、线程安全 23 | - 强大的日志系统 24 | - 缓存文件管理 25 | - 低耦合、高拓展性 26 | 27 | 28 | ## Requirements 29 | 30 | - iOS 12.0 or later 31 | - Xcode 11.0 or later 32 | 33 | 34 | ## Supported Formats 35 | 36 | 支持全部基于 HTTP 协议传输的资源: 37 | 38 | - 视频格式:MP4,TS,MOV,MXF,MPG,FLV,WMV,AVI,M4V,F4V,MPEG,3GP,ASF,MKV 等 39 | - 音频格式:MP3,OGG,WAV,WMA,APE,FLAC,AAC,AC3,MMF,AMR,M4A,M4R,WV,MP2 等 40 | - Support Apple HTTP Live Streaming (HLS) m3uu8 index files. 41 | 42 | 43 | ## Installation 44 | 45 | #### Installation with CocoaPods 46 | 47 | To integrate KTVHTTPCache into your Xcode project using CocoaPods, specify it in your Podfile: 48 | 49 | ```objc 50 | pod 'KTVHTTPCache', '~> 3.0.0' 51 | ``` 52 | 53 | Run `pod install` 54 | 55 | #### Installation with Carthage 56 | 57 | To integrate KTVHTTPCache into your Xcode project using Carthage, specify it in your Cartfile: 58 | 59 | ```objc 60 | github "ChangbaDevs/KTVHTTPCache" ~> 3.0.0 61 | ``` 62 | 63 | Run `carthage update` to build the framework and drag the built `KTVHTTPCache.framework` and `KTVCocoaHTTPServer.framework` into your Xcode project. 64 | 65 | 66 | ## 设计原则 67 | 68 | KTVHTTPCache 最初是为了实现多媒体资源在线播放过程中边播放边下载边缓存的功能,设计之初便严格遵守如下原则: 69 | 70 | - 简洁高效的 API,与业务侧无耦合 71 | - 网络使用最小化,切片存储,已加载过的数据片段不再请求网络 72 | - 播放过程中可随意 Seek,且已播放过的部分不再请求网络 73 | - 缓存全局共享,同一 URL 已观播放的部分下载时不再请求网络 74 | - 与播放器无相关性,支持 AVPlayer 或其他基于 FFmpeg 的播放器 75 | - 完备的日志系统,问题全程可排查、可追溯 76 | 77 | 该框架本质是对 HTTP 请求进行缓存,对传输内容并没有限制,因此应用场景不限于音视频在线播放,也可以用于文件下载、图片加载、普通网络请求等场景。 78 | 79 | ## 缓存策略 80 | 81 | 以网络使用最小化为原则,采用分片加载数据的方式。有 Network Source 和 File Source 两种数据源,分别用于下载网络数据和读取本地数据。通过对比 Request Header 中 Range 和本地缓存状态动态生成数据加载策略。 82 | 例如 Request Header 中 Range 为 0-1000,本地缓存中已有 200-500 和 700-800 两段数据。那么会对应生成 5 个 Source,分别是: 83 | 84 | - Network Source: 0-199 85 | - File Source : 200-500 86 | - Network Source: 501-699 87 | - File Source : 700-800 88 | - Network Source: 801-1000 89 | 90 | 它们由 Source Manager 进行管理,对外仅提供一个简洁的读取接口。 91 | 92 | 93 | ## 使用 94 | 95 | #### 搭配 AVPlayer 使用 96 | 97 | - 以下为搭配 AVPlayer 的使用示例。实际项目中不限于 AVPlayer,同样可与其他基于 FFmpeg 的播放器搭配使用 98 | 99 | ```objc 100 | // 1.启动本地代理服务器 101 | [KTVHTTPCache proxyStart:&error]; 102 | 103 | // 2.生成代理 URL 104 | NSURL *proxyURL = [KTVHTTPCache proxyURLWithOriginalURL:originalURL]; 105 | 106 | // 3.通过代理 URL 创建 AVPlayer 107 | AVPlayer *player = [AVPlayer playerWithURL:proxyURL]; 108 | ``` 109 | 110 | #### 预加载 111 | 112 | - 使用 Data Loader 并通过其 delegate 实时获取预加载状态 113 | 114 | ```objc 115 | // 可通过 Request Header 中 Range 参数控制预加载范围 116 | KTVHCDataRequest *request= [[KTVHCDataRequest alloc] initWithURL:URL headers:headers]; 117 | KTVHCDataLoader *loader = [KTVHTTPCache cacheLoaderWithRequest:request]; 118 | loader.delegate = self; 119 | [loader prepare]; 120 | ``` 121 | 122 | #### 激活 AirPlay 123 | 124 | - 出于稳定性的考虑,Local Server 默认仅接受来自 localhost 的请求,这导致 AirPlay 默认未激活,使用如下 API 可进行更改 125 | 126 | ```objc 127 | // 将 bindToLocalhost 设置为 NO 以激活 AirPlay 128 | NSURL *proxyURL = [KTVHTTPCache proxyURLWithOriginalURL:originalURL bindToLocalhost:NO]; 129 | ``` 130 | 131 | #### URL 映射 132 | 133 | - 如果指向同一资源的 URL 为动态变化的,可使用如下 API 进行映射 134 | 135 | ```objc 136 | /** 137 | * 例如: 138 | * http://www.xxxx.com/video.mp4?token=1 139 | * 和 140 | * http://www.xxxx.com/video.mp4?token=2 141 | * 虽 URL 不同,但都指向同一文件,即可在 block 中返回 142 | * http://www.xxxx.com/video.mp4 143 | * 以映射到同一块缓存 144 | */ 145 | [KTVHTTPCache encodeSetURLConverter:^NSURL *(NSURL *URL) { 146 | return URL; 147 | }]; 148 | ``` 149 | 150 | #### 网络配置 151 | 152 | ```objc 153 | // 设置超时时间 154 | [KTVHTTPCache downloadSetTimeoutInterval:30]; 155 | 156 | /** 157 | * 出于安全性/稳定性的考虑,默认仅开放 Content-Type 为如下类型的响应 158 | * - text/x 159 | * - video/x 160 | * - audio/x 161 | * - application/x-mpegURL 162 | * - vnd.apple.mpegURL 163 | * - application/mp4 164 | * - application/octet-stream 165 | * - binary/octet-stream 166 | * 如需开放更多类型,使用此 API 设置 167 | */ 168 | [KTVHTTPCache downloadSetAcceptableContentTypes:contentTypes]; 169 | 170 | // 遇到默认未接受的 Content-Type 类型时触发该处置器,可自行判断是否接受 171 | [KTVHTTPCache downloadSetUnacceptableContentTypeDisposer:^BOOL(NSURL *URL, NSString *contentType) { 172 | return NO; 173 | }]; 174 | ``` 175 | 176 | #### 管理缓存数据 177 | 178 | - 默认缓存空间为 500m,用完后启用淘汰机制,逐步淘汰最早的缓存数据 179 | 180 | ```objc 181 | // 如果该 URL 已被完整缓存,则该 URL 被本地服务器释放后,会自动合并为一个完整的文件 182 | NSURL *fileURL= [KTVHTTPCache cacheCompleteFileURLWithURL:originalURL]; 183 | ``` 184 | 185 | #### 日志系统 186 | 187 | ```objc 188 | // 获取指定 URL 的错误信息 189 | NSError *error = [KTVHTTPCache logErrorForURL:URL]; 190 | 191 | // 开启控制台输出日志 192 | [KTVHTTPCache logSetConsoleLogEnable:YES]; 193 | 194 | // 写入日志信息到文件 195 | [KTVHTTPCache logSetRecordLogEnable:YES]; 196 | NSString *logFilePath = [KTVHTTPCache logRecordLogFilePath]; 197 | ``` 198 | 199 | 200 | ## License 201 | 202 | KTVHTTPCache is released under the MIT license. 203 | 204 | 205 | ## Author 206 | 207 | - GitHub : [Single](https://github.com/libobjc) 208 | - Email : libobjc@gmail.com 209 | 210 | 211 | ## Developed by Author 212 | 213 | - [SGPlayer](https://github.com/libobjc/SGPlayer) - A powerful media player framework for iOS, macOS, and tvOS. 214 | - [KTVVideoProcess](https://github.com/ChangbaDevs/KTVVideoProcess) - A High-Performance video effects processing framework. 215 | 216 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo.xcodeproj/xcshareddata/xcschemes/KTVHTTPCacheDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "size" : "1024x1024", 46 | "scale" : "1x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppTransportSecurity 24 | 25 | NSAllowsArbitraryLoads 26 | 27 | 28 | UILaunchStoryboardName 29 | LaunchScreen 30 | UIMainStoryboardFile 31 | Main 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/CacheItemView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheItemView.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class SGCacheItemView; 12 | 13 | @protocol SGCacheItemViewDelegate 14 | 15 | - (void)cacheItemView:(SGCacheItemView *)view deleteButtonDidClick:(NSString *)URLString; 16 | 17 | @end 18 | 19 | @interface SGCacheItemView : UIView 20 | 21 | - (instancetype)initWithURLString:(NSString *)URLString totalLength:(long long)totalLength cacheLength:(long long)cacheLength; 22 | 23 | @property (nonatomic, weak) id delegate; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/SGCacheItemView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheItemView.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class SGCacheItemView; 12 | 13 | @protocol SGCacheItemViewDelegate 14 | 15 | - (void)cacheItemView:(SGCacheItemView *)view deleteButtonDidClick:(NSString *)URLString; 16 | 17 | @end 18 | 19 | @interface SGCacheItemView : UIView 20 | 21 | - (instancetype)initWithURLString:(NSString *)URLString totalLength:(long long)totalLength cacheLength:(long long)cacheLength; 22 | 23 | @property (nonatomic, weak) id delegate; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/SGCacheItemView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheItemView.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGCacheItemView.h" 10 | 11 | @interface SGCacheItemView () 12 | 13 | @property (nonatomic, strong) UILabel *totalLengthLabel; 14 | @property (nonatomic, strong) UILabel *cacheLengthLabel; 15 | @property (nonatomic, strong) UILabel *textLabel; 16 | @property (nonatomic, strong) UIButton *deleteButton; 17 | 18 | @end 19 | 20 | @implementation SGCacheItemView 21 | 22 | - (instancetype)initWithURLString:(NSString *)URLString 23 | totalLength:(long long)totalLength 24 | cacheLength:(long long)cacheLength 25 | { 26 | if (self = [super initWithFrame:CGRectZero]) { 27 | self.backgroundColor = [UIColor whiteColor]; 28 | 29 | self.totalLengthLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 30 | self.totalLengthLabel.textColor = self.totalLengthLabel.tintColor; 31 | self.totalLengthLabel.font = [UIFont systemFontOfSize:14]; 32 | self.totalLengthLabel.text = [NSString stringWithFormat:@"Total Length : %lld", totalLength]; 33 | 34 | self.cacheLengthLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 35 | self.cacheLengthLabel.textColor = self.cacheLengthLabel.tintColor; 36 | self.cacheLengthLabel.font = [UIFont systemFontOfSize:14]; 37 | self.cacheLengthLabel.text = [NSString stringWithFormat:@"Cache Length : %lld", cacheLength]; 38 | 39 | self.textLabel = [[UILabel alloc] initWithFrame:CGRectZero]; 40 | self.textLabel.textColor = self.textLabel.tintColor; 41 | self.textLabel.font = [UIFont systemFontOfSize:14]; 42 | self.textLabel.text = URLString; 43 | self.textLabel.numberOfLines = 0; 44 | 45 | self.deleteButton = [[UIButton alloc] initWithFrame:CGRectZero]; 46 | [self.deleteButton addTarget:self action:@selector(deleteButtonAction) forControlEvents:UIControlEventTouchUpInside]; 47 | [self.deleteButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal]; 48 | [self.deleteButton setTitle:@"Delete" forState:UIControlStateNormal]; 49 | self.deleteButton.titleLabel.font = [UIFont systemFontOfSize:15]; 50 | 51 | [self addSubview:self.totalLengthLabel]; 52 | [self addSubview:self.cacheLengthLabel]; 53 | [self addSubview:self.textLabel]; 54 | [self addSubview:self.deleteButton]; 55 | } 56 | return self; 57 | } 58 | 59 | - (void)layoutSubviews 60 | { 61 | [super layoutSubviews]; 62 | self.totalLengthLabel.frame = CGRectMake(20, 15, self.bounds.size.width - 40, 18); 63 | self.cacheLengthLabel.frame = CGRectMake(20, 33, self.bounds.size.width - 40, 18); 64 | self.textLabel.frame = CGRectMake(20, CGRectGetMaxY(self.cacheLengthLabel.frame) + 5, self.bounds.size.width - 40, self.bounds.size.height - 60); 65 | self.deleteButton.frame = CGRectMake(self.bounds.size.width - 40 - 80, 20, 80, 26); 66 | } 67 | 68 | - (void)deleteButtonAction 69 | { 70 | if ([self.delegate respondsToSelector:@selector(cacheItemView:deleteButtonDidClick:)]) { 71 | [self.delegate cacheItemView:self deleteButtonDidClick:self.textLabel.text]; 72 | } 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/SGCacheItemZoneCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheItemZoneCell.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGCacheItemZoneCell : UITableViewCell 12 | 13 | - (void)configureWithOffset:(long long)offset length:(long long)length; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/SGCacheItemZoneCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheItemZoneCell.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGCacheItemZoneCell.h" 10 | 11 | @interface SGCacheItemZoneCell () 12 | 13 | @property (nonatomic, weak) IBOutlet UILabel *offsetLabel; 14 | @property (nonatomic, weak) IBOutlet UILabel *lengthLabel; 15 | 16 | @end 17 | 18 | @implementation SGCacheItemZoneCell 19 | 20 | - (void)configureWithOffset:(long long)offset length:(long long)length 21 | { 22 | self.offsetLabel.text = [NSString stringWithFormat:@"Offset : %lld", offset]; 23 | self.lengthLabel.text = [NSString stringWithFormat:@"Length : %lld", length]; 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/SGCacheViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheViewController.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGCacheViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGCacheViewController/SGCacheViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGCacheViewController.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGCacheViewController.h" 10 | #import "SGCacheItemZoneCell.h" 11 | #import "SGCacheItemView.h" 12 | 13 | #import 14 | 15 | @interface SGCacheViewController () 16 | 17 | @property (nonatomic, weak) IBOutlet UITableView *tableView; 18 | @property (nonatomic, strong) NSArray *items; 19 | 20 | @end 21 | 22 | @implementation SGCacheViewController 23 | 24 | - (void)viewDidLoad 25 | { 26 | [super viewDidLoad]; 27 | [self setupItems]; 28 | } 29 | 30 | - (void)setupItems 31 | { 32 | self.items = [KTVHTTPCache cacheAllCacheItems]; 33 | [self.tableView reloadData]; 34 | } 35 | 36 | #pragma mark - Table View 37 | 38 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 39 | { 40 | return self.items.count; 41 | } 42 | 43 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 44 | { 45 | return [self.items objectAtIndex:section].zones.count; 46 | } 47 | 48 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 49 | { 50 | KTVHCDataCacheItem *item = [self.items objectAtIndex:indexPath.section]; 51 | KTVHCDataCacheItemZone *zone = [item.zones objectAtIndex:indexPath.row]; 52 | SGCacheItemZoneCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SGCacheItemZoneCell"]; 53 | [cell configureWithOffset:zone.offset length:zone.length]; 54 | return cell; 55 | } 56 | 57 | - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section 58 | { 59 | KTVHCDataCacheItem *item = [self.items objectAtIndex:section]; 60 | SGCacheItemView *view = [[SGCacheItemView alloc] initWithURLString:item.URL.absoluteString totalLength:item.totalLength cacheLength:item.cacheLength]; 61 | view.delegate = self; 62 | return view; 63 | } 64 | 65 | - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 66 | { 67 | return 120; 68 | } 69 | 70 | #pragma mark - SGCacheItemViewDelegate 71 | 72 | - (void)cacheItemView:(SGCacheItemView *)view deleteButtonDidClick:(NSString *)URLString 73 | { 74 | [KTVHTTPCache cacheDeleteCacheWithURL:[NSURL URLWithString:URLString]]; 75 | [self setupItems]; 76 | } 77 | 78 | - (IBAction)deleteAllCache:(UIButton *)sender 79 | { 80 | [KTVHTTPCache cacheDeleteAllCaches]; 81 | [self setupItems]; 82 | } 83 | 84 | @end 85 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGMediaViewController/SGMediaItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGMediaItem.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGMediaItem : NSObject 12 | 13 | + (NSArray *)items; 14 | 15 | @property (nonatomic, copy) NSURL *URL; 16 | @property (nonatomic, copy) NSString *title; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGMediaViewController/SGMediaItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGMediaItem.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGMediaItem.h" 10 | 11 | @implementation SGMediaItem 12 | 13 | + (NSArray *)items 14 | { 15 | return @[ 16 | [[SGMediaItem alloc] initWithURL:[NSURL URLWithString:@"http://aliuwmp3.changba.com/userdata/video/45F6BD5E445E4C029C33DC5901307461.mp4"] 17 | title:@"萧亚轩 - 冲动"], 18 | [[SGMediaItem alloc] initWithURL:[NSURL URLWithString:@"http://aliuwmp3.changba.com/userdata/video/3B1DDE764577E0529C33DC5901307461.mp4"] 19 | title:@"张惠妹 - 你是爱我的 & AirPlay"], 20 | ]; 21 | } 22 | 23 | -(instancetype)initWithURL:(NSURL *)URL title:(NSString *)title 24 | { 25 | if (self = [super init]) { 26 | self.URL = URL; 27 | self.title = title; 28 | } 29 | return self; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGMediaViewController/SGMediaItemCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGMediaItemCell.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGMediaItemCell : UITableViewCell 12 | 13 | - (void)configureWithTitle:(NSString *)title; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGMediaViewController/SGMediaItemCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGMediaItemCell.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/13. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGMediaItemCell.h" 10 | 11 | @interface SGMediaItemCell () 12 | 13 | @property (nonatomic, weak) IBOutlet UILabel *titleLabel; 14 | 15 | @end 16 | 17 | @implementation SGMediaItemCell 18 | 19 | - (void)configureWithTitle:(NSString *)title 20 | { 21 | self.titleLabel.text = title; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGMediaViewController/SGMediaViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGMediaViewController.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGMediaViewController : UIViewController 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGMediaViewController/SGMediaViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGMediaViewController.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGMediaViewController.h" 10 | #import "SGPlayerViewController.h" 11 | #import "SGMediaItemCell.h" 12 | #import "SGMediaItem.h" 13 | 14 | #import 15 | 16 | @interface SGMediaViewController () 17 | 18 | @property (nonatomic, weak) IBOutlet UITableView *tableView; 19 | @property (nonatomic, strong) NSArray *items; 20 | 21 | @end 22 | 23 | @implementation SGMediaViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | static dispatch_once_t onceToken; 29 | dispatch_once(&onceToken, ^{ 30 | [self setupHTTPCache]; 31 | }); 32 | [self setupItems]; 33 | } 34 | 35 | - (void)setupHTTPCache 36 | { 37 | [KTVHTTPCache logSetConsoleLogEnable:YES]; 38 | NSError *error = nil; 39 | [KTVHTTPCache proxyStart:&error]; 40 | if (error) { 41 | NSLog(@"Proxy Start Failure, %@", error); 42 | } else { 43 | NSLog(@"Proxy Start Success"); 44 | } 45 | [KTVHTTPCache encodeSetURLConverter:^NSURL *(NSURL *URL) { 46 | NSLog(@"URL Filter reviced URL : %@", URL); 47 | return URL; 48 | }]; 49 | [KTVHTTPCache downloadSetUnacceptableContentTypeDisposer:^BOOL(NSURL *URL, NSString *contentType) { 50 | NSLog(@"Unsupport Content-Type Filter reviced URL : %@, %@", URL, contentType); 51 | return NO; 52 | }]; 53 | } 54 | 55 | - (void)setupItems 56 | { 57 | self.items = [SGMediaItem items]; 58 | [self.tableView reloadData]; 59 | } 60 | 61 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 62 | { 63 | return 1; 64 | } 65 | 66 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 67 | { 68 | return self.items.count; 69 | } 70 | 71 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 72 | { 73 | SGMediaItem *item = [self.items objectAtIndex:indexPath.row]; 74 | SGMediaItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SGMediaItemCell"]; 75 | [cell configureWithTitle:item.title]; 76 | return cell; 77 | } 78 | 79 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 80 | { 81 | SGMediaItem *item = [self.items objectAtIndex:indexPath.row]; 82 | NSURL *URL = nil; 83 | if ([item.title containsString:@"AirPlay"]) { 84 | URL = [KTVHTTPCache proxyURLWithOriginalURL:item.URL bindToLocalhost:NO]; 85 | } else { 86 | URL = [KTVHTTPCache proxyURLWithOriginalURL:item.URL]; 87 | } 88 | SGPlayerViewController *vc = [[SGPlayerViewController alloc] initWithURL:URL]; 89 | [self presentViewController:vc animated:YES completion:nil]; 90 | } 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGPlayerViewController/SGPlayerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGPlayerViewController.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/14. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGPlayerViewController : AVPlayerViewController 12 | 13 | - (instancetype)initWithURL:(NSURL *)URL; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGPlayerViewController/SGPlayerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGPlayerViewController.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/14. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import "SGPlayerViewController.h" 10 | #import 11 | 12 | @interface SGPlayerViewController () 13 | 14 | @property (nonatomic, copy) NSURL *URL; 15 | 16 | @end 17 | 18 | @implementation SGPlayerViewController 19 | 20 | - (instancetype)initWithURL:(NSURL *)URL 21 | { 22 | if (self = [super init]) { 23 | self.URL = URL; 24 | } 25 | return self; 26 | } 27 | 28 | - (void)viewDidLoad 29 | { 30 | [super viewDidLoad]; 31 | dispatch_async(dispatch_get_main_queue(), ^{ 32 | self.player = [AVPlayer playerWithURL:self.URL]; 33 | [self.player play]; 34 | }); 35 | } 36 | 37 | - (void)dealloc 38 | { 39 | [self.player.currentItem.asset cancelLoading]; 40 | [self.player.currentItem cancelPendingSeeks]; 41 | [self.player cancelPendingPrerolls]; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGPreloadViewController/SGPreloadItemCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGPreloadItemCell.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by WBY888666 on 2024/1/12. 6 | // Copyright © 2024 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGPreloadItemCell : UITableViewCell 12 | 13 | - (void)configureWithTitle:(NSString *)title progress:(double)progress; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGPreloadViewController/SGPreloadItemCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGPreloadItemCell.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by WBY888666 on 2024/1/12. 6 | // Copyright © 2024 Single. All rights reserved. 7 | // 8 | 9 | #import "SGPreloadItemCell.h" 10 | 11 | @interface SGPreloadItemCell () 12 | 13 | @property (nonatomic, weak) IBOutlet UILabel *titleLabel; 14 | @property (nonatomic, weak) IBOutlet UILabel *progressLable; 15 | 16 | @end 17 | 18 | 19 | @implementation SGPreloadItemCell 20 | 21 | - (void)configureWithTitle:(NSString *)title progress:(double)progress 22 | { 23 | self.titleLabel.text = title; 24 | self.progressLable.text = [NSString stringWithFormat:@"%ld%%", (NSInteger)(progress * 100)]; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGPreloadViewController/SGPreloadViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGPreloadViewController.h 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2024/1/12. 6 | // Copyright © 2024 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGPreloadViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/SGPreloadViewController/SGPreloadViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGPreloadViewController.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2024/1/12. 6 | // Copyright © 2024 Single. All rights reserved. 7 | // 8 | 9 | #import "SGPreloadViewController.h" 10 | #import "SGPreloadItemCell.h" 11 | #import "SGMediaItem.h" 12 | 13 | #import 14 | 15 | @interface SGPreloadViewController () 16 | 17 | @property (weak, nonatomic) IBOutlet UITableView *tableVIew; 18 | @property (nonatomic, strong) NSArray *loaders; 19 | 20 | @end 21 | 22 | @implementation SGPreloadViewController 23 | 24 | - (void)viewDidLoad 25 | { 26 | [super viewDidLoad]; 27 | 28 | NSArray *items = [SGMediaItem items]; 29 | NSMutableArray *loaders = [NSMutableArray array]; 30 | for (SGMediaItem *obj in items) { 31 | if ([obj.URL.absoluteString containsString:@".m3u"]) { 32 | NSLog(@"HLS files do not support preloading using KTVHCDataLoader"); 33 | continue; 34 | } 35 | NSDictionary *headers = @{ 36 | // Set preload length if needed. 37 | // @"Range" : @"bytes=0-1" 38 | }; 39 | KTVHCDataRequest *request = [[KTVHCDataRequest alloc] initWithURL:obj.URL headers:headers]; 40 | KTVHCDataLoader *loader = [KTVHTTPCache cacheLoaderWithRequest:request]; 41 | loader.object = obj; 42 | loader.delegate = self; 43 | [loaders addObject:loader]; 44 | } 45 | self.loaders = loaders; 46 | [self.loaders.firstObject prepare]; 47 | } 48 | 49 | - (void)dealloc 50 | { 51 | for (KTVHCDataLoader *obj in self.loaders) { 52 | [obj close]; 53 | } 54 | } 55 | 56 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 57 | { 58 | return 1; 59 | } 60 | 61 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 62 | { 63 | return self.loaders.count; 64 | } 65 | 66 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 67 | { 68 | KTVHCDataLoader *loader = [self.loaders objectAtIndex:indexPath.row]; 69 | SGMediaItem *item = (SGMediaItem *)loader.object; 70 | SGPreloadItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"SGPreloadItemCell"]; 71 | [cell configureWithTitle:item.title progress:loader.progress]; 72 | return cell; 73 | } 74 | 75 | - (void)ktv_loaderDidFinish:(KTVHCDataLoader *)loader 76 | { 77 | NSUInteger index = [self.loaders indexOfObject:loader] + 1; 78 | if (index < self.loaders.count) { 79 | [[self.loaders objectAtIndex:index] prepare]; 80 | } 81 | } 82 | 83 | - (void)ktv_loader:(KTVHCDataLoader *)loader didFailWithError:(NSError *)error 84 | { 85 | NSLog(@"Preload failed : %@, %@", loader, error); 86 | NSUInteger index = [self.loaders indexOfObject:loader] + 1; 87 | if (index < self.loaders.count) { 88 | [[self.loaders objectAtIndex:index] prepare]; 89 | } 90 | } 91 | 92 | - (void)ktv_loader:(KTVHCDataLoader *)loader didChangeProgress:(double)progress 93 | { 94 | dispatch_async(dispatch_get_main_queue(), ^{ 95 | [self.tableVIew reloadData]; 96 | }); 97 | } 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /demo/KTVHTTPCacheDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // KTVHTTPCacheDemo 4 | // 5 | // Created by Single on 2017/8/10. 6 | // Copyright © 2017年 Single. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /documents/flow-chart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChangbaDevs/KTVHTTPCache/af13f397b9234adb1045fc322a6a5501de5662b3/documents/flow-chart.jpg --------------------------------------------------------------------------------