├── .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 |   
4 |
5 | KTVHTTPCache 是一个强大的 HTTP 缓存框架,非常适合用于多媒体资源的缓存。
6 |
7 | - [English](https://github.com/ChangbaDevs/KTVHTTPCache/blob/master/README.md)
8 |
9 |
10 | ## Flow Chart
11 |
12 | 
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
--------------------------------------------------------------------------------