From 5b7a2cb05f1617a9e7da2f4e5a19eeb7588e8a83 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 2 Mar 2012 10:42:24 -0800 Subject: [PATCH 01/41] Using class method swizzling to dynamically modify class method values for acceptable status codes and content types --- AFNetworking/AFHTTPRequestOperation.h | 43 ++++++++------ AFNetworking/AFHTTPRequestOperation.m | 57 ++++++++++++------- AFNetworking/AFImageRequestOperation.m | 9 +-- AFNetworking/AFJSONRequestOperation.m | 16 +----- AFNetworking/AFPropertyListRequestOperation.m | 9 +-- AFNetworking/AFXMLRequestOperation.m | 16 +----- 6 files changed, 74 insertions(+), 76 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 122099e..1c32631 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -28,8 +28,6 @@ */ @interface AFHTTPRequestOperation : AFURLConnectionOperation { @private - NSIndexSet *_acceptableStatusCodes; - NSSet *_acceptableContentTypes; NSError *_HTTPError; dispatch_queue_t _successCallbackQueue; dispatch_queue_t _failureCallbackQueue; @@ -49,25 +47,11 @@ /// @name Managing And Checking For Acceptable HTTP Responses ///---------------------------------------------------------- -/** - Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - - By default, this is the range 200 to 299, inclusive. - */ -@property (nonatomic, retain) NSIndexSet *acceptableStatusCodes; - /** A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`. */ @property (readonly) BOOL hasAcceptableStatusCode; -/** - Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 - - By default, this is `nil`. - */ -@property (nonatomic, retain) NSSet *acceptableContentTypes; - /** A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`. */ @@ -83,6 +67,33 @@ */ @property (nonatomic) dispatch_queue_t failureCallbackQueue; +///------------------------------------------------------------- +/// @name Managing Accceptable HTTP Status Codes & Content Types +///------------------------------------------------------------- + +/** + Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + + By default, this is the range 200 to 299, inclusive. + */ ++ (NSIndexSet *)acceptableStatusCodes; + +/** + + */ ++ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes; + +/** + Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 + + By default, this is `nil`. + */ ++ (NSSet *)acceptableContentTypes; + +/** + + */ ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes; /** A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`. diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 732b854..aa9d612 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -22,6 +22,15 @@ #import "AFHTTPRequestOperation.h" +#import + +static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL selector, void *block) { + Method originalMethod = class_getClassMethod(klass, selector); + IMP implementation = imp_implementationWithBlock(block); + class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) cStringUsingEncoding:NSUTF8StringEncoding]), selector, implementation, method_getTypeEncoding(originalMethod)); + +} + static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { NSMutableString *string = [NSMutableString string]; @@ -59,27 +68,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { @end @implementation AFHTTPRequestOperation -@synthesize acceptableStatusCodes = _acceptableStatusCodes; -@synthesize acceptableContentTypes = _acceptableContentTypes; @synthesize HTTPError = _HTTPError; @synthesize successCallbackQueue = _successCallbackQueue; @synthesize failureCallbackQueue = _failureCallbackQueue; - -- (id)initWithRequest:(NSURLRequest *)request { - self = [super initWithRequest:request]; - if (!self) { - return nil; - } - - self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; - - return self; -} - - (void)dealloc { - [_acceptableStatusCodes release]; - [_acceptableContentTypes release]; [_HTTPError release]; if (_successCallbackQueue) { @@ -103,13 +96,13 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { if (self.response && !self.HTTPError) { if (![self hasAcceptableStatusCode]) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet(self.acceptableStatusCodes), [self.response statusCode]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), [self.response statusCode]] forKey:NSLocalizedDescriptionKey]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease]; } else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), self.acceptableContentTypes, [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease]; @@ -124,11 +117,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { } - (BOOL)hasAcceptableStatusCode { - return !self.acceptableStatusCodes || [self.acceptableStatusCodes containsIndex:[self.response statusCode]]; + return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]]; } - (BOOL)hasAcceptableContentType { - return !self.acceptableContentTypes || [self.acceptableContentTypes containsObject:[self.response MIMEType]]; + return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:[self.response MIMEType]]; } - (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue { @@ -183,6 +176,30 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { #pragma mark - AFHTTPClientOperation ++ (NSIndexSet *)acceptableStatusCodes { + return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; +} + ++ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes { + NSMutableIndexSet *mutableStatusCodes = [[[NSMutableIndexSet alloc] initWithIndexSet:[self acceptableStatusCodes]] autorelease]; + [mutableStatusCodes addIndexes:statusCodes]; + AFSwizzleClassMethodWithClassAndSelectorUsingBlock([self class], @selector(acceptableStatusCodes), ^(id _self) { + return mutableStatusCodes; + }); +} + ++ (NSSet *)acceptableContentTypes { + return nil; +} + ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes { + NSMutableSet *mutableContentTypes = [[[NSMutableSet alloc] initWithSet:[self acceptableContentTypes] copyItems:YES] autorelease]; + [mutableContentTypes unionSet:contentTypes]; + AFSwizzleClassMethodWithClassAndSelectorUsingBlock([self class], @selector(acceptableContentTypes), ^(id _self) { + return mutableContentTypes; + }); +} + + (BOOL)canProcessRequest:(NSURLRequest *)request { return YES; } diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index de8237e..52426b1 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -38,7 +38,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) NSImage *responseImage; #endif -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -126,7 +125,7 @@ static dispatch_queue_t image_request_operation_processing_queue() { } #endif -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; } @@ -139,9 +138,7 @@ static dispatch_queue_t image_request_operation_processing_queue() { if (!self) { return nil; } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - + #if __IPHONE_OS_VERSION_MIN_REQUIRED self.imageScale = [[UIScreen mainScreen] scale]; #endif @@ -193,7 +190,7 @@ static dispatch_queue_t image_request_operation_processing_queue() { #pragma mark - AFHTTPClientOperation + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } #if __IPHONE_OS_VERSION_MIN_REQUIRED diff --git a/AFNetworking/AFJSONRequestOperation.m b/AFNetworking/AFJSONRequestOperation.m index 9046dd0..651f2d7 100644 --- a/AFNetworking/AFJSONRequestOperation.m +++ b/AFNetworking/AFJSONRequestOperation.m @@ -36,7 +36,6 @@ static dispatch_queue_t json_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) id responseJSON; @property (readwrite, nonatomic, retain) NSError *JSONError; -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -62,7 +61,7 @@ static dispatch_queue_t json_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; } @@ -71,18 +70,7 @@ static dispatch_queue_t json_request_operation_processing_queue() { } + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; -} - -- (id)initWithRequest:(NSURLRequest *)urlRequest { - self = [super initWithRequest:urlRequest]; - if (!self) { - return nil; - } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - - return self; + return [[self acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } - (void)dealloc { diff --git a/AFNetworking/AFPropertyListRequestOperation.m b/AFNetworking/AFPropertyListRequestOperation.m index ea9d216..b5609b3 100644 --- a/AFNetworking/AFPropertyListRequestOperation.m +++ b/AFNetworking/AFPropertyListRequestOperation.m @@ -36,7 +36,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; @property (readwrite, nonatomic, retain) NSError *propertyListError; -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -64,7 +63,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"application/x-plist", nil]; } @@ -77,9 +76,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { if (!self) { return nil; } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - + self.propertyListReadOptions = NSPropertyListImmutable; return self; @@ -112,7 +109,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { } + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success diff --git a/AFNetworking/AFXMLRequestOperation.m b/AFNetworking/AFXMLRequestOperation.m index 79c5272..25a3765 100644 --- a/AFNetworking/AFXMLRequestOperation.m +++ b/AFNetworking/AFXMLRequestOperation.m @@ -40,7 +40,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { #endif @property (readwrite, nonatomic, retain) NSError *XMLError; -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -91,7 +90,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() { } #endif -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; } @@ -99,17 +98,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { return [NSSet setWithObjects:@"xml", nil]; } -- (id)initWithRequest:(NSURLRequest *)urlRequest { - self = [super initWithRequest:urlRequest]; - if (!self) { - return nil; - } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - - return self; -} - - (void)dealloc { [_responseXMLParser release]; @@ -159,7 +147,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() { } + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success From eb7d9bef950b1dbc985b5ede052af6ceb29abe7b Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 2 Mar 2012 10:48:06 -0800 Subject: [PATCH 02/41] Adding documentation for AFHTTPRequestOperation +addAcceptableStatusCodes / +addAcceptableContentTypes --- AFNetworking/AFHTTPRequestOperation.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 1c32631..a410743 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -42,7 +42,6 @@ */ @property (readonly, nonatomic, retain) NSHTTPURLResponse *response; - ///---------------------------------------------------------- /// @name Managing And Checking For Acceptable HTTP Responses ///---------------------------------------------------------- @@ -79,7 +78,9 @@ + (NSIndexSet *)acceptableStatusCodes; /** + Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendents. + @param statusCodes The status codes to be added to the set of acceptable HTTP status codes */ + (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes; @@ -91,7 +92,9 @@ + (NSSet *)acceptableContentTypes; /** - + Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendents. + + @param contentTypes The content types to be added to the set of acceptable MIME types */ + (void)addAcceptableContentTypes:(NSSet *)contentTypes; From 01572eed3db5c31c6ac3855d3e4f2d1307889d6d Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 2 Mar 2012 10:49:13 -0800 Subject: [PATCH 03/41] Adding new documentation header for AFHTTPClient +canProcessRequest --- AFNetworking/AFHTTPRequestOperation.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index a410743..e836ce5 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -98,6 +98,11 @@ */ + (void)addAcceptableContentTypes:(NSSet *)contentTypes; + +///----------------------------------------------------- +/// @name Determining Whether A Request Can Be Processed +///----------------------------------------------------- + /** A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`. From 68eb634027c2d5af23c8f8e0a772433cbf709ea2 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 2 Mar 2012 11:18:20 -0800 Subject: [PATCH 04/41] Adding AFContentTypesFromHTTPHeader() --- AFNetworking/AFHTTPRequestOperation.h | 5 +++++ AFNetworking/AFHTTPRequestOperation.m | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index e836ce5..95260f3 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -23,6 +23,11 @@ #import #import "AFURLConnectionOperation.h" +/** + Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header. + */ +extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); + /** `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. */ diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index aa9d612..00e8c67 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -24,6 +24,31 @@ #import +NSSet * AFContentTypesFromHTTPHeader(NSString *string) { + static NSCharacterSet *_skippedCharacterSet = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _skippedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@" ,"]; + }); + + NSScanner *scanner = [NSScanner scannerWithString:string]; + scanner.charactersToBeSkipped = _skippedCharacterSet; + + NSMutableSet *mutableContentTypes = [NSMutableSet set]; + while (![scanner isAtEnd]) { + NSString *contentType = nil; + if ([scanner scanUpToString:@";" intoString:&contentType]) { + [scanner scanUpToString:@"," intoString:nil]; + } + + if (contentType) { + [mutableContentTypes addObject:contentType]; + } + } + + return [NSSet setWithSet:mutableContentTypes]; +} + static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL selector, void *block) { Method originalMethod = class_getClassMethod(klass, selector); IMP implementation = imp_implementationWithBlock(block); From 8bfee9e910e8a6b7990c68bbf358edad236d840f Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sat, 3 Mar 2012 13:19:53 -0800 Subject: [PATCH 05/41] Refactoring +canProcessRequest: and content type acceptability --- AFNetworking/AFHTTPRequestOperation.m | 10 ++++++- AFNetworking/AFImageRequestOperation.m | 27 ++++++++++--------- AFNetworking/AFJSONRequestOperation.m | 24 +++++++---------- AFNetworking/AFPropertyListRequestOperation.m | 18 +++++-------- AFNetworking/AFXMLRequestOperation.m | 18 +++++-------- 5 files changed, 47 insertions(+), 50 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 00e8c67..ec25ed0 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -31,6 +31,10 @@ NSSet * AFContentTypesFromHTTPHeader(NSString *string) { _skippedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@" ,"]; }); + if (!string) { + return nil; + } + NSScanner *scanner = [NSScanner scannerWithString:string]; scanner.charactersToBeSkipped = _skippedCharacterSet; @@ -226,7 +230,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { } + (BOOL)canProcessRequest:(NSURLRequest *)request { - return YES; + if (![[self class] isEqual:[AFHTTPRequestOperation class]]) { + return YES; + } + + return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:@"Accept"])]; } @end diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index 52426b1..ecfccd3 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -37,8 +37,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { #elif __MAC_OS_X_VERSION_MIN_REQUIRED @property (readwrite, nonatomic, retain) NSImage *responseImage; #endif - -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFImageRequestOperation @@ -125,14 +123,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { } #endif -+ (NSSet *)acceptableContentTypes { - return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil]; -} - - (id)initWithRequest:(NSURLRequest *)urlRequest { self = [super initWithRequest:urlRequest]; if (!self) { @@ -189,9 +179,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { #pragma mark - AFHTTPClientOperation -+ (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; -} #if __IPHONE_OS_VERSION_MIN_REQUIRED + (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest @@ -217,6 +204,20 @@ static dispatch_queue_t image_request_operation_processing_queue() { } #endif ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + static NSSet * _acceptablePathExtension = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _acceptablePathExtension = [[NSSet alloc] initWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil]; + }); + + return [_acceptablePathExtension containsObject:[[request URL] pathExtension]] || [super canProcessRequest:request]; +} + - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { diff --git a/AFNetworking/AFJSONRequestOperation.m b/AFNetworking/AFJSONRequestOperation.m index 651f2d7..451705e 100644 --- a/AFNetworking/AFJSONRequestOperation.m +++ b/AFNetworking/AFJSONRequestOperation.m @@ -35,8 +35,6 @@ static dispatch_queue_t json_request_operation_processing_queue() { @interface AFJSONRequestOperation () @property (readwrite, nonatomic, retain) id responseJSON; @property (readwrite, nonatomic, retain) NSError *JSONError; - -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFJSONRequestOperation @@ -61,18 +59,6 @@ static dispatch_queue_t json_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)acceptableContentTypes { - return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"json", nil]; -} - -+ (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; -} - - (void)dealloc { [_responseJSON release]; [_JSONError release]; @@ -103,6 +89,16 @@ static dispatch_queue_t json_request_operation_processing_queue() { } } +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"json"] || [super canProcessRequest:request]; +} + - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { diff --git a/AFNetworking/AFPropertyListRequestOperation.m b/AFNetworking/AFPropertyListRequestOperation.m index b5609b3..ecca1af 100644 --- a/AFNetworking/AFPropertyListRequestOperation.m +++ b/AFNetworking/AFPropertyListRequestOperation.m @@ -35,8 +35,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) id responsePropertyList; @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; @property (readwrite, nonatomic, retain) NSError *propertyListError; - -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFPropertyListRequestOperation @@ -63,14 +61,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)acceptableContentTypes { - return [NSSet setWithObjects:@"application/x-plist", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"plist", nil]; -} - - (id)initWithRequest:(NSURLRequest *)urlRequest { self = [super initWithRequest:urlRequest]; if (!self) { @@ -108,8 +98,14 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { } } +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/x-plist", nil]; +} + + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[request URL] pathExtension] isEqualToString:@"plist"] || [super canProcessRequest:request]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success diff --git a/AFNetworking/AFXMLRequestOperation.m b/AFNetworking/AFXMLRequestOperation.m index 25a3765..f40cacb 100644 --- a/AFNetworking/AFXMLRequestOperation.m +++ b/AFNetworking/AFXMLRequestOperation.m @@ -39,8 +39,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) NSXMLDocument *responseXMLDocument; #endif @property (readwrite, nonatomic, retain) NSError *XMLError; - -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFXMLRequestOperation @@ -90,14 +88,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { } #endif -+ (NSSet *)acceptableContentTypes { - return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"xml", nil]; -} - - (void)dealloc { [_responseXMLParser release]; @@ -146,8 +136,14 @@ static dispatch_queue_t xml_request_operation_processing_queue() { self.responseXMLParser.delegate = nil; } +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; +} + + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[request URL] pathExtension] isEqualToString:@"xml"] || [super canProcessRequest:request]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success From 692f4ca90e024d60347ee79606148e084668127b Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sat, 3 Mar 2012 13:20:51 -0800 Subject: [PATCH 06/41] Removing unused overrided implementation of HTTPRequestOperationWithRequest:success:failure: in AFImageRequestOperation --- AFNetworking/AFImageRequestOperation.m | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index ecfccd3..0bd654e 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -179,31 +179,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { #pragma mark - AFHTTPClientOperation - -#if __IPHONE_OS_VERSION_MIN_REQUIRED -+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest - success:(void (^)(id object))success - failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure -{ - return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, UIImage *image) { - success(image); - } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) { - failure(response, error); - }]; -} -#elif __MAC_OS_X_VERSION_MIN_REQUIRED -+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest - success:(void (^)(id object))success - failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure -{ - return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, NSImage *image) { - success(image); - } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) { - failure(response, error); - }]; -} -#endif - + (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; } From 5b49cbbc8698c2f9ffab35417ab8375d084b6942 Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Mon, 5 Mar 2012 16:01:12 -0600 Subject: [PATCH 07/41] Added additional Reachability information to the AFHTTPClient As discussed by @mattt in #197, this pull request takes a stab at exposing the Reachability state via an enum property, similar to Reachability itself. --- AFNetworking/AFHTTPClient.h | 25 +++++++++++++++++++++-- AFNetworking/AFHTTPClient.m | 40 ++++++++++++++++++++++++++++++------- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 3fce876..4f3a104 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -37,6 +37,18 @@ extern NSString * const AFNetworkingReachabilityDidChangeNotification; #endif +/** + Enum representing the reachability states to the `baseURL` of the `AFHTTPClient.` + */ +#ifdef _SYSTEMCONFIGURATION_H +typedef enum{ + AFReachabilityStatusUnknown = 0, + AFReachabilityStatusUnavilable, + AFReachabilityStatusAvailableViaWWAN, + AFReachabilityStatusAvailableViaWiFi, +}AFReachabilityStatus; +#endif + /** Method used to encode parameters into request body. */ @@ -147,6 +159,15 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete */ @property (readonly, nonatomic, retain) NSOperationQueue *operationQueue; +/** + The reachability status from the device to the current `baseURL` of the `AFHTTPClient`. + + @warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +@property (readonly, nonatomic) AFReachabilityStatus reachabilityStatus; +#endif + ///--------------------------------------------- /// @name Creating and Initializing HTTP Clients ///--------------------------------------------- @@ -178,12 +199,12 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete /** Sets a callback to be executed when the network availability of the `baseURL` host changes. - @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument, which is `YES` if the host is available, otherwise `NO`. + @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`. @warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). */ #ifdef _SYSTEMCONFIGURATION_H -- (void)setReachabilityStatusChangeBlock:(void (^)(BOOL isNetworkReachable))block; +- (void)setReachabilityStatusChangeBlock:(void (^)(AFReachabilityStatus reachabilityStatus))block; #endif ///------------------------------- diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 7dfa1ca..689b9ec 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -57,11 +57,11 @@ static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY" #ifdef _SYSTEMCONFIGURATION_H typedef SCNetworkReachabilityRef AFNetworkReachabilityRef; +typedef void (^AFNetworkReachabilityStatusBlock)(AFReachabilityStatus reachabilityStatus); #else typedef id AFNetworkReachabilityRef; #endif -typedef void (^AFNetworkReachabilityStatusBlock)(BOOL isNetworkReachable); typedef void (^AFCompletionBlock)(void); static NSUInteger const kAFHTTPClientDefaultMaxConcurrentOperationCount = 4; @@ -155,8 +155,10 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { @property (readwrite, nonatomic, retain) NSMutableArray *registeredHTTPOperationClassNames; @property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders; @property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue; +#ifdef _SYSTEMCONFIGURATION_H @property (readwrite, nonatomic, assign) AFNetworkReachabilityRef networkReachability; @property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; +#endif #ifdef _SYSTEMCONFIGURATION_H - (void)startMonitoringNetworkReachability; @@ -171,8 +173,11 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { @synthesize registeredHTTPOperationClassNames = _registeredHTTPOperationClassNames; @synthesize defaultHeaders = _defaultHeaders; @synthesize operationQueue = _operationQueue; +#ifdef _SYSTEMCONFIGURATION_H @synthesize networkReachability = _networkReachability; @synthesize networkReachabilityStatusBlock = _networkReachabilityStatusBlock; +@synthesize reachabilityStatus = _reachabilityStatus; +#endif + (AFHTTPClient *)clientWithBaseURL:(NSURL *)url { return [[[self alloc] initWithBaseURL:url] autorelease]; @@ -220,13 +225,13 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { - (void)dealloc { #ifdef _SYSTEMCONFIGURATION_H [self stopMonitoringNetworkReachability]; + [_networkReachabilityStatusBlock release]; #endif [_baseURL release]; [_registeredHTTPOperationClassNames release]; [_defaultHeaders release]; [_operationQueue release]; - [_networkReachabilityStatusBlock release]; [super dealloc]; } @@ -242,13 +247,29 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL isNetworkReachable = (isReachable && !needsConnection); - + + AFReachabilityStatus status = AFReachabilityStatusUnavilable; + if(isNetworkReachable == NO){ + status = AFReachabilityStatusUnavilable; + } +#if TARGET_OS_IPHONE + else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status = AFReachabilityStatusAvailableViaWWAN; + } + else{ + status = AFReachabilityStatusAvailableViaWiFi; + } +#else + else { + status = AFReachabilityStatusAvailableViaWiFi; + } +#endif AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; if (block) { - block(isNetworkReachable); + block(status); } - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithBool:isNetworkReachable]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithBool:status]]; } - (void)startMonitoringNetworkReachability { @@ -266,8 +287,13 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN } } -- (void)setReachabilityStatusChangeBlock:(void (^)(BOOL isNetworkReachable))block { - self.networkReachabilityStatusBlock = block; +- (void)setReachabilityStatusChangeBlock:(void (^)(AFReachabilityStatus reachabilityStatus))block { + void (^reachabilityCallback)(AFReachabilityStatus reachabilityStatus) = ^(AFReachabilityStatus reachabilityStatus){ + _reachabilityStatus = reachabilityStatus; + if(block) + block(reachabilityStatus); + }; + self.networkReachabilityStatusBlock = reachabilityCallback; [self startMonitoringNetworkReachability]; } #endif From 346c56b8f4836fa26424e0ce7e825914c09c8ebb Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Mon, 5 Mar 2012 16:21:45 -0600 Subject: [PATCH 08/41] Changed AFReachabilityStatus naming scheme to be more clear --- AFNetworking/AFHTTPClient.h | 6 +++--- AFNetworking/AFHTTPClient.m | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 4f3a104..8438659 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -43,9 +43,9 @@ extern NSString * const AFNetworkingReachabilityDidChangeNotification; #ifdef _SYSTEMCONFIGURATION_H typedef enum{ AFReachabilityStatusUnknown = 0, - AFReachabilityStatusUnavilable, - AFReachabilityStatusAvailableViaWWAN, - AFReachabilityStatusAvailableViaWiFi, + AFReachabilityStatusNotReachable, + AFReachabilityStatusReachableViaWWAN, + AFReachabilityStatusReachableViaWiFi, }AFReachabilityStatus; #endif diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 689b9ec..ff24dfd 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -248,20 +248,20 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL isNetworkReachable = (isReachable && !needsConnection); - AFReachabilityStatus status = AFReachabilityStatusUnavilable; + AFReachabilityStatus status = AFReachabilityStatusUnknown; if(isNetworkReachable == NO){ - status = AFReachabilityStatusUnavilable; + status = AFReachabilityStatusNotReachable; } #if TARGET_OS_IPHONE else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ - status = AFReachabilityStatusAvailableViaWWAN; + status = AFReachabilityStatusReachableViaWWAN; } else{ - status = AFReachabilityStatusAvailableViaWiFi; + status = AFReachabilityStatusReachableViaWiFi; } #else else { - status = AFReachabilityStatusAvailableViaWiFi; + status = AFReachabilityStatusReachableViaWiFi; } #endif AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; From 71d6af9e5cb18d0bd223560e8dbbb14635c36eee Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 9 Mar 2012 15:48:59 -0800 Subject: [PATCH 09/41] Renaming AFNetworkReachabilityStatus and changing to bitmask Fixing AFReachabilityCallback to give correct WiFi reachability value Minor formatting and refactoring --- AFNetworking/AFHTTPClient.h | 20 ++++++------ AFNetworking/AFHTTPClient.m | 61 +++++++++++++++++++++++-------------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 8438659..3c1593d 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -38,19 +38,19 @@ extern NSString * const AFNetworkingReachabilityDidChangeNotification; #endif /** - Enum representing the reachability states to the `baseURL` of the `AFHTTPClient.` + Specifies network reachability of the client to its `baseURL` domain. */ #ifdef _SYSTEMCONFIGURATION_H -typedef enum{ - AFReachabilityStatusUnknown = 0, - AFReachabilityStatusNotReachable, - AFReachabilityStatusReachableViaWWAN, - AFReachabilityStatusReachableViaWiFi, -}AFReachabilityStatus; +typedef enum { + AFNetworkReachabilityStatusUnknown = -1, + AFNetworkReachabilityStatusNotReachable = 0, + AFNetworkReachabilityStatusReachableViaWWAN = 1 << 0, + AFNetworkReachabilityStatusReachableViaWiFi = 1 << 1, +} AFNetworkReachabilityStatus; #endif /** - Method used to encode parameters into request body. + Specifies the method used to encode parameters into request body. */ typedef enum { AFFormURLParameterEncoding, @@ -165,7 +165,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). */ #ifdef _SYSTEMCONFIGURATION_H -@property (readonly, nonatomic) AFReachabilityStatus reachabilityStatus; +@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; #endif ///--------------------------------------------- @@ -204,7 +204,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). */ #ifdef _SYSTEMCONFIGURATION_H -- (void)setReachabilityStatusChangeBlock:(void (^)(AFReachabilityStatus reachabilityStatus))block; +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block; #endif ///------------------------------- diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 6a4f476..13baaa3 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -34,6 +34,11 @@ #ifdef _SYSTEMCONFIGURATION_H #import +#import +#import +#import +#import +#import #endif NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; @@ -57,7 +62,7 @@ static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY" #ifdef _SYSTEMCONFIGURATION_H typedef SCNetworkReachabilityRef AFNetworkReachabilityRef; -typedef void (^AFNetworkReachabilityStatusBlock)(AFReachabilityStatus reachabilityStatus); +typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status); #else typedef id AFNetworkReachabilityRef; #endif @@ -196,6 +201,7 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { @property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue; #ifdef _SYSTEMCONFIGURATION_H @property (readwrite, nonatomic, assign) AFNetworkReachabilityRef networkReachability; +@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; @property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; #endif @@ -214,8 +220,8 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { @synthesize operationQueue = _operationQueue; #ifdef _SYSTEMCONFIGURATION_H @synthesize networkReachability = _networkReachability; +@synthesize networkReachabilityStatus = _networkReachabilityStatus; @synthesize networkReachabilityStatusBlock = _networkReachabilityStatusBlock; -@synthesize reachabilityStatus = _reachabilityStatus; #endif + (AFHTTPClient *)clientWithBaseURL:(NSURL *)url { @@ -252,6 +258,7 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #endif #ifdef _SYSTEMCONFIGURATION_H + self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; [self startMonitoringNetworkReachability]; #endif @@ -287,22 +294,28 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL isNetworkReachable = (isReachable && !needsConnection); - AFReachabilityStatus status = AFReachabilityStatusUnknown; - if(isNetworkReachable == NO){ - status = AFReachabilityStatusNotReachable; + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusNotReachable; + if(isNetworkReachable == NO) { + status = AFNetworkReachabilityStatusNotReachable; + } else { + #if TARGET_OS_IPHONE + if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status |= AFNetworkReachabilityStatusReachableViaWWAN; + } + #endif + + struct sockaddr_in localWiFiAddress; + memset(&localWiFiAddress, '\0', sizeof(localWiFiAddress)); + localWiFiAddress.sin_len = sizeof(localWiFiAddress); + localWiFiAddress.sin_family = AF_INET; + localWiFiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&localWiFiAddress); + if (reachability != NULL) { + status |= AFNetworkReachabilityStatusReachableViaWiFi; + } } -#if TARGET_OS_IPHONE - else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ - status = AFReachabilityStatusReachableViaWWAN; - } - else{ - status = AFReachabilityStatusReachableViaWiFi; - } -#else - else { - status = AFReachabilityStatusReachableViaWiFi; - } -#endif + AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; if (block) { block(status); @@ -326,13 +339,15 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN } } -- (void)setReachabilityStatusChangeBlock:(void (^)(AFReachabilityStatus reachabilityStatus))block { - void (^reachabilityCallback)(AFReachabilityStatus reachabilityStatus) = ^(AFReachabilityStatus reachabilityStatus){ - _reachabilityStatus = reachabilityStatus; - if(block) - block(reachabilityStatus); +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { + AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ + self.networkReachabilityStatus = status; + if (block) { + block(status); + } }; - self.networkReachabilityStatusBlock = reachabilityCallback; + + self.networkReachabilityStatusBlock = callback; [self startMonitoringNetworkReachability]; } #endif From fa01557f4a130ac614bf02e2bcd13070219a25f1 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 11 Mar 2012 10:31:11 -0700 Subject: [PATCH 10/41] Add setReachabilityStatusChangeBlock to AFHTTPClient initialization --- AFNetworking/AFHTTPClient.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 13baaa3..e03e72d 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -259,7 +259,7 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #ifdef _SYSTEMCONFIGURATION_H self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; - [self startMonitoringNetworkReachability]; + [self setReachabilityStatusChangeBlock:nil]; #endif self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; From 14227aadc7b9b94adbefb45aa898ef61ea8e4fd3 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 11 Mar 2012 10:36:14 -0700 Subject: [PATCH 11/41] Reverting network reachability status from bitmask to enum --- AFNetworking/AFHTTPClient.h | 4 ++-- AFNetworking/AFHTTPClient.m | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 3c1593d..c9e0cad 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -44,8 +44,8 @@ extern NSString * const AFNetworkingReachabilityDidChangeNotification; typedef enum { AFNetworkReachabilityStatusUnknown = -1, AFNetworkReachabilityStatusNotReachable = 0, - AFNetworkReachabilityStatusReachableViaWWAN = 1 << 0, - AFNetworkReachabilityStatusReachableViaWiFi = 1 << 1, + AFNetworkReachabilityStatusReachableViaWWAN = 1, + AFNetworkReachabilityStatusReachableViaWiFi = 2, } AFNetworkReachabilityStatus; #endif diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index e03e72d..2c4c05f 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -300,7 +300,7 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN } else { #if TARGET_OS_IPHONE if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ - status |= AFNetworkReachabilityStatusReachableViaWWAN; + status = AFNetworkReachabilityStatusReachableViaWWAN; } #endif @@ -312,7 +312,7 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&localWiFiAddress); if (reachability != NULL) { - status |= AFNetworkReachabilityStatusReachableViaWiFi; + status = AFNetworkReachabilityStatusReachableViaWiFi; } } From 4bfa2f4de0c720ebb488dfa1f12735811730f47f Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 11 Mar 2012 10:53:06 -0700 Subject: [PATCH 12/41] Circumventing need to set initial network reachability status block to nil by wrapping callback in -startMonitoringNetworkReachability --- AFNetworking/AFHTTPClient.m | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 2c4c05f..0b8eb25 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -259,7 +259,7 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #ifdef _SYSTEMCONFIGURATION_H self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; - [self setReachabilityStatusChangeBlock:nil]; + [self startMonitoringNetworkReachability]; #endif self.operationQueue = [[[NSOperationQueue alloc] init] autorelease]; @@ -324,10 +324,25 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithBool:status]]; } +static const void * AFReachabilityRetainCallback(const void *info) { + return [(AFNetworkReachabilityStatusBlock)info copy]; +} + +static void AFReachabilityReleaseCallback(const void *info) { + [(AFNetworkReachabilityStatusBlock)info release]; +} + - (void)startMonitoringNetworkReachability { [self stopMonitoringNetworkReachability]; self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); - SCNetworkReachabilityContext context = {0, self.networkReachabilityStatusBlock, NULL, NULL, NULL}; + + AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ + self.networkReachabilityStatus = status; + if (self.networkReachabilityStatusBlock) { + self.networkReachabilityStatusBlock(status); + } + }; + SCNetworkReachabilityContext context = {0, callback, AFReachabilityRetainCallback, AFReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); } @@ -340,14 +355,7 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN } - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { - AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ - self.networkReachabilityStatus = status; - if (block) { - block(status); - } - }; - - self.networkReachabilityStatusBlock = callback; + self.networkReachabilityStatusBlock = block; [self startMonitoringNetworkReachability]; } #endif From fe0f1ce932226282fe59701f7a7d08be88529b8d Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Mon, 12 Mar 2012 20:22:46 -0700 Subject: [PATCH 13/41] -cStringUsingEncoding:NSUTF8Encoding -> -UTF8String --- AFNetworking/AFHTTPRequestOperation.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 3e450bc..5fbd11f 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -56,7 +56,7 @@ NSSet * AFContentTypesFromHTTPHeader(NSString *string) { static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL selector, void *block) { Method originalMethod = class_getClassMethod(klass, selector); IMP implementation = imp_implementationWithBlock(block); - class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) cStringUsingEncoding:NSUTF8StringEncoding]), selector, implementation, method_getTypeEncoding(originalMethod)); + class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod)); } From ac20e02af3ea3bf027657a92142aef99c48cc62c Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Mon, 12 Mar 2012 20:23:41 -0700 Subject: [PATCH 14/41] Constructing image in background queue --- AFNetworking/AFImageRequestOperation.m | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index 0bd654e..34a1fd6 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -210,8 +210,16 @@ static dispatch_queue_t image_request_operation_processing_queue() { } } else { if (success) { +#if __IPHONE_OS_VERSION_MIN_REQUIRED + UIImage *image = nil; +#elif __MAC_OS_X_VERSION_MIN_REQUIRED + NSImage *image = nil; +#endif + + image = self.responseImage; + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ - success(self, self.responseImage); + success(self, image); }); } } From d96f8f3fde1053a8120850e44819669c3cf330bc Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Tue, 13 Mar 2012 09:12:58 -0500 Subject: [PATCH 15/41] Updated Reachability to handle an IP Address or a Host name Note this does not fix the issue with the status not being correctly reported for a 3G connection. --- AFNetworking/AFHTTPClient.m | 111 ++++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 0b8eb25..9b04be4 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -208,6 +208,8 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #ifdef _SYSTEMCONFIGURATION_H - (void)startMonitoringNetworkReachability; - (void)stopMonitoringNetworkReachability; ++ (AFNetworkReachabilityStatus)reachabilityStatusForFlags:(SCNetworkReachabilityFlags)flags; ++ (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address; #endif @end @@ -289,39 +291,15 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #pragma mark - #ifdef _SYSTEMCONFIGURATION_H -static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { - BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); - BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); - BOOL isNetworkReachable = (isReachable && !needsConnection); - - AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusNotReachable; - if(isNetworkReachable == NO) { - status = AFNetworkReachabilityStatusNotReachable; - } else { - #if TARGET_OS_IPHONE - if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ - status = AFNetworkReachabilityStatusReachableViaWWAN; - } - #endif - - struct sockaddr_in localWiFiAddress; - memset(&localWiFiAddress, '\0', sizeof(localWiFiAddress)); - localWiFiAddress.sin_len = sizeof(localWiFiAddress); - localWiFiAddress.sin_family = AF_INET; - localWiFiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); - - SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&localWiFiAddress); - if (reachability != NULL) { - status = AFNetworkReachabilityStatusReachableViaWiFi; - } - } +static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { + AFNetworkReachabilityStatus status = [AFHTTPClient reachabilityStatusForFlags:flags]; AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; if (block) { block(status); } - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithBool:status]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; } static const void * AFReachabilityRetainCallback(const void *info) { @@ -333,8 +311,24 @@ static void AFReachabilityReleaseCallback(const void *info) { } - (void)startMonitoringNetworkReachability { - [self stopMonitoringNetworkReachability]; - self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); + [self stopMonitoringNetworkReachability]; + + //In order to handle all Reachability cases, we must determine if the Host URL is an IP Address + //or a Host Name. We must then use the appropriate SCNetworkReachabilityCreateWith... function + //based on the result. + NSString * ipMatch = @"^[0-9]{1,3}(.[0-9]{1,3}){3}$"; + NSRegularExpression *ipRegex = [NSRegularExpression regularExpressionWithPattern:ipMatch options:NSRegularExpressionCaseInsensitive error:nil]; + + BOOL isIPAddress = [ipRegex numberOfMatchesInString:[self.baseURL host] options:NSMatchingReportProgress range:NSMakeRange(0, [[self.baseURL host] length])]; + + if(isIPAddress == YES){ + struct sockaddr_in address; + [AFHTTPClient addressFromString:[self.baseURL host] address:&address]; + self.networkReachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&address); + } + else { + self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); + } AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ self.networkReachabilityStatus = status; @@ -342,9 +336,22 @@ static void AFReachabilityReleaseCallback(const void *info) { self.networkReachabilityStatusBlock(status); } }; + SCNetworkReachabilityContext context = {0, callback, AFReachabilityRetainCallback, AFReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + + if(isIPAddress == YES){ + //For SCNetworkReachabilityCreateWithAddress, the callback block is not immediately called. + //In order to duplicate the immediate callback behavior of SCNetworkReachabilityCreateWithName, + //we must pull the current status, and then manually call the block + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityGetFlags(self.networkReachability, &flags); + dispatch_async(dispatch_get_main_queue(), ^{ + AFNetworkReachabilityStatus status = [AFHTTPClient reachabilityStatusForFlags:flags]; + callback(status); + }); + } } - (void)stopMonitoringNetworkReachability { @@ -354,9 +361,53 @@ static void AFReachabilityReleaseCallback(const void *info) { } } ++ (AFNetworkReachabilityStatus)reachabilityStatusForFlags:(SCNetworkReachabilityFlags)flags{ + BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); + BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); + BOOL isNetworkReachable = (isReachable && !needsConnection); + + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusNotReachable; + if(isNetworkReachable == NO) { + status = AFNetworkReachabilityStatusNotReachable; + } else { +#if TARGET_OS_IPHONE + if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status = AFNetworkReachabilityStatusReachableViaWWAN; + } +#endif + + struct sockaddr_in localWiFiAddress; + memset(&localWiFiAddress, '\0', sizeof(localWiFiAddress)); + localWiFiAddress.sin_len = sizeof(localWiFiAddress); + localWiFiAddress.sin_family = AF_INET; + localWiFiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&localWiFiAddress); + if (reachability != NULL) { + status = AFNetworkReachabilityStatusReachableViaWiFi; + } + } + return status; +} + ++ (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address{ + if (!IPAddress || ![IPAddress length]) { + return NO; + } + + memset((char *) address, sizeof(struct sockaddr_in), 0); + address->sin_family = AF_INET; + address->sin_len = sizeof(struct sockaddr_in); + + int conversionResult = inet_aton([IPAddress UTF8String], &address->sin_addr); + if (conversionResult == 0) { + return NO; + } + return YES; +} + - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { self.networkReachabilityStatusBlock = block; - [self startMonitoringNetworkReachability]; } #endif From 428c13e1e0cfe8ecb506f61b5e0e2334f7be2b59 Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Wed, 14 Mar 2012 08:23:18 -0500 Subject: [PATCH 16/41] Refactored starting up reachability --- AFNetworking/AFHTTPClient.m | 46 ++++++++----------------------------- 1 file changed, 9 insertions(+), 37 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 9b04be4..64dee62 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -209,7 +209,6 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { - (void)startMonitoringNetworkReachability; - (void)stopMonitoringNetworkReachability; + (AFNetworkReachabilityStatus)reachabilityStatusForFlags:(SCNetworkReachabilityFlags)flags; -+ (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address; #endif @end @@ -312,23 +311,8 @@ static void AFReachabilityReleaseCallback(const void *info) { - (void)startMonitoringNetworkReachability { [self stopMonitoringNetworkReachability]; - - //In order to handle all Reachability cases, we must determine if the Host URL is an IP Address - //or a Host Name. We must then use the appropriate SCNetworkReachabilityCreateWith... function - //based on the result. - NSString * ipMatch = @"^[0-9]{1,3}(.[0-9]{1,3}){3}$"; - NSRegularExpression *ipRegex = [NSRegularExpression regularExpressionWithPattern:ipMatch options:NSRegularExpressionCaseInsensitive error:nil]; - - BOOL isIPAddress = [ipRegex numberOfMatchesInString:[self.baseURL host] options:NSMatchingReportProgress range:NSMakeRange(0, [[self.baseURL host] length])]; - - if(isIPAddress == YES){ - struct sockaddr_in address; - [AFHTTPClient addressFromString:[self.baseURL host] address:&address]; - self.networkReachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&address); - } - else { - self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); - } + + self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ self.networkReachabilityStatus = status; @@ -341,10 +325,14 @@ static void AFReachabilityReleaseCallback(const void *info) { SCNetworkReachabilitySetCallback(self.networkReachability, AFReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + //If the [self.baseURL host] is an IP Address, the reachability callback function will not be + //called until an actual change occurs. In order to duplicate the immediate callback behavior + //when using a host name instead of an IP Address, the call must manually be made below. + NSString * ipMatch = @"^[0-9]{1,3}(.[0-9]{1,3}){3}$"; + NSRegularExpression *ipRegex = [NSRegularExpression regularExpressionWithPattern:ipMatch options:NSRegularExpressionCaseInsensitive error:nil]; + + BOOL isIPAddress = [ipRegex numberOfMatchesInString:[self.baseURL host] options:NSMatchingReportProgress range:NSMakeRange(0, [[self.baseURL host] length])]; if(isIPAddress == YES){ - //For SCNetworkReachabilityCreateWithAddress, the callback block is not immediately called. - //In order to duplicate the immediate callback behavior of SCNetworkReachabilityCreateWithName, - //we must pull the current status, and then manually call the block SCNetworkReachabilityFlags flags; SCNetworkReachabilityGetFlags(self.networkReachability, &flags); dispatch_async(dispatch_get_main_queue(), ^{ @@ -390,22 +378,6 @@ static void AFReachabilityReleaseCallback(const void *info) { return status; } -+ (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)address{ - if (!IPAddress || ![IPAddress length]) { - return NO; - } - - memset((char *) address, sizeof(struct sockaddr_in), 0); - address->sin_family = AF_INET; - address->sin_len = sizeof(struct sockaddr_in); - - int conversionResult = inet_aton([IPAddress UTF8String], &address->sin_addr); - if (conversionResult == 0) { - return NO; - } - return YES; -} - - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { self.networkReachabilityStatusBlock = block; } From 50c136f5d4267929d87796cec585ee4e6a4bc3fa Mon Sep 17 00:00:00 2001 From: Kevin Harwood Date: Tue, 20 Mar 2012 11:48:54 -0500 Subject: [PATCH 17/41] Refactored the Reachability status logic --- AFNetworking/AFHTTPClient.m | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 64dee62..92f3602 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -354,27 +354,19 @@ static void AFReachabilityReleaseCallback(const void *info) { BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL isNetworkReachable = (isReachable && !needsConnection); - AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusNotReachable; - if(isNetworkReachable == NO) { + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown; + if(isNetworkReachable == NO){ status = AFNetworkReachabilityStatusNotReachable; - } else { -#if TARGET_OS_IPHONE - if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ - status = AFNetworkReachabilityStatusReachableViaWWAN; - } -#endif - - struct sockaddr_in localWiFiAddress; - memset(&localWiFiAddress, '\0', sizeof(localWiFiAddress)); - localWiFiAddress.sin_len = sizeof(localWiFiAddress); - localWiFiAddress.sin_family = AF_INET; - localWiFiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); - - SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)&localWiFiAddress); - if (reachability != NULL) { - status = AFNetworkReachabilityStatusReachableViaWiFi; - } } +#if TARGET_OS_IPHONE + else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status = AFNetworkReachabilityStatusReachableViaWWAN; + } +#endif + else { + status = AFNetworkReachabilityStatusReachableViaWiFi; + } + return status; } From 2ec0c3ec2faa7642332b44cd00c6a6ccf0ca0d59 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 20 Mar 2012 11:31:18 -0700 Subject: [PATCH 18/41] Adding AFURLConnectionOperation -setShouldExecuteAsBackgroundTaskWithExpirationHandler: --- AFNetworking/AFURLConnectionOperation.h | 13 +++++++++ AFNetworking/AFURLConnectionOperation.m | 35 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 14d0226..c9294d3 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -169,6 +169,19 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; */ - (id)initWithRequest:(NSURLRequest *)urlRequest; +///---------------------------------------------- +/// @name Configuring Backgrounding Task Behavior +///---------------------------------------------- + +/** + Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task. + + @param handler A handler to be called shortly before the application’s remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the application’s suspension momentarily while the application is notified. + */ +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler; +#endif + ///--------------------------------- /// @name Setting Progress Callbacks ///--------------------------------- diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 172595a..8c64c91 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -30,6 +30,12 @@ typedef enum { typedef unsigned short AFOperationState; +#if __IPHONE_OS_VERSION_MIN_REQUIRED +typedef UIBackgroundTaskIdentifier AFBackgroundTaskIdentifier; +#else +typedef id AFBackgroundTaskIdentifier; +#endif + static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; @@ -94,6 +100,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @property (readwrite, nonatomic, copy) NSString *responseString; @property (readwrite, nonatomic, assign) NSInteger totalBytesRead; @property (readwrite, nonatomic, retain) NSMutableData *dataAccumulator; +@property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace; @@ -117,6 +124,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @synthesize dataAccumulator = _dataAccumulator; @dynamic inputStream; @synthesize outputStream = _outputStream; +@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; @synthesize uploadProgress = _uploadProgress; @synthesize downloadProgress = _downloadProgress; @synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace; @@ -189,6 +197,13 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat [_outputStream release]; _outputStream = nil; } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + if (_backgroundTaskIdentifier) { + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier]; + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } +#endif [_uploadProgress release]; [_downloadProgress release]; @@ -229,6 +244,26 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.request = mutableRequest; } +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { + [self.lock lock]; + if (!self.backgroundTaskIdentifier) { + UIApplication *application = [UIApplication sharedApplication]; + self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ + if (handler) { + handler(); + } + + [self cancel]; + + [application endBackgroundTask:self.backgroundTaskIdentifier]; + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + }]; + } + [self.lock unlock]; +} +#endif + - (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block { self.uploadProgress = block; } From 2868b069cdd9807fc45c0eba6ffaee4479dca391 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 20 Mar 2012 11:32:44 -0700 Subject: [PATCH 19/41] Adding missing extern specifier for AFQueryStringComponentFromKeyAndValueWithEncoding(...) --- AFNetworking/AFHTTPClient.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index f0f07e0..adfe032 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -154,6 +154,7 @@ NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEn #pragma mark - +extern AFQueryStringComponent * AFQueryStringComponentFromKeyAndValueWithEncoding(id key, id value, NSStringEncoding stringEncoding); extern NSArray * AFQueryStringComponentsFromKeyAndValueWithEncoding(NSString *key, id value, NSStringEncoding stringEncoding); extern NSArray * AFQueryStringComponentsFromKeyAndDictionaryValueWithEncoding(NSString *key, NSDictionary *value, NSStringEncoding stringEncoding); extern NSArray * AFQueryStringComponentsFromKeyAndArrayValueWithEncoding(NSString *key, NSArray *value, NSStringEncoding stringEncoding); @@ -465,9 +466,9 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN if (!operation) { operation = [[[AFHTTPRequestOperation alloc] initWithRequest:urlRequest] autorelease]; } - + [operation setCompletionBlockWithSuccess:success failure:failure]; - + return operation; } From e61275110b4f17726d4468b5deef3a393a8d7c82 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 20 Mar 2012 11:33:15 -0700 Subject: [PATCH 20/41] Adding locking to AFURLConnectionOperation -setInputStream: --- AFNetworking/AFURLConnectionOperation.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 8c64c91..a370e0c 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -239,9 +239,11 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } - (void)setInputStream:(NSInputStream *)inputStream { + [self.lock lock]; NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; + [self.lock unlock]; } #if __IPHONE_OS_VERSION_MIN_REQUIRED From ca87ea1a253f4159248a3c3fc69e59f5c663b5ea Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 20 Mar 2012 16:43:19 -0700 Subject: [PATCH 21/41] Refactoring network reachability functions Using inet_pton instead of regexp to detect whether a URL host is an IP address --- AFNetworking/AFHTTPClient.m | 118 ++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 67637a8..5724c5d 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -242,7 +242,6 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #ifdef _SYSTEMCONFIGURATION_H - (void)startMonitoringNetworkReachability; - (void)stopMonitoringNetworkReachability; -+ (AFNetworkReachabilityStatus)reachabilityStatusForFlags:(SCNetworkReachabilityFlags)flags; #endif @end @@ -324,66 +323,14 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #pragma mark - #ifdef _SYSTEMCONFIGURATION_H -static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { - AFNetworkReachabilityStatus status = [AFHTTPClient reachabilityStatusForFlags:flags]; +static BOOL AFURLHostIsIPAddress(NSURL *url) { + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; - AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; - if (block) { - block(status); - } - - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; + return [url host] && (inet_pton(AF_INET, [[url host] UTF8String], &sa_in) == 1 || inet_pton(AF_INET6, [[url host] UTF8String], &sa_in6) == 1); } -static const void * AFReachabilityRetainCallback(const void *info) { - return [(AFNetworkReachabilityStatusBlock)info copy]; -} - -static void AFReachabilityReleaseCallback(const void *info) { - [(AFNetworkReachabilityStatusBlock)info release]; -} - -- (void)startMonitoringNetworkReachability { - [self stopMonitoringNetworkReachability]; - - self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); - - AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ - self.networkReachabilityStatus = status; - if (self.networkReachabilityStatusBlock) { - self.networkReachabilityStatusBlock(status); - } - }; - - SCNetworkReachabilityContext context = {0, callback, AFReachabilityRetainCallback, AFReachabilityReleaseCallback, NULL}; - SCNetworkReachabilitySetCallback(self.networkReachability, AFReachabilityCallback, &context); - SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); - - //If the [self.baseURL host] is an IP Address, the reachability callback function will not be - //called until an actual change occurs. In order to duplicate the immediate callback behavior - //when using a host name instead of an IP Address, the call must manually be made below. - NSString * ipMatch = @"^[0-9]{1,3}(.[0-9]{1,3}){3}$"; - NSRegularExpression *ipRegex = [NSRegularExpression regularExpressionWithPattern:ipMatch options:NSRegularExpressionCaseInsensitive error:nil]; - - BOOL isIPAddress = [ipRegex numberOfMatchesInString:[self.baseURL host] options:NSMatchingReportProgress range:NSMakeRange(0, [[self.baseURL host] length])]; - if(isIPAddress == YES){ - SCNetworkReachabilityFlags flags; - SCNetworkReachabilityGetFlags(self.networkReachability, &flags); - dispatch_async(dispatch_get_main_queue(), ^{ - AFNetworkReachabilityStatus status = [AFHTTPClient reachabilityStatusForFlags:flags]; - callback(status); - }); - } -} - -- (void)stopMonitoringNetworkReachability { - if (_networkReachability) { - SCNetworkReachabilityUnscheduleFromRunLoop(_networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); - CFRelease(_networkReachability); - } -} - -+ (AFNetworkReachabilityStatus)reachabilityStatusForFlags:(SCNetworkReachabilityFlags)flags{ +static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL isNetworkReachable = (isReachable && !needsConnection); @@ -400,10 +347,63 @@ static void AFReachabilityReleaseCallback(const void *info) { else { status = AFNetworkReachabilityStatusReachableViaWiFi; } - + return status; } +static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; + if (block) { + block(status); + } + + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; +} + +static const void * AFNetworkReachabilityRetainCallback(const void *info) { + return [(AFNetworkReachabilityStatusBlock)info copy]; +} + +static void AFNetworkReachabilityReleaseCallback(const void *info) { + [(AFNetworkReachabilityStatusBlock)info release]; +} + +- (void)startMonitoringNetworkReachability { + [self stopMonitoringNetworkReachability]; + + self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); + + AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ + self.networkReachabilityStatus = status; + if (self.networkReachabilityStatusBlock) { + self.networkReachabilityStatusBlock(status); + } + }; + + SCNetworkReachabilityContext context = {0, callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; + SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); + SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + + /* Network reachability monitoring does not establish a baseline for IP addresses as it does for hostnames, so if the base URL host is an IP address, the initial reachability callback is manually triggered. + */ + if (AFURLHostIsIPAddress(self.baseURL)) { + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityGetFlags(self.networkReachability, &flags); + dispatch_async(dispatch_get_main_queue(), ^{ + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + callback(status); + }); + } +} + +- (void)stopMonitoringNetworkReachability { + if (_networkReachability) { + SCNetworkReachabilityUnscheduleFromRunLoop(_networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + CFRelease(_networkReachability); + } +} + - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { self.networkReachabilityStatusBlock = block; } From b8007fed90bcdaeee509a1fdbabc9e0350a47475 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 24 Mar 2012 06:53:36 -0700 Subject: [PATCH 22/41] Explicitly declare that we implement NSURLConnectionDataDelegate here. --- AFNetworking/AFURLConnectionOperation.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 14d0226..4eaee7f 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -75,7 +75,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5. */ -@interface AFURLConnectionOperation : NSOperation { +@interface AFURLConnectionOperation : NSOperation { @private unsigned short _state; BOOL _cancelled; From 49186d9a3032dc02da1d0cc445cbf69b0cfb001b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Mar 2012 00:46:08 -0700 Subject: [PATCH 23/41] add download request operation. Supports Content-Range to resume partial downloads. (http://tools.ietf.org/html/rfc2616#section-14.16) --- AFNetworking/AFDownloadRequestOperation.h | 108 ++++++++ AFNetworking/AFDownloadRequestOperation.m | 290 ++++++++++++++++++++++ 2 files changed, 398 insertions(+) create mode 100644 AFNetworking/AFDownloadRequestOperation.h create mode 100644 AFNetworking/AFDownloadRequestOperation.m diff --git a/AFNetworking/AFDownloadRequestOperation.h b/AFNetworking/AFDownloadRequestOperation.h new file mode 100644 index 0000000..ef12c8d --- /dev/null +++ b/AFNetworking/AFDownloadRequestOperation.h @@ -0,0 +1,108 @@ +// AFDownloadRequestOperation.h +// +// Copyright (c) 2012 Peter Steinberger (http://petersteinberger.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFHTTPRequestOperation.h" + +#define kAFNetworkingIncompleteDownloadFolderName @"Incomplete" + +/** + `AFDownloadRequestOperation` is a subclass of `AFHTTPRequestOperation` for streamed file downloading. Supports Content-Range. (http://tools.ietf.org/html/rfc2616#section-14.16) + */ +@interface AFDownloadRequestOperation : AFHTTPRequestOperation + +/** + A String value that defines the target path or directory. + + We try to be clever here and understand both a directory or a filename. + The target directory should already be create, or the download fill fail. + + If the target is a directory, we use the last part of the URL as a default file name. + */ +@property (retain) NSString *targetPath; + +/** + A Boolean value that indicates if we should try to resume the download. Defaults is `YES`. + + Can only be set while creating the request. + */ +@property (assign, readonly) BOOL shouldResume; + +/** + Deletes the temporary file if operations is cancelled. Defaults to `NO`. + */ +@property (assign, getter=isDeletingTempFileOnCancel) BOOL deleteTempFileOnCancel; + +/** + Expected total length. This is different than expectedContentLength if the file is resumed. + + Note: this can also be zero if the file size is not sent (*) + */ +@property (assign, readonly) long long totalContentLength; + +/** + Indicator for the file offset on partial downloads. This is greater than zero if the file download is resumed. + */ +@property (assign, readonly) long long offsetContentLength; + +///---------------------------------- +/// @name Creating Request Operations +///---------------------------------- + +/** + Creates and returns an `AFDownloadRequestOperation` object and sets the specified success and failure callbacks. + + @param urlRequest The request object to be loaded asynchronously during execution of the operation + @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes two arguments: the request sent from the client, and the filePath where the file is saved. + @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while saving/moving the file. This block has no return value and takes two arguments: the request sent from the client, and the error describing the network or file saving error that occurred. + + @return A new download request operation + */ ++ (AFDownloadRequestOperation *)downloadOperationWithRequest:(NSURLRequest *)urlRequest + targetPath:(NSString *)targetPath + shouldResume:(BOOL)shouldResume + success:(void (^)(NSURLRequest *request, NSString *filePath))success + failure:(void (^)(NSURLRequest *request, NSError *error))failure; + +- (id)initWithRequest:(NSURLRequest *)urlRequest targetPath:(NSString *)targetPath shouldResume:(BOOL)shouldResume; + +/** + Deletes the temporary file. + + Returns `NO` if an error happened, `YES` if the file is removed or did not exist in the first place. + */ +- (BOOL)deleteTempFileWithError:(NSError **)error; + +/** + Returns the path used for the temporary file. Returns `nil` if the targetPath has not been set. + */ +- (NSString *)tempPath; + +/** + Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. This is a variant of setDownloadProgressBlock that adds support for progressive downloads and adds the + + @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes five arguments: the number of bytes read since the last time the download progress block was called, the bytes expected to be read during the request, the bytes already read during this request, the total bytes read (including from previous partial downloads), and the total bytes expected to be read for the file. This block may be called multiple times. + + @see setDownloadProgressBlock + */ +- (void)setProgressiveDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile))block; + +@end diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m new file mode 100644 index 0000000..a1ab142 --- /dev/null +++ b/AFNetworking/AFDownloadRequestOperation.m @@ -0,0 +1,290 @@ +// AFDownloadRequestOperation.m +// +// Copyright (c) 2012 Peter Steinberger (http://petersteinberger.com) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#import "AFDownloadRequestOperation.h" +#import "AFURLConnectionOperation.h" +#import +#include +#include + +@interface AFURLConnectionOperation (AFInternal) +@property (nonatomic, strong) NSURLRequest *request; +@property (readonly, nonatomic, assign) long long totalBytesRead; +@end + +typedef void (^AFURLConnectionProgressiveOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile); + +@interface AFDownloadRequestOperation() { + NSError *_fileError; +} +@property (nonatomic, retain) NSString *tempPath; +@property (assign) long long totalContentLength; +@property (assign) long long offsetContentLength; +@property (nonatomic, copy) AFURLConnectionProgressiveOperationProgressBlock progressiveDownloadProgress; +@end + +@implementation AFDownloadRequestOperation + +@synthesize targetPath = _targetPath; +@synthesize tempPath = _tempPath; +@synthesize totalContentLength = _totalContentLength; +@synthesize offsetContentLength = _offsetContentLength; +@synthesize shouldResume = _shouldResume; +@synthesize deleteTempFileOnCancel = _deleteTempFileOnCancel; +@synthesize progressiveDownloadProgress = _progressiveDownloadProgress; + +#pragma mark - Static + ++ (AFDownloadRequestOperation *)downloadOperationWithRequest:(NSURLRequest *)urlRequest + targetPath:(NSString *)targetPath + shouldResume:(BOOL)shouldResume + success:(void (^)(NSURLRequest *request, NSString *filePath))success + failure:(void (^)(NSURLRequest *request, NSError *error))failure +{ + AFDownloadRequestOperation *requestOperation = [[self alloc] initWithRequest:urlRequest targetPath:(NSString *)targetPath shouldResume:shouldResume]; + + [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { + if (success) { + success(operation.request, ((AFDownloadRequestOperation *)operation).targetPath); + } + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { + if (failure) { + failure(operation.request, error); + } + }]; + + return requestOperation; +} + ++ (NSString *)cacheFolder { + static NSString *cacheFolder; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *cacheDir = NSTemporaryDirectory(); + cacheFolder = [[cacheDir stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadFolderName] retain]; + + // ensure all cache directories are there (needed only once) + NSError *error = nil; + NSFileManager *fileMan = [[NSFileManager alloc] init]; + if(![fileMan createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error]) { + NSLog(@"Failed to create cache directory at %@", cacheFolder); + [fileMan release]; + } + }); + return cacheFolder; +} + +// calculates the MD5 hash of a key ++ (NSString *)md5StringForString:(NSString *)string { + const char *str = [string UTF8String]; + unsigned char r[CC_MD5_DIGEST_LENGTH]; + CC_MD5(str, strlen(str), r); + return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; +} + +#pragma mark - Private + +- (unsigned long long)fileSizeForPath:(NSString *)path { + signed long long fileSize = 0; + NSFileManager *fileManager = [[NSFileManager alloc] init]; // not thread safe + if ([fileManager fileExistsAtPath:path]) { + NSError *error = nil; + NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error]; + if (!error && fileDict) { + fileSize = [fileDict fileSize]; + } + } + [fileManager release]; + return fileSize; +} + +#pragma mark - NSObject + +- (id)initWithRequest:(NSURLRequest *)urlRequest targetPath:(NSString *)targetPath shouldResume:(BOOL)shouldResume { + if ((self = [super initWithRequest:urlRequest])) { + NSParameterAssert(targetPath != nil && urlRequest != nil); + _shouldResume = shouldResume; + + // we assume that at least the directory has to exist on the targetPath + BOOL isDirectory; + if(![[NSFileManager defaultManager] fileExistsAtPath:targetPath isDirectory:&isDirectory]) { + isDirectory = NO; + } + // if targetPath is a directory, use the file name we got from the urlRequest. + if (isDirectory) { + NSString *fileName = [urlRequest.URL lastPathComponent]; + _targetPath = [[NSString pathWithComponents:[NSArray arrayWithObjects:targetPath, fileName, nil]] retain]; + }else { + _targetPath = [targetPath retain]; + } + + // download is saved into a temporal file and remaned upon completion + NSString *tempPath = [self tempPath]; + + // do we need to resume the file? + BOOL isResuming = NO; + if (shouldResume) { + unsigned long long downloadedBytes = [self fileSizeForPath:tempPath]; + if (downloadedBytes > 0) { + NSMutableURLRequest *mutableURLRequest = [urlRequest mutableCopy]; + NSString *requestRange = [NSString stringWithFormat:@"bytes=%llu-", downloadedBytes]; + [mutableURLRequest setValue:requestRange forHTTPHeaderField:@"Range"]; + self.request = mutableURLRequest; + isResuming = YES; + } + } + + // try to create/open a file at the target location + if (!isResuming) { + int fileDescriptor = open([tempPath UTF8String], O_CREAT | O_EXCL | O_RDWR, 0666); + if (fileDescriptor > 0) { + close(fileDescriptor); + } + } + + self.outputStream = [NSOutputStream outputStreamToFileAtPath:tempPath append:isResuming]; + + // if the output stream can't be created, instantly destroy the object. + if (!self.outputStream) { + [self release]; + return nil; + } + } + return self; +} + +- (void)dealloc { + [_progressiveDownloadProgress release]; + [_targetPath release]; + [super dealloc]; +} + +#pragma mark - Public + +- (BOOL)deleteTempFileWithError:(NSError **)error { + NSFileManager *fileManager = [[NSFileManager alloc] init]; + BOOL success = YES; + @synchronized(self) { + NSString *tempPath = [self tempPath]; + if ([fileManager fileExistsAtPath:tempPath]) { + success = [fileManager removeItemAtPath:[self tempPath] error:error]; + } + } + [fileManager release]; + return success; +} + +- (NSString *)tempPath { + NSString *tempPath = nil; + if (self.targetPath) { + NSString *md5URLString = [[self class] md5StringForString:self.targetPath]; + tempPath = [[[self class] cacheFolder] stringByAppendingPathComponent:md5URLString]; + } + return tempPath; +} + + +- (void)setProgressiveDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile))block { + self.progressiveDownloadProgress = block; +} + +#pragma mark - AFURLRequestOperation + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if([self isCancelled]) { + // should we clean up? most likely we don't. + if (self.isDeletingTempFileOnCancel) { + [self deleteTempFileWithError:&_fileError]; + } + return; + }else { + // move file to final position and capture error + @synchronized(self) { + NSFileManager *fileManager = [[NSFileManager alloc] init]; + [fileManager moveItemAtPath:[self tempPath] toPath:_targetPath error:&_fileError]; + [fileManager release]; + } + } + + if (self.error) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } else { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, _targetPath); + }); + } + }; +} + +- (NSError *)error { + if (_fileError) { + return _fileError; + } else { + return [super error]; + } +} + +#pragma mark - NSURLConnectionDelegate + +- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { + [super connection:connection didReceiveResponse:response]; + + // check if we have the correct response + 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 + } + } + } + + self.offsetContentLength = MAX(fileOffset, 0); + self.totalContentLength = totalContentLength; + [self.outputStream setProperty:[NSNumber numberWithLongLong:_offsetContentLength] forKey:NSStreamFileCurrentOffsetKey]; +} + +- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { + [super connection:connection didReceiveData:data]; + + if (self.progressiveDownloadProgress) { + self.progressiveDownloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength,self.totalBytesRead + self.offsetContentLength, self.totalContentLength); + } +} + +@end From 2b2558a90e7bfb2ef5169b1ca21b91851e5a5b7d Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Mar 2012 01:50:02 -0700 Subject: [PATCH 24/41] fixes an integer overflow for files > 2GB --- AFNetworking/AFURLConnectionOperation.h | 6 +++--- AFNetworking/AFURLConnectionOperation.m | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 4eaee7f..86f188c 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -89,7 +89,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; NSError *_error; NSData *_responseData; - NSInteger _totalBytesRead; + long long _totalBytesRead; NSMutableData *_dataAccumulator; NSOutputStream *_outputStream; } @@ -180,7 +180,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @see setDownloadProgressBlock */ -- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block; +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; /** Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. @@ -189,7 +189,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @see setUploadProgressBlock */ -- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block; +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; ///------------------------------------------------- /// @name Setting NSURLConnection Delegate Callbacks diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 172595a..faf32c4 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -40,7 +40,7 @@ NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; -typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, NSInteger totalBytes, NSInteger totalBytesExpected); +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); typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); @@ -92,7 +92,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @property (readwrite, nonatomic, retain) NSError *error; @property (readwrite, nonatomic, retain) NSData *responseData; @property (readwrite, nonatomic, copy) NSString *responseString; -@property (readwrite, nonatomic, assign) NSInteger totalBytesRead; +@property (readwrite, nonatomic, assign) long long totalBytesRead; @property (readwrite, nonatomic, retain) NSMutableData *dataAccumulator; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @@ -229,11 +229,11 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.request = mutableRequest; } -- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block { +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { self.uploadProgress = block; } -- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block { +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { self.downloadProgress = block; } @@ -468,7 +468,7 @@ didReceiveResponse:(NSURLResponse *)response } if (self.downloadProgress) { - self.downloadProgress([data length], self.totalBytesRead, (NSInteger)self.response.expectedContentLength); + self.downloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength); } } From 6f63157e33149226bfc2f014748cb5ef5c9a3a9b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 25 Mar 2012 02:19:33 -0700 Subject: [PATCH 25/41] fixes a few memory leaks. ARC is already starting to cripple my memory management code. Time to make the switch on AFNetworking. --- AFNetworking/AFDownloadRequestOperation.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m index a1ab142..ca33ff1 100644 --- a/AFNetworking/AFDownloadRequestOperation.m +++ b/AFNetworking/AFDownloadRequestOperation.m @@ -60,7 +60,7 @@ typedef void (^AFURLConnectionProgressiveOperationProgressBlock)(NSInteger bytes success:(void (^)(NSURLRequest *request, NSString *filePath))success failure:(void (^)(NSURLRequest *request, NSError *error))failure { - AFDownloadRequestOperation *requestOperation = [[self alloc] initWithRequest:urlRequest targetPath:(NSString *)targetPath shouldResume:shouldResume]; + AFDownloadRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest targetPath:(NSString *)targetPath shouldResume:shouldResume] autorelease]; [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { if (success) { @@ -87,8 +87,8 @@ typedef void (^AFURLConnectionProgressiveOperationProgressBlock)(NSInteger bytes NSFileManager *fileMan = [[NSFileManager alloc] init]; if(![fileMan createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error]) { NSLog(@"Failed to create cache directory at %@", cacheFolder); - [fileMan release]; } + [fileMan release]; }); return cacheFolder; } @@ -146,7 +146,7 @@ typedef void (^AFURLConnectionProgressiveOperationProgressBlock)(NSInteger bytes if (shouldResume) { unsigned long long downloadedBytes = [self fileSizeForPath:tempPath]; if (downloadedBytes > 0) { - NSMutableURLRequest *mutableURLRequest = [urlRequest mutableCopy]; + NSMutableURLRequest *mutableURLRequest = [[urlRequest mutableCopy] autorelease]; NSString *requestRange = [NSString stringWithFormat:@"bytes=%llu-", downloadedBytes]; [mutableURLRequest setValue:requestRange forHTTPHeaderField:@"Range"]; self.request = mutableURLRequest; From f2ae5c40ba087957883f77c826834d9068bc0981 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 25 Mar 2012 11:14:17 -0700 Subject: [PATCH 26/41] Stashing re-implementation of resumeable streaming downloads into AFURLConnectionOperation --- AFNetworking/AFURLConnectionOperation.h | 27 ++++++ AFNetworking/AFURLConnectionOperation.m | 106 ++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 86f188c..2344c14 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -39,6 +39,11 @@ extern NSString * const AFNetworkingOperationDidStartNotification; */ extern NSString * const AFNetworkingOperationDidFinishNotification; +/** + + */ +extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; + /** `AFURLConnectionOperation` is an `NSOperation` that implements NSURLConnection delegate methods. @@ -138,6 +143,11 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; */ @property (readonly, nonatomic, copy) NSString *responseString; +/** + + */ +@property (readonly, nonatomic, copy) NSString *responseFilePath; + ///------------------------ /// @name Accessing Streams ///------------------------ @@ -169,6 +179,23 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; */ - (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 ///--------------------------------- diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index faf32c4..7a0c30e 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -40,6 +40,8 @@ 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); @@ -83,6 +85,38 @@ 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; @@ -92,6 +126,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @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, retain) NSMutableData *dataAccumulator; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @@ -113,6 +148,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @synthesize error = _error; @synthesize responseData = _responseData; @synthesize responseString = _responseString; +@synthesize responseFilePath = _responseFilePath; @synthesize totalBytesRead = _totalBytesRead; @synthesize dataAccumulator = _dataAccumulator; @dynamic inputStream; @@ -229,6 +265,40 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat 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; + } + } + + NSLog(@"Request: %@", [self.request allHTTPHeaderFields]); + + 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; } @@ -451,6 +521,28 @@ didReceiveResponse:(NSURLResponse *)response NSUInteger capacity = MIN(maxCapacity, kAFHTTPMaximumInitialDataCapacity); self.dataAccumulator = [NSMutableData dataWithCapacity:capacity]; } + + 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 @@ -474,7 +566,21 @@ didReceiveResponse:(NSURLResponse *)response - (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection { if (self.outputStream) { + + if (self.responseFilePath) { + NSLog(@"responseFilePath"); + @synchronized(self) { + NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]]; + NSLog(@"temporaryFilePath: %@", temporaryFilePath); + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + [fileManager moveItemAtPath:temporaryFilePath toPath:self.responseFilePath error:&_error]; + NSLog(@"Error: %@", _error); + } + } + [self.outputStream close]; + } else { self.responseData = [NSData dataWithData:self.dataAccumulator]; [_dataAccumulator release]; _dataAccumulator = nil; From 7bd7ee609b45dc42def431b4377831afc9e0baa4 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 25 Mar 2012 11:47:51 -0700 Subject: [PATCH 27/41] Replacing NSMutableData accumulator with NSOutputStream --- AFNetworking/AFURLConnectionOperation.h | 3 +- AFNetworking/AFURLConnectionOperation.m | 75 ++++++++++--------------- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 2344c14..9a7f34e 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -80,7 +80,7 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; @warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5. */ -@interface AFURLConnectionOperation : NSOperation { +@interface AFURLConnectionOperation : NSOperation { @private unsigned short _state; BOOL _cancelled; @@ -95,7 +95,6 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; NSData *_responseData; long long _totalBytesRead; - NSMutableData *_dataAccumulator; NSOutputStream *_outputStream; } diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 7a0c30e..b196ae6 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -30,9 +30,6 @@ typedef enum { typedef unsigned short AFOperationState; -static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; -static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; - static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; @@ -128,7 +125,6 @@ static NSString * AFIncompleteDownloadDirectory() { @property (readwrite, nonatomic, copy) NSString *responseString; @property (readwrite, nonatomic, copy) NSString *responseFilePath; @property (readwrite, nonatomic, assign) long long totalBytesRead; -@property (readwrite, nonatomic, retain) NSMutableData *dataAccumulator; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace; @@ -150,7 +146,6 @@ static NSString * AFIncompleteDownloadDirectory() { @synthesize responseString = _responseString; @synthesize responseFilePath = _responseFilePath; @synthesize totalBytesRead = _totalBytesRead; -@synthesize dataAccumulator = _dataAccumulator; @dynamic inputStream; @synthesize outputStream = _outputStream; @synthesize uploadProgress = _uploadProgress; @@ -201,7 +196,10 @@ static NSString * AFIncompleteDownloadDirectory() { self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; - + + self.outputStream = [NSOutputStream outputStreamToMemory]; + self.outputStream.delegate = self; + self.state = AFHTTPOperationReadyState; return self; @@ -218,7 +216,6 @@ static NSString * AFIncompleteDownloadDirectory() { [_responseData release]; [_responseString release]; - [_dataAccumulator release]; if (_outputStream) { [_outputStream close]; @@ -289,9 +286,7 @@ static NSString * AFIncompleteDownloadDirectory() { self.request = mutableURLRequest; } } - - NSLog(@"Request: %@", [self.request allHTTPHeaderFields]); - + self.outputStream = [NSOutputStream outputStreamToFileAtPath:temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]]; } @@ -516,10 +511,6 @@ didReceiveResponse:(NSURLResponse *)response if (self.outputStream) { [self.outputStream open]; - } else { - NSUInteger maxCapacity = MAX((NSUInteger)llabs(response.expectedContentLength), kAFHTTPMinimumInitialDataCapacity); - NSUInteger capacity = MIN(maxCapacity, kAFHTTPMaximumInitialDataCapacity); - self.dataAccumulator = [NSMutableData dataWithCapacity:capacity]; } NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response; @@ -550,13 +541,9 @@ didReceiveResponse:(NSURLResponse *)response { self.totalBytesRead += [data length]; - if (self.outputStream) { - if ([self.outputStream hasSpaceAvailable]) { - const uint8_t *dataBuffer = (uint8_t *) [data bytes]; - [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; - } - } else { - [self.dataAccumulator appendData:data]; + if ([self.outputStream hasSpaceAvailable]) { + const uint8_t *dataBuffer = (uint8_t *) [data bytes]; + [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; } if (self.downloadProgress) { @@ -565,27 +552,18 @@ didReceiveResponse:(NSURLResponse *)response } - (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection { - if (self.outputStream) { - - if (self.responseFilePath) { - NSLog(@"responseFilePath"); - @synchronized(self) { - NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]]; - NSLog(@"temporaryFilePath: %@", temporaryFilePath); - - NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; - [fileManager moveItemAtPath:temporaryFilePath toPath:self.responseFilePath error:&_error]; - NSLog(@"Error: %@", _error); - } + 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]; } - - [self.outputStream close]; - - } else { - self.responseData = [NSData dataWithData:self.dataAccumulator]; - [_dataAccumulator release]; _dataAccumulator = nil; } + self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + + [self.outputStream close]; + [self finish]; self.connection = nil; @@ -596,11 +574,7 @@ didReceiveResponse:(NSURLResponse *)response { self.error = error; - if (self.outputStream) { - [self.outputStream close]; - } else { - [_dataAccumulator release]; _dataAccumulator = nil; - } + [self.outputStream close]; [self finish]; @@ -621,4 +595,17 @@ didReceiveResponse:(NSURLResponse *)response } } +#pragma mark - NSStreamDelegate + +- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { + switch (eventCode) { + case NSStreamEventErrorOccurred: + self.error = [stream streamError]; + [self cancel]; + break; + default: + break; + } +} + @end From 331d7bcbf2e19d2c275b5b1c8b0dd51a64e66ea4 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 25 Mar 2012 12:23:40 -0700 Subject: [PATCH 28/41] Moving resumeable streaming download functionality to AFHTTPRequestOperation --- AFNetworking/AFHTTPRequestOperation.h | 21 +++++ AFNetworking/AFHTTPRequestOperation.m | 117 +++++++++++++++++++++++- AFNetworking/AFURLConnectionOperation.h | 22 ----- AFNetworking/AFURLConnectionOperation.m | 100 +------------------- 4 files changed, 134 insertions(+), 126 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 122099e..d46d1ba 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -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 ///----------------------------------------------------------- diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 871cb18..84e4549 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -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 diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 9a7f34e..872476d 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -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 ///--------------------------------- diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index b196ae6..56a9854 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -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]; From c40983e56ffdd6d98d896e9b45734b4ea46c7ce9 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Mon, 26 Mar 2012 11:29:31 -0700 Subject: [PATCH 29/41] Adding -temporaryPath property to AFHTTPRequestOperation --- AFNetworking/AFHTTPRequestOperation.m | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 84e4549..48618ad 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -92,6 +92,7 @@ static NSString * AFIncompleteDownloadDirectory() { @property (readwrite, nonatomic, retain) NSURLRequest *request; @property (readwrite, nonatomic, retain) NSError *HTTPError; @property (readwrite, nonatomic, copy) NSString *responseFilePath; +@property (readonly) NSString *temporaryFilePath; @end @implementation AFHTTPRequestOperation @@ -204,11 +205,9 @@ static NSString * AFIncompleteDownloadDirectory() { } else { self.responseFilePath = path; } - - NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]]; - + if (shouldResume) { - unsigned long long downloadedBytes = AFFileSizeForPath(temporaryFilePath); + unsigned long long downloadedBytes = AFFileSizeForPath(self.temporaryFilePath); if (downloadedBytes > 0) { NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes] forHTTPHeaderField:@"Range"]; @@ -216,7 +215,11 @@ static NSString * AFIncompleteDownloadDirectory() { } } - self.outputStream = [NSOutputStream outputStreamToFileAtPath:temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]]; + self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]]; +} + +- (NSString *)temporaryFilePath { + return [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]]; } - (BOOL)deleteTemporaryFileWithError:(NSError **)error { From ca697ce3007caa1c3c4bd6d6b6358f333fb009f5 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 27 Mar 2012 11:01:20 -0700 Subject: [PATCH 30/41] Settling on a relatively stable implementation of pause/resume --- AFNetworking/AFHTTPRequestOperation.m | 65 ++++++++++++++----------- AFNetworking/AFURLConnectionOperation.h | 9 +++- AFNetworking/AFURLConnectionOperation.m | 63 +++++++++++++++++++++--- 3 files changed, 101 insertions(+), 36 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 441785b..9bfb8c8 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -58,7 +58,6 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL Method originalMethod = class_getClassMethod(klass, selector); IMP implementation = imp_implementationWithBlock(block); class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod)); - } static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { @@ -127,6 +126,7 @@ static NSString * AFIncompleteDownloadDirectory() { @interface AFHTTPRequestOperation () @property (readwrite, nonatomic, retain) NSURLRequest *request; +@property (readwrite, nonatomic, retain) NSHTTPURLResponse *response; @property (readwrite, nonatomic, retain) NSError *HTTPError; @property (readwrite, nonatomic, copy) NSString *responseFilePath; @property (readonly) NSString *temporaryFilePath; @@ -180,6 +180,24 @@ static NSString * AFIncompleteDownloadDirectory() { } } +- (void)pause { + unsigned long long offset = 0; + if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { + offset = [[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue]; + } else { + offset = [[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length]; + } + + NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; + if ([[self.response allHeaderFields] valueForKey:@"ETag"]) { + [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"]; + } + [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"]; + self.request = mutableURLRequest; + + [super pause]; +} + - (BOOL)hasAcceptableStatusCode { return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]]; } @@ -228,14 +246,14 @@ static NSString * AFIncompleteDownloadDirectory() { self.responseFilePath = path; } - if (shouldResume) { - unsigned long long downloadedBytes = AFFileSizeForPath(self.temporaryFilePath); - if (downloadedBytes > 0) { - NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; - [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes] forHTTPHeaderField:@"Range"]; - self.request = mutableURLRequest; - } - } +// if (shouldResume) { +// unsigned long long downloadedBytes = AFFileSizeForPath(self.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:self.temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]]; } @@ -311,34 +329,25 @@ static NSString * AFIncompleteDownloadDirectory() { - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { - [super connection:connection didReceiveResponse:response]; + self.response = (NSHTTPURLResponse *)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 + if ([self.response statusCode] != 206) { + if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { + [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; + } else { + if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) { + self.outputStream = [NSOutputStream outputStreamToMemory]; } } } - unsigned long long offsetContentLength = MAX(fileOffset, 0); - [self.outputStream setProperty:[NSNumber numberWithLongLong:offsetContentLength] forKey:NSStreamFileCurrentOffsetKey]; + [self.outputStream open]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [super connectionDidFinishLoading:connection]; - - if (self.responseFilePath) { + + if (self.responseFilePath && ![self isCancelled]) { @synchronized(self) { NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]]; NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 872476d..3aa8a36 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -82,7 +82,7 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; */ @interface AFURLConnectionOperation : NSOperation { @private - unsigned short _state; + signed short _state; BOOL _cancelled; NSRecursiveLock *_lock; @@ -156,7 +156,7 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; /** The output stream that is used to write data received until the request is finished. - @discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. + @discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set. */ @property (nonatomic, retain) NSOutputStream *outputStream; @@ -173,6 +173,11 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; */ - (id)initWithRequest:(NSURLRequest *)urlRequest; +- (void)pause; +- (BOOL)isPaused; + +- (void)resume; + ///--------------------------------- /// @name Setting Progress Callbacks ///--------------------------------- diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 56a9854..a793a6e 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -23,12 +23,13 @@ #import "AFURLConnectionOperation.h" typedef enum { + AFHTTPOperationPausedState = -1, AFHTTPOperationReadyState = 1, AFHTTPOperationExecutingState = 2, AFHTTPOperationFinishedState = 3, } _AFOperationState; -typedef unsigned short AFOperationState; +typedef signed short AFOperationState; static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; @@ -50,6 +51,8 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { return @"isExecuting"; case AFHTTPOperationFinishedState: return @"isFinished"; + case AFHTTPOperationPausedState: + return @"isPaused"; default: return @"state"; } @@ -59,6 +62,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat switch (fromState) { case AFHTTPOperationReadyState: switch (toState) { + case AFHTTPOperationPausedState: case AFHTTPOperationExecutingState: return YES; case AFHTTPOperationFinishedState: @@ -68,6 +72,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } case AFHTTPOperationExecutingState: switch (toState) { + case AFHTTPOperationPausedState: case AFHTTPOperationFinishedState: return YES; default: @@ -75,6 +80,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } case AFHTTPOperationFinishedState: return NO; + case AFHTTPOperationPausedState: + return toState == AFHTTPOperationReadyState; default: return YES; } @@ -164,6 +171,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.outputStream = [NSOutputStream outputStreamToMemory]; self.outputStream.delegate = self; + [self.outputStream setProperty:@"Foo bar" forKey:@"Test"]; + self.state = AFHTTPOperationReadyState; return self; @@ -221,9 +230,24 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } - (void)setInputStream:(NSInputStream *)inputStream { + [self willChangeValueForKey:@"inputStream"]; NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; + [self didChangeValueForKey:@"inputStream"]; +} + +- (void)setOutputStream:(NSOutputStream *)outputStream { + [self willChangeValueForKey:@"outputStream"]; + [outputStream retain]; + [_outputStream release]; + _outputStream = outputStream; + [self didChangeValueForKey:@"outputStream"]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } } - (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { @@ -287,6 +311,35 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat return _responseString; } +- (void)pause { + if ([self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationPausedState; + + [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + [self.lock unlock]; +} + +- (BOOL)isPaused { + return self.state == AFHTTPOperationPausedState; +} + +- (void)resume { + if (![self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationReadyState; + + [self start]; + [self.lock unlock]; +} + #pragma mark - NSOperation - (BOOL)isReady { @@ -439,15 +492,13 @@ totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite - (void)connection:(NSURLConnection *)__unused connection didReceiveResponse:(NSURLResponse *)response { - self.response = (NSHTTPURLResponse *)response; + self.response = response; - if (self.outputStream) { - [self.outputStream open]; - } + [self.outputStream open]; } - (void)connection:(NSURLConnection *)__unused connection - didReceiveData:(NSData *)data + didReceiveData:(NSData *)data { self.totalBytesRead += [data length]; From 2a1d81a79221dada4eefe8e8d0bd2090f4326181 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 27 Mar 2012 12:05:00 -0700 Subject: [PATCH 31/41] Cleaning up experimental bits Re-creating AFDownloadRequestOperation, now with an API closer to NSURLDownload --- AFNetworking/AFDownloadRequestOperation.h | 98 +++---- AFNetworking/AFDownloadRequestOperation.m | 296 ++++------------------ AFNetworking/AFHTTPRequestOperation.h | 17 -- AFNetworking/AFHTTPRequestOperation.m | 79 ------ AFNetworking/AFURLConnectionOperation.h | 11 +- AFNetworking/AFURLConnectionOperation.m | 18 +- 6 files changed, 86 insertions(+), 433 deletions(-) diff --git a/AFNetworking/AFDownloadRequestOperation.h b/AFNetworking/AFDownloadRequestOperation.h index ef12c8d..512cc1b 100644 --- a/AFNetworking/AFDownloadRequestOperation.h +++ b/AFNetworking/AFDownloadRequestOperation.h @@ -1,6 +1,6 @@ // AFDownloadRequestOperation.h // -// Copyright (c) 2012 Peter Steinberger (http://petersteinberger.com) +// Copyright (c) 2012 Mattt Thompson (http://mattt.me) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -27,82 +27,42 @@ /** `AFDownloadRequestOperation` is a subclass of `AFHTTPRequestOperation` for streamed file downloading. Supports Content-Range. (http://tools.ietf.org/html/rfc2616#section-14.16) */ -@interface AFDownloadRequestOperation : AFHTTPRequestOperation +@interface AFDownloadRequestOperation : AFURLConnectionOperation { +@private + NSString *_responsePath; + NSError *_downloadError; + NSString *_destination; + BOOL _allowOverwrite; + BOOL _deletesFileUponFailure; +} -/** - A String value that defines the target path or directory. - - We try to be clever here and understand both a directory or a filename. - The target directory should already be create, or the download fill fail. - - If the target is a directory, we use the last part of the URL as a default file name. - */ -@property (retain) NSString *targetPath; - -/** - A Boolean value that indicates if we should try to resume the download. Defaults is `YES`. - - Can only be set while creating the request. - */ -@property (assign, readonly) BOOL shouldResume; - -/** - Deletes the temporary file if operations is cancelled. Defaults to `NO`. - */ -@property (assign, getter=isDeletingTempFileOnCancel) BOOL deleteTempFileOnCancel; - -/** - Expected total length. This is different than expectedContentLength if the file is resumed. - - Note: this can also be zero if the file size is not sent (*) - */ -@property (assign, readonly) long long totalContentLength; - -/** - Indicator for the file offset on partial downloads. This is greater than zero if the file download is resumed. - */ -@property (assign, readonly) long long offsetContentLength; - -///---------------------------------- -/// @name Creating Request Operations -///---------------------------------- +@property (readonly, nonatomic, copy) NSString *responsePath; /** - Creates and returns an `AFDownloadRequestOperation` object and sets the specified success and failure callbacks. - @param urlRequest The request object to be loaded asynchronously during execution of the operation - @param success A block object to be executed when the operation finishes successfully. This block has no return value and takes two arguments: the request sent from the client, and the filePath where the file is saved. - @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while saving/moving the file. This block has no return value and takes two arguments: the request sent from the client, and the error describing the network or file saving error that occurred. - - @return A new download request operation */ -+ (AFDownloadRequestOperation *)downloadOperationWithRequest:(NSURLRequest *)urlRequest - targetPath:(NSString *)targetPath - shouldResume:(BOOL)shouldResume - success:(void (^)(NSURLRequest *request, NSString *filePath))success - failure:(void (^)(NSURLRequest *request, NSError *error))failure; - -- (id)initWithRequest:(NSURLRequest *)urlRequest targetPath:(NSString *)targetPath shouldResume:(BOOL)shouldResume; - -/** - Deletes the temporary file. - - Returns `NO` if an error happened, `YES` if the file is removed or did not exist in the first place. - */ -- (BOOL)deleteTempFileWithError:(NSError **)error; - -/** - Returns the path used for the temporary file. Returns `nil` if the targetPath has not been set. - */ -- (NSString *)tempPath; +- (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite; /** - Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. This is a variant of setDownloadProgressBlock that adds support for progressive downloads and adds the - @param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes five arguments: the number of bytes read since the last time the download progress block was called, the bytes expected to be read during the request, the bytes already read during this request, the total bytes read (including from previous partial downloads), and the total bytes expected to be read for the file. This block may be called multiple times. - - @see setDownloadProgressBlock */ -- (void)setProgressiveDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile))block; +- (BOOL)deletesFileUponFailure; + +/** + + */ +- (void)setDeletesFileUponFailure:(BOOL)deletesFileUponFailure; + + +///** +// +// */ +//- (void)setDecideDestinationWithSuggestedFilenameBlock:(void (^)(NSString *filename))block; +// +///** +// +// */ +//- (void)setShouldDecodeSourceDataOfMimeTypeBlock:(BOOL (^)(NSString *encodingType))block; +// @end diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m index ca33ff1..bd5f0df 100644 --- a/AFNetworking/AFDownloadRequestOperation.m +++ b/AFNetworking/AFDownloadRequestOperation.m @@ -1,6 +1,6 @@ // AFDownloadRequestOperation.m // -// Copyright (c) 2012 Peter Steinberger (http://petersteinberger.com) +// Copyright (c) 2012 Mattt Thompson (http://mattt.me) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,269 +22,75 @@ #import "AFDownloadRequestOperation.h" #import "AFURLConnectionOperation.h" -#import -#include -#include -@interface AFURLConnectionOperation (AFInternal) -@property (nonatomic, strong) NSURLRequest *request; -@property (readonly, nonatomic, assign) long long totalBytesRead; -@end - -typedef void (^AFURLConnectionProgressiveOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile); - -@interface AFDownloadRequestOperation() { - NSError *_fileError; -} -@property (nonatomic, retain) NSString *tempPath; -@property (assign) long long totalContentLength; -@property (assign) long long offsetContentLength; -@property (nonatomic, copy) AFURLConnectionProgressiveOperationProgressBlock progressiveDownloadProgress; +@interface AFDownloadRequestOperation() +@property (readwrite, nonatomic, copy) NSString *responsePath; +@property (readwrite, nonatomic, retain) NSError *downloadError; +@property (readwrite, nonatomic, copy) NSString *destination; +@property (readwrite, nonatomic, assign) BOOL allowOverwrite; +@property (readwrite, nonatomic, assign) BOOL deletesFileUponFailure; @end @implementation AFDownloadRequestOperation - -@synthesize targetPath = _targetPath; -@synthesize tempPath = _tempPath; -@synthesize totalContentLength = _totalContentLength; -@synthesize offsetContentLength = _offsetContentLength; -@synthesize shouldResume = _shouldResume; -@synthesize deleteTempFileOnCancel = _deleteTempFileOnCancel; -@synthesize progressiveDownloadProgress = _progressiveDownloadProgress; - -#pragma mark - Static - -+ (AFDownloadRequestOperation *)downloadOperationWithRequest:(NSURLRequest *)urlRequest - targetPath:(NSString *)targetPath - shouldResume:(BOOL)shouldResume - success:(void (^)(NSURLRequest *request, NSString *filePath))success - failure:(void (^)(NSURLRequest *request, NSError *error))failure -{ - AFDownloadRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest targetPath:(NSString *)targetPath shouldResume:shouldResume] autorelease]; - - [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { - if (success) { - success(operation.request, ((AFDownloadRequestOperation *)operation).targetPath); - } - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { - if (failure) { - failure(operation.request, error); - } - }]; - - return requestOperation; -} - -+ (NSString *)cacheFolder { - static NSString *cacheFolder; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *cacheDir = NSTemporaryDirectory(); - cacheFolder = [[cacheDir stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadFolderName] retain]; - - // ensure all cache directories are there (needed only once) - NSError *error = nil; - NSFileManager *fileMan = [[NSFileManager alloc] init]; - if(![fileMan createDirectoryAtPath:cacheFolder withIntermediateDirectories:YES attributes:nil error:&error]) { - NSLog(@"Failed to create cache directory at %@", cacheFolder); - } - [fileMan release]; - }); - return cacheFolder; -} - -// calculates the MD5 hash of a key -+ (NSString *)md5StringForString:(NSString *)string { - const char *str = [string UTF8String]; - unsigned char r[CC_MD5_DIGEST_LENGTH]; - CC_MD5(str, strlen(str), r); - return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", - r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; -} - -#pragma mark - Private - -- (unsigned long long)fileSizeForPath:(NSString *)path { - signed long long fileSize = 0; - NSFileManager *fileManager = [[NSFileManager alloc] init]; // not thread safe - if ([fileManager fileExistsAtPath:path]) { - NSError *error = nil; - NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error]; - if (!error && fileDict) { - fileSize = [fileDict fileSize]; - } - } - [fileManager release]; - return fileSize; -} - -#pragma mark - NSObject - -- (id)initWithRequest:(NSURLRequest *)urlRequest targetPath:(NSString *)targetPath shouldResume:(BOOL)shouldResume { - if ((self = [super initWithRequest:urlRequest])) { - NSParameterAssert(targetPath != nil && urlRequest != nil); - _shouldResume = shouldResume; - - // we assume that at least the directory has to exist on the targetPath - BOOL isDirectory; - if(![[NSFileManager defaultManager] fileExistsAtPath:targetPath isDirectory:&isDirectory]) { - isDirectory = NO; - } - // if targetPath is a directory, use the file name we got from the urlRequest. - if (isDirectory) { - NSString *fileName = [urlRequest.URL lastPathComponent]; - _targetPath = [[NSString pathWithComponents:[NSArray arrayWithObjects:targetPath, fileName, nil]] retain]; - }else { - _targetPath = [targetPath retain]; - } - - // download is saved into a temporal file and remaned upon completion - NSString *tempPath = [self tempPath]; - - // do we need to resume the file? - BOOL isResuming = NO; - if (shouldResume) { - unsigned long long downloadedBytes = [self fileSizeForPath:tempPath]; - if (downloadedBytes > 0) { - NSMutableURLRequest *mutableURLRequest = [[urlRequest mutableCopy] autorelease]; - NSString *requestRange = [NSString stringWithFormat:@"bytes=%llu-", downloadedBytes]; - [mutableURLRequest setValue:requestRange forHTTPHeaderField:@"Range"]; - self.request = mutableURLRequest; - isResuming = YES; - } - } - - // try to create/open a file at the target location - if (!isResuming) { - int fileDescriptor = open([tempPath UTF8String], O_CREAT | O_EXCL | O_RDWR, 0666); - if (fileDescriptor > 0) { - close(fileDescriptor); - } - } - - self.outputStream = [NSOutputStream outputStreamToFileAtPath:tempPath append:isResuming]; - - // if the output stream can't be created, instantly destroy the object. - if (!self.outputStream) { - [self release]; - return nil; - } - } - return self; -} +@synthesize responsePath = _responsePath; +@synthesize downloadError = _downloadError; +@synthesize destination = _destination; +@synthesize allowOverwrite = _allowOverwrite; +@synthesize deletesFileUponFailure = _deletesFileUponFailure; - (void)dealloc { - [_progressiveDownloadProgress release]; - [_targetPath release]; + [_responsePath release]; + [_downloadError release]; + [_destination release]; [super dealloc]; } -#pragma mark - Public - -- (BOOL)deleteTempFileWithError:(NSError **)error { - NSFileManager *fileManager = [[NSFileManager alloc] init]; - BOOL success = YES; - @synchronized(self) { - NSString *tempPath = [self tempPath]; - if ([fileManager fileExistsAtPath:tempPath]) { - success = [fileManager removeItemAtPath:[self tempPath] error:error]; - } - } - [fileManager release]; - return success; -} - -- (NSString *)tempPath { - NSString *tempPath = nil; - if (self.targetPath) { - NSString *md5URLString = [[self class] md5StringForString:self.targetPath]; - tempPath = [[[self class] cacheFolder] stringByAppendingPathComponent:md5URLString]; - } - return tempPath; -} - - -- (void)setProgressiveDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpected, long long totalBytesReadForFile, long long totalBytesExpectedToReadForFile))block { - self.progressiveDownloadProgress = block; -} - -#pragma mark - AFURLRequestOperation - -- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - self.completionBlock = ^ { - if([self isCancelled]) { - // should we clean up? most likely we don't. - if (self.isDeletingTempFileOnCancel) { - [self deleteTempFileWithError:&_fileError]; - } - return; - }else { - // move file to final position and capture error - @synchronized(self) { - NSFileManager *fileManager = [[NSFileManager alloc] init]; - [fileManager moveItemAtPath:[self tempPath] toPath:_targetPath error:&_fileError]; - [fileManager release]; - } - } - - if (self.error) { - dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ - failure(self, self.error); - }); - } else { - dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ - success(self, _targetPath); - }); - } - }; -} - - (NSError *)error { - if (_fileError) { - return _fileError; + if (_downloadError) { + return _downloadError; } else { return [super error]; } } -#pragma mark - NSURLConnectionDelegate +#pragma mark - -- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { - [super connection:connection didReceiveResponse:response]; - - // check if we have the correct response - 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 - } - } - } - - self.offsetContentLength = MAX(fileOffset, 0); - self.totalContentLength = totalContentLength; - [self.outputStream setProperty:[NSNumber numberWithLongLong:_offsetContentLength] forKey:NSStreamFileCurrentOffsetKey]; +/** + + */ +- (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite { + [self willChangeValueForKey:@"isReady"]; + self.destination = path; + self.allowOverwrite = allowOverwrite; + [self didChangeValueForKey:@"isReady"]; } -- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { - [super connection:connection didReceiveData:data]; - - if (self.progressiveDownloadProgress) { - self.progressiveDownloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength,self.totalBytesRead + self.offsetContentLength, self.totalContentLength); +#pragma mark - NSOperation + +- (BOOL)isReady { + return [super isReady] && self.destination; +} + +- (void)start { + if ([self isReady]) { + // TODO Create temporary path + self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.destination append:NO]; + + [super start]; } } + +#pragma mark - + +///** +// +// */ +//- (void)setDecideDestinationWithSuggestedFilenameBlock:(void (^)(NSString *filename))block; +// +///** +// +// */ +//- (void)setShouldDecodeSourceDataOfMimeTypeBlock:(BOOL (^)(NSString *encodingType))block; + @end diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 3840b37..4b6a63e 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -120,23 +120,6 @@ extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); */ + (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 ///----------------------------------------------------------- diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 9bfb8c8..be53c61 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -90,38 +90,6 @@ 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 () @@ -129,7 +97,6 @@ static NSString * AFIncompleteDownloadDirectory() { @property (readwrite, nonatomic, retain) NSHTTPURLResponse *response; @property (readwrite, nonatomic, retain) NSError *HTTPError; @property (readwrite, nonatomic, copy) NSString *responseFilePath; -@property (readonly) NSString *temporaryFilePath; @end @implementation AFHTTPRequestOperation @@ -232,40 +199,6 @@ static NSString * AFIncompleteDownloadDirectory() { } } -- (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; - } - -// if (shouldResume) { -// unsigned long long downloadedBytes = AFFileSizeForPath(self.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:self.temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]]; -} - -- (NSString *)temporaryFilePath { - return [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]]; -} - -- (BOOL)deleteTemporaryFileWithError:(NSError **)error { - return NO; -} - - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -344,16 +277,4 @@ didReceiveResponse:(NSURLResponse *)response [self.outputStream open]; } -- (void)connectionDidFinishLoading:(NSURLConnection *)connection { - [super connectionDidFinishLoading:connection]; - - if (self.responseFilePath && ![self isCancelled]) { - @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 diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 3aa8a36..88f717a 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -39,11 +39,6 @@ extern NSString * const AFNetworkingOperationDidStartNotification; */ extern NSString * const AFNetworkingOperationDidFinishNotification; -/** - - */ -extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; - /** `AFURLConnectionOperation` is an `NSOperation` that implements NSURLConnection delegate methods. @@ -80,7 +75,7 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; @warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5. */ -@interface AFURLConnectionOperation : NSOperation { +@interface AFURLConnectionOperation : NSOperation { @private signed short _state; BOOL _cancelled; @@ -173,6 +168,10 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName; */ - (id)initWithRequest:(NSURLRequest *)urlRequest; +///---------------------------------- +/// @name Pausing / Resuming Requests +///---------------------------------- + - (void)pause; - (BOOL)isPaused; diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index a793a6e..669a20a 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -169,10 +169,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.request = urlRequest; self.outputStream = [NSOutputStream outputStreamToMemory]; - self.outputStream.delegate = self; - - [self.outputStream setProperty:@"Foo bar" forKey:@"Test"]; - + self.state = AFHTTPOperationReadyState; return self; @@ -548,17 +545,4 @@ didReceiveResponse:(NSURLResponse *)response } } -#pragma mark - NSStreamDelegate - -- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { - switch (eventCode) { - case NSStreamEventErrorOccurred: - self.error = [stream streamError]; - [self cancel]; - break; - default: - break; - } -} - @end From 4cf0199712d9dbf10d6a419875d128aa8b183e37 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 27 Mar 2012 12:11:46 -0700 Subject: [PATCH 32/41] Reverting AFDownloadRequestOperation to be a subclass of AFHTTPRequestOperation --- AFNetworking/AFDownloadRequestOperation.h | 6 ++---- AFNetworking/AFDownloadRequestOperation.m | 10 ---------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/AFNetworking/AFDownloadRequestOperation.h b/AFNetworking/AFDownloadRequestOperation.h index 512cc1b..a059518 100644 --- a/AFNetworking/AFDownloadRequestOperation.h +++ b/AFNetworking/AFDownloadRequestOperation.h @@ -22,12 +22,10 @@ #import "AFHTTPRequestOperation.h" -#define kAFNetworkingIncompleteDownloadFolderName @"Incomplete" - /** - `AFDownloadRequestOperation` is a subclass of `AFHTTPRequestOperation` for streamed file downloading. Supports Content-Range. (http://tools.ietf.org/html/rfc2616#section-14.16) + */ -@interface AFDownloadRequestOperation : AFURLConnectionOperation { +@interface AFDownloadRequestOperation : AFHTTPRequestOperation { @private NSString *_responsePath; NSError *_downloadError; diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m index bd5f0df..9e3888c 100644 --- a/AFNetworking/AFDownloadRequestOperation.m +++ b/AFNetworking/AFDownloadRequestOperation.m @@ -55,9 +55,6 @@ #pragma mark - -/** - - */ - (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite { [self willChangeValueForKey:@"isReady"]; self.destination = path; @@ -80,17 +77,10 @@ } } - #pragma mark - -///** -// -// */ //- (void)setDecideDestinationWithSuggestedFilenameBlock:(void (^)(NSString *filename))block; // -///** -// -// */ //- (void)setShouldDecodeSourceDataOfMimeTypeBlock:(BOOL (^)(NSString *encodingType))block; @end From e4c84db10bb8e9c9b52f53f6b6f7cb42e2e005f5 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 8 Apr 2012 12:42:44 -0700 Subject: [PATCH 33/41] Removing stray newline --- AFNetworking/AFHTTPRequestOperation.m | 1 - 1 file changed, 1 deletion(-) diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 5fbd11f..df3e7ce 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -57,7 +57,6 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL Method originalMethod = class_getClassMethod(klass, selector); IMP implementation = imp_implementationWithBlock(block); class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod)); - } static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { From f2ce416ca2c138b4b9337970695d4ae06b2287b5 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Sun, 8 Apr 2012 14:15:18 -0700 Subject: [PATCH 34/41] Adding AFDownloadRRequestOperation to project files --- .../AFNetworking Mac Example.xcodeproj/project.pbxproj | 6 ++++++ .../AFNetworking iOS Example.xcodeproj/project.pbxproj | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj b/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj index d06a8c5..5d8a7dd 100644 --- a/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj +++ b/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ F8A27AC9142CFE1300F5E0D6 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F8A27AB9142CFE1300F5E0D6 /* Credits.rtf */; }; F8A27ACA142CFE1300F5E0D6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F8A27ABB142CFE1300F5E0D6 /* MainMenu.xib */; }; F8A27ACB142CFE1300F5E0D6 /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A27AC0142CFE1300F5E0D6 /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc -w -Xanalyzer -analyzer-disable-checker"; }; }; + F8BF7FE4153234BB00885289 /* AFDownloadRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8BF7FE3153234BB00885289 /* AFDownloadRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; F8CEEB6F142CEC6E00247B03 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8CEEB6E142CEC6E00247B03 /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -63,6 +64,8 @@ F8A27AC0142CFE1300F5E0D6 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = ""; }; F8A27AC4142CFE1300F5E0D6 /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = ""; }; F8A27AC5142CFE1300F5E0D6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + F8BF7FE2153234BB00885289 /* AFDownloadRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFDownloadRequestOperation.h; sourceTree = ""; }; + F8BF7FE3153234BB00885289 /* AFDownloadRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFDownloadRequestOperation.m; sourceTree = ""; }; F8CEEB6A142CEC6E00247B03 /* AFNetworking Mac Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AFNetworking Mac Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; F8CEEB6E142CEC6E00247B03 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; F8CEEB71142CEC6E00247B03 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -128,6 +131,8 @@ F87A159E1444926300318955 /* AFURLConnectionOperation.m */, F897DE6B142CFEDC000DDD35 /* AFHTTPRequestOperation.h */, F897DE6C142CFEDC000DDD35 /* AFHTTPRequestOperation.m */, + F8BF7FE2153234BB00885289 /* AFDownloadRequestOperation.h */, + F8BF7FE3153234BB00885289 /* AFDownloadRequestOperation.m */, F897DE71142CFEDC000DDD35 /* AFJSONRequestOperation.h */, F897DE72142CFEDC000DDD35 /* AFJSONRequestOperation.m */, F87A15A01444926900318955 /* AFXMLRequestOperation.h */, @@ -287,6 +292,7 @@ F87A159F1444926300318955 /* AFURLConnectionOperation.m in Sources */, F897DE78142CFEDC000DDD35 /* AFHTTPClient.m in Sources */, F897DE79142CFEDC000DDD35 /* AFHTTPRequestOperation.m in Sources */, + F8BF7FE4153234BB00885289 /* AFDownloadRequestOperation.m in Sources */, F897DE7B142CFEDC000DDD35 /* AFImageRequestOperation.m in Sources */, F897DE7C142CFEDC000DDD35 /* AFJSONRequestOperation.m in Sources */, F87A15A21444926A00318955 /* AFXMLRequestOperation.m in Sources */, diff --git a/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj b/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj index 02b18ea..ebd3f05 100644 --- a/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj +++ b/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + F8BF7FE0153234AE00885289 /* AFDownloadRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8BF7FDF153234AE00885289 /* AFDownloadRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; F8D0701B14310F4A00653FD3 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */; }; F8D0701C14310F4F00653FD3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E013957DF100DB05C8 /* Security.framework */; }; F8DA09E41396AC040057D0CC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F8DA09E31396AC040057D0CC /* main.m */; }; @@ -39,6 +40,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + F8BF7FDE153234AE00885289 /* AFDownloadRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFDownloadRequestOperation.h; sourceTree = ""; }; + F8BF7FDF153234AE00885289 /* AFDownloadRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFDownloadRequestOperation.m; sourceTree = ""; }; F8DA09E31396AC040057D0CC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; }; F8DA09E51396AC220057D0CC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; }; F8DA09E61396AC220057D0CC /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = SOURCE_ROOT; }; @@ -235,6 +238,8 @@ F8FA94AC150EFEC100ED4EAD /* AFURLConnectionOperation.m */, F8FA949E150EFEC100ED4EAD /* AFHTTPRequestOperation.h */, F8FA949F150EFEC100ED4EAD /* AFHTTPRequestOperation.m */, + F8BF7FDE153234AE00885289 /* AFDownloadRequestOperation.h */, + F8BF7FDF153234AE00885289 /* AFDownloadRequestOperation.m */, F8FA94A2150EFEC100ED4EAD /* AFJSONRequestOperation.h */, F8FA94A3150EFEC100ED4EAD /* AFJSONRequestOperation.m */, F8FA94AD150EFEC100ED4EAD /* AFXMLRequestOperation.h */, @@ -331,6 +336,7 @@ F8FA949A150EF9DA00ED4EAD /* PublicTimelineViewController.m in Sources */, F8FA94B1150EFEC100ED4EAD /* AFHTTPClient.m in Sources */, F8FA94B2150EFEC100ED4EAD /* AFHTTPRequestOperation.m in Sources */, + F8BF7FE0153234AE00885289 /* AFDownloadRequestOperation.m in Sources */, F8FA94B3150EFEC100ED4EAD /* AFImageRequestOperation.m in Sources */, F8FA94B4150EFEC100ED4EAD /* AFJSONRequestOperation.m in Sources */, F8FA94B5150EFEC100ED4EAD /* AFJSONUtilities.m in Sources */, From 2413d95c3ffe806d424890c185739d6218a2fb38 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 6 Apr 2012 03:00:04 -0700 Subject: [PATCH 35/41] we don't need to declare ivars anymore. --- AFNetworking/AFDownloadRequestOperation.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/AFNetworking/AFDownloadRequestOperation.h b/AFNetworking/AFDownloadRequestOperation.h index a059518..da0a3cb 100644 --- a/AFNetworking/AFDownloadRequestOperation.h +++ b/AFNetworking/AFDownloadRequestOperation.h @@ -25,14 +25,7 @@ /** */ -@interface AFDownloadRequestOperation : AFHTTPRequestOperation { -@private - NSString *_responsePath; - NSError *_downloadError; - NSString *_destination; - BOOL _allowOverwrite; - BOOL _deletesFileUponFailure; -} +@interface AFDownloadRequestOperation : AFHTTPRequestOperation; @property (readonly, nonatomic, copy) NSString *responsePath; From 24564772df1881f44f910f56768a87c1652c3870 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 6 Apr 2012 03:00:33 -0700 Subject: [PATCH 36/41] create and set temporary path --- AFNetworking/AFDownloadRequestOperation.m | 16 ++++++++++++---- AFNetworking/AFHTTPRequestOperation.h | 2 ++ AFNetworking/AFHTTPRequestOperation.m | 23 +++++++++++++++++++++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m index 9e3888c..cba1714 100644 --- a/AFNetworking/AFDownloadRequestOperation.m +++ b/AFNetworking/AFDownloadRequestOperation.m @@ -53,13 +53,21 @@ } } + +- (NSString *)temporaryPath { + NSString *temporaryPath = nil; + if (self.destination) { + NSString *hashString = [NSString stringWithFormat:@"%d", [self.destination hash]]; + temporaryPath = [AFCreateIncompleteDownloadDirectoryPath() stringByAppendingPathComponent:hashString]; + } + return temporaryPath; +} + #pragma mark - - (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite { - [self willChangeValueForKey:@"isReady"]; self.destination = path; self.allowOverwrite = allowOverwrite; - [self didChangeValueForKey:@"isReady"]; } #pragma mark - NSOperation @@ -70,8 +78,8 @@ - (void)start { if ([self isReady]) { - // TODO Create temporary path - self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.destination append:NO]; + NSString *temporaryPath = [self temporaryPath]; + self.outputStream = [NSOutputStream outputStreamToFileAtPath:temporaryPath append:NO]; [super start]; } diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 4b6a63e..28c8639 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -28,6 +28,8 @@ */ extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); +extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); + /** `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. */ diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index be53c61..49247a8 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -90,6 +90,24 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { return string; } +NSString * AFCreateIncompleteDownloadDirectoryPath(void) { + static NSString *incompleteDownloadPath; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *tempDirectory = NSTemporaryDirectory(); + incompleteDownloadPath = [[tempDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName] retain]; + + NSError *error = nil; + NSFileManager *fileMan = [[NSFileManager alloc] init]; + if(![fileMan createDirectoryAtPath:incompleteDownloadPath withIntermediateDirectories:YES attributes:nil error:&error]) { + NSLog(@"Failed to create incomplete downloads directory at %@", incompleteDownloadPath); + } + [fileMan release]; + }); + + return incompleteDownloadPath; +} + #pragma mark - @interface AFHTTPRequestOperation () @@ -154,7 +172,7 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { } else { offset = [[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length]; } - + NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; if ([[self.response allHeaderFields] valueForKey:@"ETag"]) { [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"]; @@ -178,7 +196,7 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { if (_successCallbackQueue) { dispatch_release(_successCallbackQueue); } - + if (successCallbackQueue) { dispatch_retain(successCallbackQueue); _successCallbackQueue = successCallbackQueue; @@ -264,6 +282,7 @@ didReceiveResponse:(NSURLResponse *)response { self.response = (NSHTTPURLResponse *)response; + // 206 = Partial Content. if ([self.response statusCode] != 206) { if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; From 61eda7c4e02b4cd7b8a68bc35af07a9c196921a5 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Apr 2012 17:45:41 -0700 Subject: [PATCH 37/41] adds function to responseFilePath (streaming into a file, if set) adds total/offsetContentLength. We really need those in case we pause/resume. --- AFNetworking/AFHTTPRequestOperation.h | 23 +++++++++++++++++-- AFNetworking/AFHTTPRequestOperation.m | 33 +++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 28c8639..c4e0bd5 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -50,9 +50,28 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); @property (readonly, nonatomic, retain) NSHTTPURLResponse *response; /** - + Set a target file for the response, will stream directly into this destination. + Defaults to nil, which will use a memory stream. Will create a new outputStream on change. + + Note: Changing this while the request is not in ready state will be ignored. */ -@property (readonly, nonatomic, copy) NSString *responseFilePath; +@property (nonatomic, copy) NSString *responseFilePath; + + +/** + Expected total length. This is different than expectedContentLength if the file is resumed. + On regular requests, this is equal to self.response.expectedContentLength unless we resume a request. + + Note: this can also be -1 if the file size is not sent (*) + */ +@property (assign, readonly) long long totalContentLength; + +/** + Indicator for the file offset on partial/resumed downloads. + This is greater than zero if the file download is resumed. + */ +@property (assign, readonly) long long offsetContentLength; + ///---------------------------------------------------------- /// @name Managing And Checking For Acceptable HTTP Responses diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 49247a8..8d389a0 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -114,7 +114,8 @@ NSString * AFCreateIncompleteDownloadDirectoryPath(void) { @property (readwrite, nonatomic, retain) NSURLRequest *request; @property (readwrite, nonatomic, retain) NSHTTPURLResponse *response; @property (readwrite, nonatomic, retain) NSError *HTTPError; -@property (readwrite, nonatomic, copy) NSString *responseFilePath; +@property (assign) long long totalContentLength; +@property (assign) long long offsetContentLength; @end @implementation AFHTTPRequestOperation @@ -122,6 +123,8 @@ NSString * AFCreateIncompleteDownloadDirectoryPath(void) { @synthesize responseFilePath = _responseFilePath; @synthesize successCallbackQueue = _successCallbackQueue; @synthesize failureCallbackQueue = _failureCallbackQueue; +@synthesize totalContentLength = _totalContentLength; +@synthesize offsetContentLength = _offsetContentLength; @dynamic request; @dynamic response; @@ -241,6 +244,19 @@ NSString * AFCreateIncompleteDownloadDirectoryPath(void) { }; } +- (void)setResponseFilePath:(NSString *)responseFilePath { + if ([self isReady] && responseFilePath != _responseFilePath) { + [_responseFilePath release]; + _responseFilePath = [responseFilePath retain]; + + if (responseFilePath) { + self.outputStream = [NSOutputStream outputStreamToFileAtPath:responseFilePath append:NO]; + }else { + self.outputStream = [NSOutputStream outputStreamToMemory]; + } + } +} + #pragma mark - AFHTTPClientOperation + (NSIndexSet *)acceptableStatusCodes { @@ -283,6 +299,8 @@ didReceiveResponse:(NSURLResponse *)response self.response = (NSHTTPURLResponse *)response; // 206 = Partial Content. + long long totalContentLength = self.response.expectedContentLength; + long long fileOffset = 0; if ([self.response statusCode] != 206) { if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; @@ -291,8 +309,19 @@ didReceiveResponse:(NSURLResponse *)response self.outputStream = [NSOutputStream outputStreamToMemory]; } } + }else { + 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] ?: -1; // if this is *, it's converted to 0, but -1 is default. + } + } + } - + self.offsetContentLength = MAX(fileOffset, 0); + self.totalContentLength = totalContentLength; [self.outputStream open]; } From 47f6793c3f30f65950432ce390ce11c4abbb7cc7 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 9 Apr 2012 17:47:02 -0700 Subject: [PATCH 38/41] Restore download resuming. The default destination will be the documents folder, unless set otherwise. We also look into the response metadata for the actual filename (unless set otherwise) --- AFNetworking/AFDownloadRequestOperation.h | 23 +++-- AFNetworking/AFDownloadRequestOperation.m | 114 ++++++++++++++++++++-- 2 files changed, 119 insertions(+), 18 deletions(-) diff --git a/AFNetworking/AFDownloadRequestOperation.h b/AFNetworking/AFDownloadRequestOperation.h index da0a3cb..651c56d 100644 --- a/AFNetworking/AFDownloadRequestOperation.h +++ b/AFNetworking/AFDownloadRequestOperation.h @@ -27,22 +27,27 @@ */ @interface AFDownloadRequestOperation : AFHTTPRequestOperation; -@property (readonly, nonatomic, copy) NSString *responsePath; +/** + A Boolean value that indicates if we should try to resume the download. Defaults is `YES`. + + Can only be set while creating the request. + + Note: This allows long-lasting resumes between app-starts. Use this for content that doesn't change. + If the file changed in the meantime, you'll end up with a broken file. + */ +@property (assign, readonly) BOOL shouldResume; /** - + Set a destination. If you don't manually set one, this defaults to the documents directory. + Note: This can point to a path or a file. If this is a path, response.suggestedFilename will be used for the filename. */ - (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite; -/** - - */ -- (BOOL)deletesFileUponFailure; -/** - +/** + Deletes the temporary file if operation fails/is cancelled. Defaults to `NO`. */ -- (void)setDeletesFileUponFailure:(BOOL)deletesFileUponFailure; +@property (assign) BOOL deletesFileUponFailure; ///** diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m index cba1714..e41ff63 100644 --- a/AFNetworking/AFDownloadRequestOperation.m +++ b/AFNetworking/AFDownloadRequestOperation.m @@ -24,22 +24,42 @@ #import "AFURLConnectionOperation.h" @interface AFDownloadRequestOperation() -@property (readwrite, nonatomic, copy) NSString *responsePath; +@property (readwrite, nonatomic, retain) NSURLRequest *request; @property (readwrite, nonatomic, retain) NSError *downloadError; @property (readwrite, nonatomic, copy) NSString *destination; @property (readwrite, nonatomic, assign) BOOL allowOverwrite; -@property (readwrite, nonatomic, assign) BOOL deletesFileUponFailure; @end +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; +} + @implementation AFDownloadRequestOperation -@synthesize responsePath = _responsePath; +@synthesize shouldResume = _shouldResume; @synthesize downloadError = _downloadError; @synthesize destination = _destination; @synthesize allowOverwrite = _allowOverwrite; @synthesize deletesFileUponFailure = _deletesFileUponFailure; +@dynamic request; + +- (id)initWithRequest:(NSURLRequest *)urlRequest { + if ((self = [super initWithRequest:urlRequest])) { + _shouldResume = YES; + _destination = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] copy]; + } + return self; +} - (void)dealloc { - [_responsePath release]; [_downloadError release]; [_destination release]; [super dealloc]; @@ -53,21 +73,51 @@ } } - +// the temporary path depends on the request URL. - (NSString *)temporaryPath { NSString *temporaryPath = nil; if (self.destination) { - NSString *hashString = [NSString stringWithFormat:@"%d", [self.destination hash]]; + NSString *hashString = [NSString stringWithFormat:@"%u", [self.request.URL hash]]; temporaryPath = [AFCreateIncompleteDownloadDirectoryPath() stringByAppendingPathComponent:hashString]; } return temporaryPath; } +// build the final destination with _destination and response.suggestedFilename. +- (NSString *)destinationPath { + NSString *destinationPath = _destination; + + // we assume that at least the directory has to exist on the targetPath + BOOL isDirectory; + if(![[NSFileManager defaultManager] fileExistsAtPath:_destination isDirectory:&isDirectory]) { + isDirectory = NO; + } + // if targetPath is a directory, use the file name we got from the urlRequest. + if (isDirectory) { + destinationPath = [NSString pathWithComponents:[NSArray arrayWithObjects:_destination, self.response.suggestedFilename, nil]]; + } + + return destinationPath; +} + +- (BOOL)deleteTempFileWithError:(NSError **)error { + NSFileManager *fileManager = [[NSFileManager alloc] init]; + BOOL success = YES; + @synchronized(self) { + NSString *tempPath = [self temporaryPath]; + if ([fileManager fileExistsAtPath:tempPath]) { + success = [fileManager removeItemAtPath:[self temporaryPath] error:error]; + } + } + [fileManager release]; + return success; +} + #pragma mark - - (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite { - self.destination = path; self.allowOverwrite = allowOverwrite; + self.destination = path; } #pragma mark - NSOperation @@ -78,13 +128,59 @@ - (void)start { if ([self isReady]) { - NSString *temporaryPath = [self temporaryPath]; - self.outputStream = [NSOutputStream outputStreamToFileAtPath:temporaryPath append:NO]; + self.responseFilePath = [self temporaryPath]; + + if (_shouldResume) { + unsigned long long tempFileSize = AFFileSizeForPath([self temporaryPath]); + if (tempFileSize > 0) { + NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; + [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", tempFileSize] forHTTPHeaderField:@"Range"]; + self.request = mutableURLRequest; + [self.outputStream setProperty:[NSNumber numberWithUnsignedLongLong:tempFileSize] forKey:NSStreamFileCurrentOffsetKey]; + } + } [super start]; } } +#pragma mark - AFURLRequestOperation + +- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success + failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure +{ + self.completionBlock = ^ { + if([self isCancelled]) { + if (self.deletesFileUponFailure) { + [self deleteTempFileWithError:&_downloadError]; + } + return; + }else { + @synchronized(self) { + NSString *destinationPath = [self destinationPath]; + NSFileManager *fileManager = [[NSFileManager alloc] init]; + if (_allowOverwrite && [fileManager fileExistsAtPath:destinationPath]) { + [fileManager removeItemAtPath:destinationPath error:&_downloadError]; + } + if (!_downloadError) { + [fileManager moveItemAtPath:[self temporaryPath] toPath:destinationPath error:&_downloadError]; + } + [fileManager release]; + } + } + + if (self.error) { + dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ + failure(self, self.error); + }); + } else { + dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ + success(self, _destination); + }); + } + }; +} + #pragma mark - //- (void)setDecideDestinationWithSuggestedFilenameBlock:(void (^)(NSString *filename))block; From 9a07a953a77bf00e8c50306b7c4f04004222ddc8 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 13 Apr 2012 08:37:04 -0700 Subject: [PATCH 39/41] Fixing pragma mark label --- AFNetworking/AFHTTPRequestOperation.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index 8d389a0..679ff22 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -257,7 +257,7 @@ NSString * AFCreateIncompleteDownloadDirectoryPath(void) { } } -#pragma mark - AFHTTPClientOperation +#pragma mark - AFHTTPRequestOperation + (NSIndexSet *)acceptableStatusCodes { return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; From 4c8634dfce8098cbcbe70cbbc8c49637173f4d71 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 24 Apr 2012 20:43:46 -0700 Subject: [PATCH 40/41] Removing AFDownloadRequestOperation --- AFNetworking/AFDownloadRequestOperation.h | 64 ------ AFNetworking/AFDownloadRequestOperation.m | 190 ------------------ .../project.pbxproj | 6 - 3 files changed, 260 deletions(-) delete mode 100644 AFNetworking/AFDownloadRequestOperation.h delete mode 100644 AFNetworking/AFDownloadRequestOperation.m diff --git a/AFNetworking/AFDownloadRequestOperation.h b/AFNetworking/AFDownloadRequestOperation.h deleted file mode 100644 index 651c56d..0000000 --- a/AFNetworking/AFDownloadRequestOperation.h +++ /dev/null @@ -1,64 +0,0 @@ -// AFDownloadRequestOperation.h -// -// Copyright (c) 2012 Mattt Thompson (http://mattt.me) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import "AFHTTPRequestOperation.h" - -/** - - */ -@interface AFDownloadRequestOperation : AFHTTPRequestOperation; - -/** - A Boolean value that indicates if we should try to resume the download. Defaults is `YES`. - - Can only be set while creating the request. - - Note: This allows long-lasting resumes between app-starts. Use this for content that doesn't change. - If the file changed in the meantime, you'll end up with a broken file. - */ -@property (assign, readonly) BOOL shouldResume; - -/** - Set a destination. If you don't manually set one, this defaults to the documents directory. - Note: This can point to a path or a file. If this is a path, response.suggestedFilename will be used for the filename. - */ -- (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite; - - -/** - Deletes the temporary file if operation fails/is cancelled. Defaults to `NO`. - */ -@property (assign) BOOL deletesFileUponFailure; - - -///** -// -// */ -//- (void)setDecideDestinationWithSuggestedFilenameBlock:(void (^)(NSString *filename))block; -// -///** -// -// */ -//- (void)setShouldDecodeSourceDataOfMimeTypeBlock:(BOOL (^)(NSString *encodingType))block; -// - -@end diff --git a/AFNetworking/AFDownloadRequestOperation.m b/AFNetworking/AFDownloadRequestOperation.m deleted file mode 100644 index e41ff63..0000000 --- a/AFNetworking/AFDownloadRequestOperation.m +++ /dev/null @@ -1,190 +0,0 @@ -// AFDownloadRequestOperation.m -// -// Copyright (c) 2012 Mattt Thompson (http://mattt.me) -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -#import "AFDownloadRequestOperation.h" -#import "AFURLConnectionOperation.h" - -@interface AFDownloadRequestOperation() -@property (readwrite, nonatomic, retain) NSURLRequest *request; -@property (readwrite, nonatomic, retain) NSError *downloadError; -@property (readwrite, nonatomic, copy) NSString *destination; -@property (readwrite, nonatomic, assign) BOOL allowOverwrite; -@end - -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; -} - -@implementation AFDownloadRequestOperation -@synthesize shouldResume = _shouldResume; -@synthesize downloadError = _downloadError; -@synthesize destination = _destination; -@synthesize allowOverwrite = _allowOverwrite; -@synthesize deletesFileUponFailure = _deletesFileUponFailure; -@dynamic request; - -- (id)initWithRequest:(NSURLRequest *)urlRequest { - if ((self = [super initWithRequest:urlRequest])) { - _shouldResume = YES; - _destination = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] copy]; - } - return self; -} - -- (void)dealloc { - [_downloadError release]; - [_destination release]; - [super dealloc]; -} - -- (NSError *)error { - if (_downloadError) { - return _downloadError; - } else { - return [super error]; - } -} - -// the temporary path depends on the request URL. -- (NSString *)temporaryPath { - NSString *temporaryPath = nil; - if (self.destination) { - NSString *hashString = [NSString stringWithFormat:@"%u", [self.request.URL hash]]; - temporaryPath = [AFCreateIncompleteDownloadDirectoryPath() stringByAppendingPathComponent:hashString]; - } - return temporaryPath; -} - -// build the final destination with _destination and response.suggestedFilename. -- (NSString *)destinationPath { - NSString *destinationPath = _destination; - - // we assume that at least the directory has to exist on the targetPath - BOOL isDirectory; - if(![[NSFileManager defaultManager] fileExistsAtPath:_destination isDirectory:&isDirectory]) { - isDirectory = NO; - } - // if targetPath is a directory, use the file name we got from the urlRequest. - if (isDirectory) { - destinationPath = [NSString pathWithComponents:[NSArray arrayWithObjects:_destination, self.response.suggestedFilename, nil]]; - } - - return destinationPath; -} - -- (BOOL)deleteTempFileWithError:(NSError **)error { - NSFileManager *fileManager = [[NSFileManager alloc] init]; - BOOL success = YES; - @synchronized(self) { - NSString *tempPath = [self temporaryPath]; - if ([fileManager fileExistsAtPath:tempPath]) { - success = [fileManager removeItemAtPath:[self temporaryPath] error:error]; - } - } - [fileManager release]; - return success; -} - -#pragma mark - - -- (void)setDestination:(NSString *)path allowOverwrite:(BOOL)allowOverwrite { - self.allowOverwrite = allowOverwrite; - self.destination = path; -} - -#pragma mark - NSOperation - -- (BOOL)isReady { - return [super isReady] && self.destination; -} - -- (void)start { - if ([self isReady]) { - self.responseFilePath = [self temporaryPath]; - - if (_shouldResume) { - unsigned long long tempFileSize = AFFileSizeForPath([self temporaryPath]); - if (tempFileSize > 0) { - NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; - [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", tempFileSize] forHTTPHeaderField:@"Range"]; - self.request = mutableURLRequest; - [self.outputStream setProperty:[NSNumber numberWithUnsignedLongLong:tempFileSize] forKey:NSStreamFileCurrentOffsetKey]; - } - } - - [super start]; - } -} - -#pragma mark - AFURLRequestOperation - -- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success - failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure -{ - self.completionBlock = ^ { - if([self isCancelled]) { - if (self.deletesFileUponFailure) { - [self deleteTempFileWithError:&_downloadError]; - } - return; - }else { - @synchronized(self) { - NSString *destinationPath = [self destinationPath]; - NSFileManager *fileManager = [[NSFileManager alloc] init]; - if (_allowOverwrite && [fileManager fileExistsAtPath:destinationPath]) { - [fileManager removeItemAtPath:destinationPath error:&_downloadError]; - } - if (!_downloadError) { - [fileManager moveItemAtPath:[self temporaryPath] toPath:destinationPath error:&_downloadError]; - } - [fileManager release]; - } - } - - if (self.error) { - dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{ - failure(self, self.error); - }); - } else { - dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{ - success(self, _destination); - }); - } - }; -} - -#pragma mark - - -//- (void)setDecideDestinationWithSuggestedFilenameBlock:(void (^)(NSString *filename))block; -// -//- (void)setShouldDecodeSourceDataOfMimeTypeBlock:(BOOL (^)(NSString *encodingType))block; - -@end diff --git a/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj b/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj index ebd3f05..02b18ea 100644 --- a/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj +++ b/iOS Example/AFNetworking iOS Example.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - F8BF7FE0153234AE00885289 /* AFDownloadRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8BF7FDF153234AE00885289 /* AFDownloadRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; F8D0701B14310F4A00653FD3 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */; }; F8D0701C14310F4F00653FD3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E013957DF100DB05C8 /* Security.framework */; }; F8DA09E41396AC040057D0CC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = F8DA09E31396AC040057D0CC /* main.m */; }; @@ -40,8 +39,6 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - F8BF7FDE153234AE00885289 /* AFDownloadRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFDownloadRequestOperation.h; sourceTree = ""; }; - F8BF7FDF153234AE00885289 /* AFDownloadRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFDownloadRequestOperation.m; sourceTree = ""; }; F8DA09E31396AC040057D0CC /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; }; F8DA09E51396AC220057D0CC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; }; F8DA09E61396AC220057D0CC /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = SOURCE_ROOT; }; @@ -238,8 +235,6 @@ F8FA94AC150EFEC100ED4EAD /* AFURLConnectionOperation.m */, F8FA949E150EFEC100ED4EAD /* AFHTTPRequestOperation.h */, F8FA949F150EFEC100ED4EAD /* AFHTTPRequestOperation.m */, - F8BF7FDE153234AE00885289 /* AFDownloadRequestOperation.h */, - F8BF7FDF153234AE00885289 /* AFDownloadRequestOperation.m */, F8FA94A2150EFEC100ED4EAD /* AFJSONRequestOperation.h */, F8FA94A3150EFEC100ED4EAD /* AFJSONRequestOperation.m */, F8FA94AD150EFEC100ED4EAD /* AFXMLRequestOperation.h */, @@ -336,7 +331,6 @@ F8FA949A150EF9DA00ED4EAD /* PublicTimelineViewController.m in Sources */, F8FA94B1150EFEC100ED4EAD /* AFHTTPClient.m in Sources */, F8FA94B2150EFEC100ED4EAD /* AFHTTPRequestOperation.m in Sources */, - F8BF7FE0153234AE00885289 /* AFDownloadRequestOperation.m in Sources */, F8FA94B3150EFEC100ED4EAD /* AFImageRequestOperation.m in Sources */, F8FA94B4150EFEC100ED4EAD /* AFJSONRequestOperation.m in Sources */, F8FA94B5150EFEC100ED4EAD /* AFJSONUtilities.m in Sources */, From 346799dc8b29f52cd1ff88e825388126d65a9434 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Tue, 24 Apr 2012 21:28:06 -0700 Subject: [PATCH 41/41] Adding documentation for pause/resume --- AFNetworking/AFURLConnectionOperation.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index f04f474..0266ced 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -156,9 +156,25 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; /// @name Pausing / Resuming Requests ///---------------------------------- +/** + Pauses the execution of the request operation. + + @discussion A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished or cancelled operation has no effect. + */ - (void)pause; + +/** + Whether the request operation is currently paused. + + @return `YES` if the operation is currently paused, otherwise `NO`. + */ - (BOOL)isPaused; +/** + Resumes the execution of the paused request operation. + + @discussion Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request. + */ - (void)resume; ///----------------------------------------------