Moving resumeable streaming download functionality to AFHTTPRequestOperation
This commit is contained in:
parent
7bd7ee609b
commit
331d7bcbf2
@ -44,6 +44,10 @@
|
||||
*/
|
||||
@property (readonly, nonatomic, retain) NSHTTPURLResponse *response;
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
@property (readonly, nonatomic, copy) NSString *responseFilePath;
|
||||
|
||||
///----------------------------------------------------------
|
||||
/// @name Managing And Checking For Acceptable HTTP Responses
|
||||
@ -91,6 +95,23 @@
|
||||
*/
|
||||
+ (BOOL)canProcessRequest:(NSURLRequest *)urlRequest;
|
||||
|
||||
///-------------------------------------------------
|
||||
/// @name Configuring Resumeable Streaming Downloads
|
||||
///-------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
- (void)setOutputStreamDownloadingToFile:(NSString *)path
|
||||
shouldResume:(BOOL)shouldResume;
|
||||
|
||||
/**
|
||||
Deletes the temporary file.
|
||||
|
||||
@return `YES` if the file is successfully removed or did not exist in the first place, otherwise `NO`.
|
||||
*/
|
||||
- (BOOL)deleteTemporaryFileWithError:(NSError **)error;
|
||||
|
||||
///-----------------------------------------------------------
|
||||
/// @name Setting Completion Block Success / Failure Callbacks
|
||||
///-----------------------------------------------------------
|
||||
|
||||
@ -22,6 +22,8 @@
|
||||
|
||||
#import "AFHTTPRequestOperation.h"
|
||||
|
||||
NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete";
|
||||
|
||||
static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
||||
NSMutableString *string = [NSMutableString string];
|
||||
|
||||
@ -52,19 +54,55 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
||||
return string;
|
||||
}
|
||||
|
||||
static unsigned long long AFFileSizeForPath(NSString *path) {
|
||||
unsigned long long fileSize = 0;
|
||||
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
if ([fileManager fileExistsAtPath:path]) {
|
||||
NSError *error = nil;
|
||||
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
|
||||
if (!error && attributes) {
|
||||
fileSize = [attributes fileSize];
|
||||
}
|
||||
}
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
static NSString * AFIncompleteDownloadDirectory() {
|
||||
static NSString *_af_incompleteDownloadDirectory = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
_af_incompleteDownloadDirectory = [[temporaryDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName] retain];
|
||||
|
||||
NSError *error = nil;
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
if(![fileManager createDirectoryAtPath:_af_incompleteDownloadDirectory withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||
NSLog(NSLocalizedString(@"Failed to create incomplete download directory at %@", nil), _af_incompleteDownloadDirectory);
|
||||
}
|
||||
});
|
||||
|
||||
return _af_incompleteDownloadDirectory;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@interface AFHTTPRequestOperation ()
|
||||
@property (readwrite, nonatomic, retain) NSURLRequest *request;
|
||||
@property (readwrite, nonatomic, retain) NSError *HTTPError;
|
||||
@property (readwrite, nonatomic, copy) NSString *responseFilePath;
|
||||
@end
|
||||
|
||||
@implementation AFHTTPRequestOperation
|
||||
@synthesize acceptableStatusCodes = _acceptableStatusCodes;
|
||||
@synthesize acceptableContentTypes = _acceptableContentTypes;
|
||||
@synthesize HTTPError = _HTTPError;
|
||||
@synthesize responseFilePath = _responseFilePath;
|
||||
@synthesize successCallbackQueue = _successCallbackQueue;
|
||||
@synthesize failureCallbackQueue = _failureCallbackQueue;
|
||||
|
||||
@dynamic request;
|
||||
@dynamic response;
|
||||
|
||||
- (id)initWithRequest:(NSURLRequest *)request {
|
||||
self = [super initWithRequest:request];
|
||||
@ -95,10 +133,6 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSHTTPURLResponse *)response {
|
||||
return (NSHTTPURLResponse *)[super response];
|
||||
}
|
||||
|
||||
- (NSError *)error {
|
||||
if (self.response && !self.HTTPError) {
|
||||
if (![self hasAcceptableStatusCode]) {
|
||||
@ -157,6 +191,38 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOutputStreamDownloadingToFile:(NSString *)path
|
||||
shouldResume:(BOOL)shouldResume
|
||||
{
|
||||
BOOL isDirectory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
|
||||
isDirectory = NO;
|
||||
}
|
||||
|
||||
if (isDirectory) {
|
||||
self.responseFilePath = [NSString pathWithComponents:[NSArray arrayWithObjects:path, [[self.request URL] lastPathComponent], nil]];
|
||||
} else {
|
||||
self.responseFilePath = path;
|
||||
}
|
||||
|
||||
NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]];
|
||||
|
||||
if (shouldResume) {
|
||||
unsigned long long downloadedBytes = AFFileSizeForPath(temporaryFilePath);
|
||||
if (downloadedBytes > 0) {
|
||||
NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease];
|
||||
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes] forHTTPHeaderField:@"Range"];
|
||||
self.request = mutableURLRequest;
|
||||
}
|
||||
}
|
||||
|
||||
self.outputStream = [NSOutputStream outputStreamToFileAtPath:temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]];
|
||||
}
|
||||
|
||||
- (BOOL)deleteTemporaryFileWithError:(NSError **)error {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
|
||||
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
|
||||
{
|
||||
@ -187,4 +253,45 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - NSURLConnectionDelegate
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection
|
||||
didReceiveResponse:(NSURLResponse *)response
|
||||
{
|
||||
[super connection:connection didReceiveResponse:response];
|
||||
|
||||
if (![self.response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for valid response to resume the download if possible
|
||||
long long totalContentLength = self.response.expectedContentLength;
|
||||
long long fileOffset = 0;
|
||||
if ([self.response statusCode] == 206) {
|
||||
NSString *contentRange = [[self.response allHeaderFields] valueForKey:@"Content-Range"];
|
||||
if ([contentRange hasPrefix:@"bytes"]) {
|
||||
NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
|
||||
if ([bytes count] == 4) {
|
||||
fileOffset = [[bytes objectAtIndex:1] longLongValue];
|
||||
totalContentLength = [[bytes objectAtIndex:2] longLongValue]; // if this is *, it's converted to 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long long offsetContentLength = MAX(fileOffset, 0);
|
||||
[self.outputStream setProperty:[NSNumber numberWithLongLong:offsetContentLength] forKey:NSStreamFileCurrentOffsetKey];
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
||||
[super connectionDidFinishLoading:connection];
|
||||
|
||||
if (self.responseFilePath) {
|
||||
@synchronized(self) {
|
||||
NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]];
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
[fileManager moveItemAtPath:temporaryFilePath toPath:self.responseFilePath error:&_HTTPError];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@ -142,11 +142,6 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName;
|
||||
*/
|
||||
@property (readonly, nonatomic, copy) NSString *responseString;
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
@property (readonly, nonatomic, copy) NSString *responseFilePath;
|
||||
|
||||
///------------------------
|
||||
/// @name Accessing Streams
|
||||
///------------------------
|
||||
@ -178,23 +173,6 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName;
|
||||
*/
|
||||
- (id)initWithRequest:(NSURLRequest *)urlRequest;
|
||||
|
||||
///-------------------------------------------------
|
||||
/// @name Configuring Resumeable Streaming Downloads
|
||||
///-------------------------------------------------
|
||||
|
||||
/**
|
||||
|
||||
*/
|
||||
- (void)setOutputStreamDownloadingToFile:(NSString *)path
|
||||
shouldResume:(BOOL)shouldResume;
|
||||
|
||||
/**
|
||||
Deletes the temporary file.
|
||||
|
||||
@return `YES` if the file is successfully removed or did not exist in the first place, otherwise `NO`.
|
||||
*/
|
||||
- (BOOL)deleteTemporaryFileWithError:(NSError **)error;
|
||||
|
||||
///---------------------------------
|
||||
/// @name Setting Progress Callbacks
|
||||
///---------------------------------
|
||||
|
||||
@ -37,8 +37,6 @@ NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error";
|
||||
NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start";
|
||||
NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish";
|
||||
|
||||
NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete";
|
||||
|
||||
typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected);
|
||||
typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace);
|
||||
typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge);
|
||||
@ -82,38 +80,6 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long long AFFileSizeForPath(NSString *path) {
|
||||
unsigned long long fileSize = 0;
|
||||
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
if ([fileManager fileExistsAtPath:path]) {
|
||||
NSError *error = nil;
|
||||
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:&error];
|
||||
if (!error && attributes) {
|
||||
fileSize = [attributes fileSize];
|
||||
}
|
||||
}
|
||||
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
static NSString * AFIncompleteDownloadDirectory() {
|
||||
static NSString *_af_incompleteDownloadDirectory = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSString *temporaryDirectory = NSTemporaryDirectory();
|
||||
_af_incompleteDownloadDirectory = [[temporaryDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName] retain];
|
||||
|
||||
NSError *error = nil;
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
if(![fileManager createDirectoryAtPath:_af_incompleteDownloadDirectory withIntermediateDirectories:YES attributes:nil error:&error]) {
|
||||
NSLog(NSLocalizedString(@"Failed to create incomplete download directory at %@", nil), _af_incompleteDownloadDirectory);
|
||||
}
|
||||
});
|
||||
|
||||
return _af_incompleteDownloadDirectory;
|
||||
}
|
||||
|
||||
@interface AFURLConnectionOperation ()
|
||||
@property (readwrite, nonatomic, assign) AFOperationState state;
|
||||
@property (readwrite, nonatomic, retain) NSRecursiveLock *lock;
|
||||
@ -123,7 +89,6 @@ static NSString * AFIncompleteDownloadDirectory() {
|
||||
@property (readwrite, nonatomic, retain) NSError *error;
|
||||
@property (readwrite, nonatomic, retain) NSData *responseData;
|
||||
@property (readwrite, nonatomic, copy) NSString *responseString;
|
||||
@property (readwrite, nonatomic, copy) NSString *responseFilePath;
|
||||
@property (readwrite, nonatomic, assign) long long totalBytesRead;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress;
|
||||
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress;
|
||||
@ -144,7 +109,6 @@ static NSString * AFIncompleteDownloadDirectory() {
|
||||
@synthesize error = _error;
|
||||
@synthesize responseData = _responseData;
|
||||
@synthesize responseString = _responseString;
|
||||
@synthesize responseFilePath = _responseFilePath;
|
||||
@synthesize totalBytesRead = _totalBytesRead;
|
||||
@dynamic inputStream;
|
||||
@synthesize outputStream = _outputStream;
|
||||
@ -262,38 +226,6 @@ static NSString * AFIncompleteDownloadDirectory() {
|
||||
self.request = mutableRequest;
|
||||
}
|
||||
|
||||
- (void)setOutputStreamDownloadingToFile:(NSString *)path
|
||||
shouldResume:(BOOL)shouldResume
|
||||
{
|
||||
BOOL isDirectory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
|
||||
isDirectory = NO;
|
||||
}
|
||||
|
||||
if (isDirectory) {
|
||||
self.responseFilePath = [NSString pathWithComponents:[NSArray arrayWithObjects:path, [[self.request URL] lastPathComponent], nil]];
|
||||
} else {
|
||||
self.responseFilePath = path;
|
||||
}
|
||||
|
||||
NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]];
|
||||
|
||||
if (shouldResume) {
|
||||
unsigned long long downloadedBytes = AFFileSizeForPath(temporaryFilePath);
|
||||
if (downloadedBytes > 0) {
|
||||
NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease];
|
||||
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes] forHTTPHeaderField:@"Range"];
|
||||
self.request = mutableURLRequest;
|
||||
}
|
||||
}
|
||||
|
||||
self.outputStream = [NSOutputStream outputStreamToFileAtPath:temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]];
|
||||
}
|
||||
|
||||
- (BOOL)deleteTemporaryFileWithError:(NSError **)error {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block {
|
||||
self.uploadProgress = block;
|
||||
}
|
||||
@ -512,28 +444,6 @@ didReceiveResponse:(NSURLResponse *)response
|
||||
if (self.outputStream) {
|
||||
[self.outputStream open];
|
||||
}
|
||||
|
||||
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
|
||||
if (![HTTPResponse isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for valid response to resume the download if possible
|
||||
long long totalContentLength = self.response.expectedContentLength;
|
||||
long long fileOffset = 0;
|
||||
if([HTTPResponse statusCode] == 206) {
|
||||
NSString *contentRange = [[HTTPResponse allHeaderFields] valueForKey:@"Content-Range"];
|
||||
if ([contentRange hasPrefix:@"bytes"]) {
|
||||
NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
|
||||
if ([bytes count] == 4) {
|
||||
fileOffset = [[bytes objectAtIndex:1] longLongValue];
|
||||
totalContentLength = [[bytes objectAtIndex:2] longLongValue]; // if this is *, it's converted to 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long long offsetContentLength = MAX(fileOffset, 0);
|
||||
[self.outputStream setProperty:[NSNumber numberWithLongLong:offsetContentLength] forKey:NSStreamFileCurrentOffsetKey];
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)__unused connection
|
||||
@ -551,15 +461,7 @@ didReceiveResponse:(NSURLResponse *)response
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection {
|
||||
if (self.responseFilePath) {
|
||||
@synchronized(self) {
|
||||
NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]];
|
||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||
[fileManager moveItemAtPath:temporaryFilePath toPath:self.responseFilePath error:&_error];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection {
|
||||
self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
|
||||
|
||||
[self.outputStream close];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user