Settling on a relatively stable implementation of pause/resume

This commit is contained in:
Mattt Thompson 2012-03-27 11:01:20 -07:00
parent e37a6af943
commit ca697ce300
3 changed files with 101 additions and 36 deletions

View file

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

View file

@ -82,7 +82,7 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName;
*/
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDataDelegate, NSStreamDelegate> {
@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
///---------------------------------

View file

@ -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,11 +492,9 @@ totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
- (void)connection:(NSURLConnection *)__unused connection
didReceiveResponse:(NSURLResponse *)response
{
self.response = (NSHTTPURLResponse *)response;
self.response = response;
if (self.outputStream) {
[self.outputStream open];
}
}
- (void)connection:(NSURLConnection *)__unused connection