Showing
7 changed files
with
129 additions
and
6 deletions
@@ -179,6 +179,8 @@ typedef enum _ASINetworkErrorType { | @@ -179,6 +179,8 @@ typedef enum _ASINetworkErrorType { | ||
179 | 179 | ||
180 | NSStringEncoding defaultResponseEncoding; | 180 | NSStringEncoding defaultResponseEncoding; |
181 | NSStringEncoding responseEncoding; | 181 | NSStringEncoding responseEncoding; |
182 | + | ||
183 | + BOOL allowResumeForFileDownloads; | ||
182 | } | 184 | } |
183 | 185 | ||
184 | #pragma mark init / dealloc | 186 | #pragma mark init / dealloc |
@@ -311,7 +313,7 @@ typedef enum _ASINetworkErrorType { | @@ -311,7 +313,7 @@ typedef enum _ASINetworkErrorType { | ||
311 | @property (assign) BOOL useKeychainPersistance; | 313 | @property (assign) BOOL useKeychainPersistance; |
312 | @property (assign) BOOL useSessionPersistance; | 314 | @property (assign) BOOL useSessionPersistance; |
313 | @property (retain) NSString *downloadDestinationPath; | 315 | @property (retain) NSString *downloadDestinationPath; |
314 | -@property (retain,readonly) NSString *temporaryFileDownloadPath; | 316 | +@property (retain) NSString *temporaryFileDownloadPath; |
315 | @property (assign) SEL didFinishSelector; | 317 | @property (assign) SEL didFinishSelector; |
316 | @property (assign) SEL didFailSelector; | 318 | @property (assign) SEL didFailSelector; |
317 | @property (retain,readonly) NSString *authenticationRealm; | 319 | @property (retain,readonly) NSString *authenticationRealm; |
@@ -339,4 +341,5 @@ typedef enum _ASINetworkErrorType { | @@ -339,4 +341,5 @@ typedef enum _ASINetworkErrorType { | ||
339 | @property (assign) NSStringEncoding defaultResponseEncoding; | 341 | @property (assign) NSStringEncoding defaultResponseEncoding; |
340 | @property (assign) NSStringEncoding responseEncoding; | 342 | @property (assign) NSStringEncoding responseEncoding; |
341 | @property (assign) BOOL allowCompressedResponse; | 343 | @property (assign) BOOL allowCompressedResponse; |
344 | +@property (assign) BOOL allowResumeForFileDownloads; | ||
342 | @end | 345 | @end |
@@ -81,9 +81,11 @@ static NSError *ASIUnableToCreateRequestError; | @@ -81,9 +81,11 @@ static NSError *ASIUnableToCreateRequestError; | ||
81 | [self setUploadBufferSize:0]; | 81 | [self setUploadBufferSize:0]; |
82 | [self setResponseHeaders:nil]; | 82 | [self setResponseHeaders:nil]; |
83 | [self setTimeOutSeconds:10]; | 83 | [self setTimeOutSeconds:10]; |
84 | + [self setAllowResumeForFileDownloads:NO]; | ||
84 | [self setUseKeychainPersistance:NO]; | 85 | [self setUseKeychainPersistance:NO]; |
85 | [self setUseSessionPersistance:YES]; | 86 | [self setUseSessionPersistance:YES]; |
86 | [self setUseCookiePersistance:YES]; | 87 | [self setUseCookiePersistance:YES]; |
88 | + [self setRawResponseData:nil]; | ||
87 | [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]]; | 89 | [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]]; |
88 | [self setDidFinishSelector:@selector(requestFinished:)]; | 90 | [self setDidFinishSelector:@selector(requestFinished:)]; |
89 | [self setDidFailSelector:@selector(requestFailed:)]; | 91 | [self setDidFailSelector:@selector(requestFailed:)]; |
@@ -272,6 +274,12 @@ static NSError *ASIUnableToCreateRequestError; | @@ -272,6 +274,12 @@ static NSError *ASIUnableToCreateRequestError; | ||
272 | [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; | 274 | [self addRequestHeader:@"Accept-Encoding" value:@"gzip"]; |
273 | } | 275 | } |
274 | 276 | ||
277 | + // Should this request resume an existing download? | ||
278 | + if ([self allowResumeForFileDownloads] && [self downloadDestinationPath] && [self temporaryFileDownloadPath] && [[NSFileManager defaultManager] fileExistsAtPath:[self temporaryFileDownloadPath]]) { | ||
279 | + unsigned long long downloadedSoFar = [[[NSFileManager defaultManager] fileAttributesAtPath:[self temporaryFileDownloadPath] traverseLink:NO] fileSize]; | ||
280 | + [self addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",downloadedSoFar]]; | ||
281 | + } | ||
282 | + | ||
275 | // Add custom headers | 283 | // Add custom headers |
276 | NSDictionary *headers; | 284 | NSDictionary *headers; |
277 | 285 | ||
@@ -322,8 +330,9 @@ static NSError *ASIUnableToCreateRequestError; | @@ -322,8 +330,9 @@ static NSError *ASIUnableToCreateRequestError; | ||
322 | contentLength = 0; | 330 | contentLength = 0; |
323 | } | 331 | } |
324 | [self setResponseHeaders:nil]; | 332 | [self setResponseHeaders:nil]; |
333 | + if (![self downloadDestinationPath]) { | ||
325 | [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]]; | 334 | [self setRawResponseData:[[[NSMutableData alloc] init] autorelease]]; |
326 | - | 335 | + } |
327 | // Create the stream for the request. | 336 | // Create the stream for the request. |
328 | readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,readStream); | 337 | readStream = CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,readStream); |
329 | if (!readStream) { | 338 | if (!readStream) { |
@@ -431,11 +440,15 @@ static NSError *ASIUnableToCreateRequestError; | @@ -431,11 +440,15 @@ static NSError *ASIUnableToCreateRequestError; | ||
431 | if (rawResponseData) { | 440 | if (rawResponseData) { |
432 | [self setRawResponseData:nil]; | 441 | [self setRawResponseData:nil]; |
433 | 442 | ||
434 | - // If we were downloading to a file, let's remove it | 443 | + // If we were downloading to a file |
435 | } else if (temporaryFileDownloadPath) { | 444 | } else if (temporaryFileDownloadPath) { |
436 | [outputStream close]; | 445 | [outputStream close]; |
446 | + | ||
447 | + // If we haven't said we might want to resume, let's remove the temporary file too | ||
448 | + if (![self allowResumeForFileDownloads]) { | ||
437 | [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:NULL]; | 449 | [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:NULL]; |
438 | } | 450 | } |
451 | + } | ||
439 | 452 | ||
440 | [self setResponseHeaders:nil]; | 453 | [self setResponseHeaders:nil]; |
441 | [cancelledLock unlock]; | 454 | [cancelledLock unlock]; |
@@ -755,6 +768,7 @@ static NSError *ASIUnableToCreateRequestError; | @@ -755,6 +768,7 @@ static NSError *ASIUnableToCreateRequestError; | ||
755 | BOOL isAuthenticationChallenge = NO; | 768 | BOOL isAuthenticationChallenge = NO; |
756 | CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader); | 769 | CFHTTPMessageRef headers = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader); |
757 | if (CFHTTPMessageIsHeaderComplete(headers)) { | 770 | if (CFHTTPMessageIsHeaderComplete(headers)) { |
771 | + | ||
758 | CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(headers); | 772 | CFDictionaryRef headerFields = CFHTTPMessageCopyAllHeaderFields(headers); |
759 | [self setResponseHeaders:(NSDictionary *)headerFields]; | 773 | [self setResponseHeaders:(NSDictionary *)headerFields]; |
760 | CFRelease(headerFields); | 774 | CFRelease(headerFields); |
@@ -1083,9 +1097,14 @@ static NSError *ASIUnableToCreateRequestError; | @@ -1083,9 +1097,14 @@ static NSError *ASIUnableToCreateRequestError; | ||
1083 | // Are we downloading to a file? | 1097 | // Are we downloading to a file? |
1084 | if (downloadDestinationPath) { | 1098 | if (downloadDestinationPath) { |
1085 | if (!outputStream) { | 1099 | if (!outputStream) { |
1086 | - [temporaryFileDownloadPath release]; | 1100 | + BOOL append = NO; |
1087 | - temporaryFileDownloadPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] retain]; | 1101 | + if (![self temporaryFileDownloadPath]) { |
1088 | - outputStream = [[NSOutputStream alloc] initToFileAtPath:temporaryFileDownloadPath append:NO]; | 1102 | + [self setTemporaryFileDownloadPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]]; |
1103 | + } else if ([self allowResumeForFileDownloads]) { | ||
1104 | + append = YES; | ||
1105 | + } | ||
1106 | + | ||
1107 | + outputStream = [[NSOutputStream alloc] initToFileAtPath:temporaryFileDownloadPath append:append]; | ||
1089 | [outputStream open]; | 1108 | [outputStream open]; |
1090 | } | 1109 | } |
1091 | [outputStream write:buffer maxLength:bytesRead]; | 1110 | [outputStream write:buffer maxLength:bytesRead]; |
@@ -1442,4 +1461,5 @@ static NSError *ASIUnableToCreateRequestError; | @@ -1442,4 +1461,5 @@ static NSError *ASIUnableToCreateRequestError; | ||
1442 | @synthesize defaultResponseEncoding; | 1461 | @synthesize defaultResponseEncoding; |
1443 | @synthesize responseEncoding; | 1462 | @synthesize responseEncoding; |
1444 | @synthesize allowCompressedResponse; | 1463 | @synthesize allowCompressedResponse; |
1464 | +@synthesize allowResumeForFileDownloads; | ||
1445 | @end | 1465 | @end |
@@ -504,5 +504,29 @@ | @@ -504,5 +504,29 @@ | ||
504 | } | 504 | } |
505 | 505 | ||
506 | 506 | ||
507 | +- (void)testPartialFetch | ||
508 | +{ | ||
509 | + NSString *downloadPath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testfile.txt"]; | ||
510 | + NSString *tempPath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"tempfile.txt"]; | ||
511 | + NSString *partialContent = @"This file should be exactly 163 bytes long when encoded as UTF8, Unix line breaks with no BOM.\n"; | ||
512 | + [partialContent writeToFile:tempPath atomically:NO encoding:NSASCIIStringEncoding error:nil]; | ||
513 | + | ||
514 | + NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/Tests/test_partial_download.txt"] autorelease]; | ||
515 | + ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; | ||
516 | + [request setDownloadDestinationPath:downloadPath]; | ||
517 | + [request setTemporaryFileDownloadPath:tempPath]; | ||
518 | + [request setAllowResumeForFileDownloads:YES]; | ||
519 | + [request start]; | ||
520 | + | ||
521 | + BOOL success = ([request contentLength] == 68); | ||
522 | + GHAssertTrue(success,@"Failed to download a segment of the data"); | ||
523 | + | ||
524 | + NSString *content = [NSString stringWithContentsOfFile:downloadPath]; | ||
525 | + | ||
526 | + NSString *newPartialContent = [content substringFromIndex:95]; | ||
527 | + success = ([newPartialContent isEqualToString:@"This is the content we ought to be getting if we start from byte 95."]); | ||
528 | + GHAssertTrue(success,@"Failed to append the correct data to the end of the file?"); | ||
529 | + | ||
530 | +} | ||
507 | 531 | ||
508 | @end | 532 | @end |
@@ -139,6 +139,13 @@ | @@ -139,6 +139,13 @@ | ||
139 | if ([[request requestMethod] isEqualToString:@"GET"]) { | 139 | if ([[request requestMethod] isEqualToString:@"GET"]) { |
140 | ASIHTTPRequest *HEADRequest = [[[ASIHTTPRequest alloc] initWithURL:[request url]] autorelease]; | 140 | ASIHTTPRequest *HEADRequest = [[[ASIHTTPRequest alloc] initWithURL:[request url]] autorelease]; |
141 | [HEADRequest setMainRequest:request]; | 141 | [HEADRequest setMainRequest:request]; |
142 | + | ||
143 | + //If we're downloading to a file, and we already have a partial download to start from | ||
144 | + if ([request allowResumeForFileDownloads] && [request downloadDestinationPath] && [request temporaryFileDownloadPath] && [[NSFileManager defaultManager] fileExistsAtPath:[request temporaryFileDownloadPath]]) { | ||
145 | + unsigned long long downloadedSoFar = [[[NSFileManager defaultManager] fileAttributesAtPath:[request temporaryFileDownloadPath] traverseLink:NO] fileSize]; | ||
146 | + [HEADRequest addRequestHeader:@"Range" value:[NSString stringWithFormat:@"bytes=%llu-",downloadedSoFar]]; | ||
147 | + } | ||
148 | + | ||
142 | [self addHEADOperation:HEADRequest]; | 149 | [self addHEADOperation:HEADRequest]; |
143 | 150 | ||
144 | //Tell the request not to reset the progress indicator when it gets a content-length, as we will get the length from the HEAD request | 151 | //Tell the request not to reset the progress indicator when it gets a content-length, as we will get the length from the HEAD request |
@@ -25,6 +25,7 @@ | @@ -25,6 +25,7 @@ | ||
25 | - (void)testProgress; | 25 | - (void)testProgress; |
26 | - (void)testProgressWithAuthentication; | 26 | - (void)testProgressWithAuthentication; |
27 | - (void)testWithNoListener; | 27 | - (void)testWithNoListener; |
28 | +- (void)testPartialResume; | ||
28 | 29 | ||
29 | - (void)setProgress:(float)newProgress; | 30 | - (void)setProgress:(float)newProgress; |
30 | 31 |
@@ -303,5 +303,73 @@ static CFStringRef ASIHTTPRequestTestsRunMode = CFSTR("ASIHTTPRequestTestsRunMod | @@ -303,5 +303,73 @@ static CFStringRef ASIHTTPRequestTestsRunMode = CFSTR("ASIHTTPRequestTestsRunMod | ||
303 | [networkQueue release]; | 303 | [networkQueue release]; |
304 | } | 304 | } |
305 | 305 | ||
306 | +- (void)testPartialResume | ||
307 | +{ | ||
308 | + complete = NO; | ||
309 | + | ||
310 | + NSString *temporaryPath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"MemexTrails_1.0b1.zip.download"]; | ||
311 | + if ([[NSFileManager defaultManager] fileExistsAtPath:temporaryPath]) { | ||
312 | + [[NSFileManager defaultManager] removeItemAtPath:temporaryPath error:nil]; | ||
313 | + } | ||
314 | + | ||
315 | + NSString *downloadPath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"MemexTrails_1.0b1.zip"]; | ||
316 | + if ([[NSFileManager defaultManager] fileExistsAtPath:downloadPath]) { | ||
317 | + [[NSFileManager defaultManager] removeItemAtPath:downloadPath error:nil]; | ||
318 | + } | ||
319 | + | ||
320 | + NSURL *downloadURL = [NSURL URLWithString:@"http://trails-network.net/Downloads/MemexTrails_1.0b1.zip"]; | ||
321 | + networkQueue = [[ASINetworkQueue alloc] init]; | ||
322 | + | ||
323 | + ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:downloadURL] autorelease]; | ||
324 | + [request setDownloadDestinationPath:downloadPath]; | ||
325 | + [request setTemporaryFileDownloadPath:temporaryPath]; | ||
326 | + [request setAllowResumeForFileDownloads:YES]; | ||
327 | + [networkQueue addOperation:request]; | ||
328 | + [networkQueue go]; | ||
329 | + | ||
330 | + // Let the download run for 5 seconds, which hopefully won't be enough time to grab this file. If you have a super fast connection, this test may fail, serves you right for being so smug. :) | ||
331 | + NSTimer *timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:5 target:self selector:@selector(stopQueue:) userInfo:nil repeats:NO]; | ||
332 | + | ||
333 | + while (!complete) { | ||
334 | + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; | ||
335 | + } | ||
336 | + | ||
337 | + // 5 seconds is up, let's tell the queue to stop | ||
338 | + [networkQueue cancelAllOperations]; | ||
339 | + | ||
340 | + [networkQueue release]; | ||
341 | + networkQueue = [[ASINetworkQueue alloc] init]; | ||
342 | + | ||
343 | + | ||
344 | + unsigned long long downloadedSoFar = [[[NSFileManager defaultManager] fileAttributesAtPath:temporaryPath traverseLink:NO] fileSize]; | ||
345 | + BOOL success = (downloadedSoFar > 0); | ||
346 | + GHAssertTrue(success,@"Failed to download part of the file, so we can't proceed with this test"); | ||
347 | + | ||
348 | + request = [[[ASIHTTPRequest alloc] initWithURL:downloadURL] autorelease]; | ||
349 | + [request setDownloadDestinationPath:downloadPath]; | ||
350 | + [request setTemporaryFileDownloadPath:temporaryPath]; | ||
351 | + [request setAllowResumeForFileDownloads:YES]; | ||
352 | + | ||
353 | + [networkQueue addOperation:request]; | ||
354 | + [networkQueue go]; | ||
355 | + | ||
356 | + [networkQueue waitUntilAllOperationsAreFinished]; | ||
357 | + | ||
358 | + unsigned long long amountDownloaded = [[[NSFileManager defaultManager] fileAttributesAtPath:downloadPath traverseLink:NO] fileSize]; | ||
359 | + success = (amountDownloaded == 9145357); | ||
360 | + GHAssertTrue(success,@"Failed to complete the download"); | ||
361 | + | ||
362 | + | ||
363 | + [networkQueue release]; | ||
364 | + | ||
365 | + timeoutTimer = nil; | ||
366 | + | ||
367 | +} | ||
368 | + | ||
369 | +- (void)stopQueue:(id)sender | ||
370 | +{ | ||
371 | + complete = YES; | ||
372 | +} | ||
373 | + | ||
306 | 374 | ||
307 | @end | 375 | @end |
-
Please register or login to post a comment