Settling on a relatively stable implementation of pause/resume
This commit is contained in:
parent
e37a6af943
commit
ca697ce300
3 changed files with 101 additions and 36 deletions
|
|
@ -58,7 +58,6 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL
|
||||||
Method originalMethod = class_getClassMethod(klass, selector);
|
Method originalMethod = class_getClassMethod(klass, selector);
|
||||||
IMP implementation = imp_implementationWithBlock(block);
|
IMP implementation = imp_implementationWithBlock(block);
|
||||||
class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod));
|
class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
|
||||||
|
|
@ -127,6 +126,7 @@ static NSString * AFIncompleteDownloadDirectory() {
|
||||||
|
|
||||||
@interface AFHTTPRequestOperation ()
|
@interface AFHTTPRequestOperation ()
|
||||||
@property (readwrite, nonatomic, retain) NSURLRequest *request;
|
@property (readwrite, nonatomic, retain) NSURLRequest *request;
|
||||||
|
@property (readwrite, nonatomic, retain) NSHTTPURLResponse *response;
|
||||||
@property (readwrite, nonatomic, retain) NSError *HTTPError;
|
@property (readwrite, nonatomic, retain) NSError *HTTPError;
|
||||||
@property (readwrite, nonatomic, copy) NSString *responseFilePath;
|
@property (readwrite, nonatomic, copy) NSString *responseFilePath;
|
||||||
@property (readonly) NSString *temporaryFilePath;
|
@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 {
|
- (BOOL)hasAcceptableStatusCode {
|
||||||
return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]];
|
return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]];
|
||||||
}
|
}
|
||||||
|
|
@ -228,14 +246,14 @@ static NSString * AFIncompleteDownloadDirectory() {
|
||||||
self.responseFilePath = path;
|
self.responseFilePath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldResume) {
|
// if (shouldResume) {
|
||||||
unsigned long long downloadedBytes = AFFileSizeForPath(self.temporaryFilePath);
|
// unsigned long long downloadedBytes = AFFileSizeForPath(self.temporaryFilePath);
|
||||||
if (downloadedBytes > 0) {
|
// if (downloadedBytes > 0) {
|
||||||
NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease];
|
// NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease];
|
||||||
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes] forHTTPHeaderField:@"Range"];
|
// [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", downloadedBytes] forHTTPHeaderField:@"Range"];
|
||||||
self.request = mutableURLRequest;
|
// self.request = mutableURLRequest;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]];
|
self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:!![self.request valueForHTTPHeaderField:@"Range"]];
|
||||||
}
|
}
|
||||||
|
|
@ -311,34 +329,25 @@ static NSString * AFIncompleteDownloadDirectory() {
|
||||||
- (void)connection:(NSURLConnection *)connection
|
- (void)connection:(NSURLConnection *)connection
|
||||||
didReceiveResponse:(NSURLResponse *)response
|
didReceiveResponse:(NSURLResponse *)response
|
||||||
{
|
{
|
||||||
[super connection:connection didReceiveResponse:response];
|
self.response = (NSHTTPURLResponse *)response;
|
||||||
|
|
||||||
if (![self.response isKindOfClass:[NSHTTPURLResponse class]]) {
|
if ([self.response statusCode] != 206) {
|
||||||
return;
|
if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) {
|
||||||
}
|
[self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey];
|
||||||
|
} else {
|
||||||
// check for valid response to resume the download if possible
|
if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) {
|
||||||
long long totalContentLength = self.response.expectedContentLength;
|
self.outputStream = [NSOutputStream outputStreamToMemory];
|
||||||
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 open];
|
||||||
[self.outputStream setProperty:[NSNumber numberWithLongLong:offsetContentLength] forKey:NSStreamFileCurrentOffsetKey];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
||||||
[super connectionDidFinishLoading:connection];
|
[super connectionDidFinishLoading:connection];
|
||||||
|
|
||||||
if (self.responseFilePath) {
|
if (self.responseFilePath && ![self isCancelled]) {
|
||||||
@synchronized(self) {
|
@synchronized(self) {
|
||||||
NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]];
|
NSString *temporaryFilePath = [AFIncompleteDownloadDirectory() stringByAppendingPathComponent:[[NSNumber numberWithInteger:[self.responseFilePath hash]] stringValue]];
|
||||||
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName;
|
||||||
*/
|
*/
|
||||||
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDataDelegate, NSStreamDelegate> {
|
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDataDelegate, NSStreamDelegate> {
|
||||||
@private
|
@private
|
||||||
unsigned short _state;
|
signed short _state;
|
||||||
BOOL _cancelled;
|
BOOL _cancelled;
|
||||||
NSRecursiveLock *_lock;
|
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.
|
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;
|
@property (nonatomic, retain) NSOutputStream *outputStream;
|
||||||
|
|
||||||
|
|
@ -173,6 +173,11 @@ extern NSString * const kAFNetworkingIncompleteDownloadDirectoryName;
|
||||||
*/
|
*/
|
||||||
- (id)initWithRequest:(NSURLRequest *)urlRequest;
|
- (id)initWithRequest:(NSURLRequest *)urlRequest;
|
||||||
|
|
||||||
|
- (void)pause;
|
||||||
|
- (BOOL)isPaused;
|
||||||
|
|
||||||
|
- (void)resume;
|
||||||
|
|
||||||
///---------------------------------
|
///---------------------------------
|
||||||
/// @name Setting Progress Callbacks
|
/// @name Setting Progress Callbacks
|
||||||
///---------------------------------
|
///---------------------------------
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,13 @@
|
||||||
#import "AFURLConnectionOperation.h"
|
#import "AFURLConnectionOperation.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
AFHTTPOperationPausedState = -1,
|
||||||
AFHTTPOperationReadyState = 1,
|
AFHTTPOperationReadyState = 1,
|
||||||
AFHTTPOperationExecutingState = 2,
|
AFHTTPOperationExecutingState = 2,
|
||||||
AFHTTPOperationFinishedState = 3,
|
AFHTTPOperationFinishedState = 3,
|
||||||
} _AFOperationState;
|
} _AFOperationState;
|
||||||
|
|
||||||
typedef unsigned short AFOperationState;
|
typedef signed short AFOperationState;
|
||||||
|
|
||||||
static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock";
|
static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock";
|
||||||
|
|
||||||
|
|
@ -50,6 +51,8 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
|
||||||
return @"isExecuting";
|
return @"isExecuting";
|
||||||
case AFHTTPOperationFinishedState:
|
case AFHTTPOperationFinishedState:
|
||||||
return @"isFinished";
|
return @"isFinished";
|
||||||
|
case AFHTTPOperationPausedState:
|
||||||
|
return @"isPaused";
|
||||||
default:
|
default:
|
||||||
return @"state";
|
return @"state";
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +62,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
|
||||||
switch (fromState) {
|
switch (fromState) {
|
||||||
case AFHTTPOperationReadyState:
|
case AFHTTPOperationReadyState:
|
||||||
switch (toState) {
|
switch (toState) {
|
||||||
|
case AFHTTPOperationPausedState:
|
||||||
case AFHTTPOperationExecutingState:
|
case AFHTTPOperationExecutingState:
|
||||||
return YES;
|
return YES;
|
||||||
case AFHTTPOperationFinishedState:
|
case AFHTTPOperationFinishedState:
|
||||||
|
|
@ -68,6 +72,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
|
||||||
}
|
}
|
||||||
case AFHTTPOperationExecutingState:
|
case AFHTTPOperationExecutingState:
|
||||||
switch (toState) {
|
switch (toState) {
|
||||||
|
case AFHTTPOperationPausedState:
|
||||||
case AFHTTPOperationFinishedState:
|
case AFHTTPOperationFinishedState:
|
||||||
return YES;
|
return YES;
|
||||||
default:
|
default:
|
||||||
|
|
@ -75,6 +80,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
|
||||||
}
|
}
|
||||||
case AFHTTPOperationFinishedState:
|
case AFHTTPOperationFinishedState:
|
||||||
return NO;
|
return NO;
|
||||||
|
case AFHTTPOperationPausedState:
|
||||||
|
return toState == AFHTTPOperationReadyState;
|
||||||
default:
|
default:
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +171,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
|
||||||
self.outputStream = [NSOutputStream outputStreamToMemory];
|
self.outputStream = [NSOutputStream outputStreamToMemory];
|
||||||
self.outputStream.delegate = self;
|
self.outputStream.delegate = self;
|
||||||
|
|
||||||
|
[self.outputStream setProperty:@"Foo bar" forKey:@"Test"];
|
||||||
|
|
||||||
self.state = AFHTTPOperationReadyState;
|
self.state = AFHTTPOperationReadyState;
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
@ -221,9 +230,24 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setInputStream:(NSInputStream *)inputStream {
|
- (void)setInputStream:(NSInputStream *)inputStream {
|
||||||
|
[self willChangeValueForKey:@"inputStream"];
|
||||||
NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease];
|
NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease];
|
||||||
mutableRequest.HTTPBodyStream = inputStream;
|
mutableRequest.HTTPBodyStream = inputStream;
|
||||||
self.request = mutableRequest;
|
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 {
|
- (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;
|
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
|
#pragma mark - NSOperation
|
||||||
|
|
||||||
- (BOOL)isReady {
|
- (BOOL)isReady {
|
||||||
|
|
@ -439,11 +492,9 @@ totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
|
||||||
- (void)connection:(NSURLConnection *)__unused connection
|
- (void)connection:(NSURLConnection *)__unused connection
|
||||||
didReceiveResponse:(NSURLResponse *)response
|
didReceiveResponse:(NSURLResponse *)response
|
||||||
{
|
{
|
||||||
self.response = (NSHTTPURLResponse *)response;
|
self.response = response;
|
||||||
|
|
||||||
if (self.outputStream) {
|
[self.outputStream open];
|
||||||
[self.outputStream open];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)connection:(NSURLConnection *)__unused connection
|
- (void)connection:(NSURLConnection *)__unused connection
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue