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];