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);
|
||||
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];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
///---------------------------------
|
||||
|
|
|
|||
|
|
@ -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,12 +492,10 @@ 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
|
||||
didReceiveData:(NSData *)data
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue