Skip to content

Commit

Permalink
Make gzipped file downloads work
Browse files Browse the repository at this point in the history
Fix iphone sample to work with renamed properties
  • Loading branch information
pokeb committed Feb 23, 2009
1 parent 35bc9ca commit 192dc94
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 29 deletions.
15 changes: 13 additions & 2 deletions ASIHTTPRequest.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
#import <CFNetwork/CFNetwork.h> #import <CFNetwork/CFNetwork.h>
#endif #endif
#import <zlib.h> #import <stdio.h>


typedef enum _ASINetworkErrorType { typedef enum _ASINetworkErrorType {
ASIConnectionFailureErrorType = 1, ASIConnectionFailureErrorType = 1,
Expand All @@ -24,7 +24,8 @@ typedef enum _ASINetworkErrorType {
ASIRequestCancelledErrorType = 4, ASIRequestCancelledErrorType = 4,
ASIUnableToCreateRequestErrorType = 5, ASIUnableToCreateRequestErrorType = 5,
ASIInternalErrorWhileBuildingRequestType = 6, ASIInternalErrorWhileBuildingRequestType = 6,
ASIInternalErrorWhileApplyingCredentialsType = 7 ASIInternalErrorWhileApplyingCredentialsType = 7,
ASIFileManagementError = 8


} ASINetworkErrorType; } ASINetworkErrorType;


Expand Down Expand Up @@ -70,6 +71,9 @@ typedef enum _ASINetworkErrorType {
// If downloadDestinationPath is not set, download data will be stored in memory // If downloadDestinationPath is not set, download data will be stored in memory
NSString *downloadDestinationPath; NSString *downloadDestinationPath;


//The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath
NSString *temporaryFileDownloadPath;

// Used for writing data to a file when downloadDestinationPath is set // Used for writing data to a file when downloadDestinationPath is set
NSOutputStream *outputStream; NSOutputStream *outputStream;


Expand Down Expand Up @@ -195,6 +199,9 @@ typedef enum _ASINetworkErrorType {
// Response data, automatically uncompressed where appropriate // Response data, automatically uncompressed where appropriate
- (NSData *)responseData; - (NSData *)responseData;


// Returns true if the response was gzip compressed
- (BOOL)isResponseCompressed;

#pragma mark request logic #pragma mark request logic


// Start loading the request // Start loading the request
Expand Down Expand Up @@ -284,6 +291,9 @@ typedef enum _ASINetworkErrorType {
// Uncompress gzipped data with zlib // Uncompress gzipped data with zlib
+ (NSData *)uncompressZippedData:(NSData*)compressedData; + (NSData *)uncompressZippedData:(NSData*)compressedData;


// Uncompress gzipped data from a file into another file, used when downloading to a file
+ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath;
+ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest;


@property (retain) NSString *username; @property (retain) NSString *username;
@property (retain) NSString *password; @property (retain) NSString *password;
Expand All @@ -296,6 +306,7 @@ typedef enum _ASINetworkErrorType {
@property (assign) BOOL useKeychainPersistance; @property (assign) BOOL useKeychainPersistance;
@property (assign) BOOL useSessionPersistance; @property (assign) BOOL useSessionPersistance;
@property (retain) NSString *downloadDestinationPath; @property (retain) NSString *downloadDestinationPath;
@property (retain,readonly) NSString *temporaryFileDownloadPath;
@property (assign) SEL didFinishSelector; @property (assign) SEL didFinishSelector;
@property (assign) SEL didFailSelector; @property (assign) SEL didFailSelector;
@property (retain,readonly) NSString *authenticationRealm; @property (retain,readonly) NSString *authenticationRealm;
Expand Down
153 changes: 140 additions & 13 deletions ASIHTTPRequest.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@


#import "ASIHTTPRequest.h" #import "ASIHTTPRequest.h"
#import "NSHTTPCookieAdditions.h" #import "NSHTTPCookieAdditions.h"
#import <zlib.h>


// We use our own custom run loop mode as CoreAnimation seems to want to hijack our threads otherwise // We use our own custom run loop mode as CoreAnimation seems to want to hijack our threads otherwise
static CFStringRef ASIHTTPRequestRunMode = CFSTR("ASIHTTPRequest"); static CFStringRef ASIHTTPRequestRunMode = CFSTR("ASIHTTPRequest");
Expand Down Expand Up @@ -105,6 +106,7 @@ - (void)dealloc
[requestHeaders release]; [requestHeaders release];
[requestCookies release]; [requestCookies release];
[downloadDestinationPath release]; [downloadDestinationPath release];
[temporaryFileDownloadPath release];
[outputStream release]; [outputStream release];
[username release]; [username release];
[password release]; [password release];
Expand Down Expand Up @@ -178,11 +180,15 @@ - (NSString *)responseString
return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease]; return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
} }


- (BOOL)isResponseCompressed
{
NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
}


- (NSData *)responseData - (NSData *)responseData
{ {
NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"]; if ([self isResponseCompressed]) {
if(encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound) {
return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]]; return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]];
} else { } else {
return [self rawResponseData]; return [self rawResponseData];
Expand Down Expand Up @@ -417,9 +423,9 @@ - (void)cancelLoad
[self setRawResponseData:nil]; [self setRawResponseData:nil];


// If we were downloading to a file, let's remove it // If we were downloading to a file, let's remove it
} else if (downloadDestinationPath) { } else if (temporaryFileDownloadPath) {
[outputStream close]; [outputStream close];
[[NSFileManager defaultManager] removeItemAtPath:downloadDestinationPath error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:NULL];
} }


[self setResponseHeaders:nil]; [self setResponseHeaders:nil];
Expand Down Expand Up @@ -1036,7 +1042,9 @@ - (void)handleBytesAvailable
// Are we downloading to a file? // Are we downloading to a file?
if (downloadDestinationPath) { if (downloadDestinationPath) {
if (!outputStream) { if (!outputStream) {
outputStream = [[NSOutputStream alloc] initToFileAtPath:downloadDestinationPath append:NO]; [temporaryFileDownloadPath release];
temporaryFileDownloadPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] retain];
outputStream = [[NSOutputStream alloc] initToFileAtPath:temporaryFileDownloadPath append:NO];
[outputStream open]; [outputStream open];
} }
[outputStream write:buffer maxLength:bytesRead]; [outputStream write:buffer maxLength:bytesRead];
Expand All @@ -1046,15 +1054,10 @@ - (void)handleBytesAvailable
[rawResponseData appendBytes:buffer length:bytesRead]; [rawResponseData appendBytes:buffer length:bytesRead];
} }
} }


} }



- (void)handleStreamComplete - (void)handleStreamComplete
{ {


//Try to read the headers (if this is a HEAD request handleBytesAvailable available may not be called) //Try to read the headers (if this is a HEAD request handleBytesAvailable available may not be called)
if (!responseHeaders) { if (!responseHeaders) {
if ([self readResponseHeadersReturningAuthenticationFailure]) { if ([self readResponseHeadersReturningAuthenticationFailure]) {
Expand All @@ -1074,13 +1077,51 @@ - (void)handleStreamComplete
readStream = NULL; readStream = NULL;
} }


NSError *fileError = nil;

// Close the output stream as we're done writing to the file // Close the output stream as we're done writing to the file
if (downloadDestinationPath) { if (temporaryFileDownloadPath) {
[outputStream close]; [outputStream close];

// Decompress the file (if necessary) directly to the destination path
if ([self isResponseCompressed]) {
int decompressionStatus = [ASIHTTPRequest uncompressZippedDataFromFile:temporaryFileDownloadPath toFile:downloadDestinationPath];
if (decompressionStatus != Z_OK) {
fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed with code %hi",temporaryFileDownloadPath,decompressionStatus],NSLocalizedDescriptionKey,nil]];
}

//Remove the temporary file
NSError *removeError = nil;
[[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:&removeError];
if (removeError) {
fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",temporaryFileDownloadPath,removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]];
}
} else {

//Remove any file at the destination path
NSError *moveError = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:downloadDestinationPath]) {
[[NSFileManager defaultManager] removeItemAtPath:downloadDestinationPath error:&moveError];
if (moveError) {
fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Unable to remove file at path '%@'",downloadDestinationPath],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
}
}

//Move the temporary file to the destination path
if (!fileError) {
[[NSFileManager defaultManager] moveItemAtPath:temporaryFileDownloadPath toPath:downloadDestinationPath error:&moveError];
if (moveError) {
fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",temporaryFileDownloadPath,downloadDestinationPath],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
}
}
}
} }
[progressLock unlock]; [progressLock unlock];
[self requestFinished]; if (fileError) {

[self failWithError:fileError];
} else {
[self requestFinished];
}
} }




Expand Down Expand Up @@ -1235,6 +1276,91 @@ + (NSData *)uncompressZippedData:(NSData*)compressedData
} }
} }



+ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath
{
// Get a FILE struct for the source file
FILE *source = fdopen([[NSFileHandle fileHandleForReadingAtPath:sourcePath] fileDescriptor], "r");

// Create an empty file at the destination path
[[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil];

// Get a FILE struct for the destination path
FILE *dest = fdopen([[NSFileHandle fileHandleForWritingAtPath:destinationPath] fileDescriptor], "w");

// Uncompress data in source and save in destination
int status = [ASIHTTPRequest uncompressZippedDataFromSource:source toDestination:dest];

// Close the files
fclose(source);
fclose(dest);
return status;
}

//
// From the zlib sample code by Mark Adler, code here:
// http://www.zlib.net/zpipe.c
//
#define CHUNK 16384
+ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest
{
int ret;
unsigned have;
z_stream strm;
unsigned char in[CHUNK];
unsigned char out[CHUNK];

/* allocate inflate state */
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
strm.avail_in = 0;
strm.next_in = Z_NULL;
ret = inflateInit2(&strm, (15+32));
if (ret != Z_OK)
return ret;

/* decompress until deflate stream ends or end of file */
do {
strm.avail_in = fread(in, 1, CHUNK, source);
if (ferror(source)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
if (strm.avail_in == 0)
break;
strm.next_in = in;

/* run inflate() on input until output buffer not full */
do {
strm.avail_out = CHUNK;
strm.next_out = out;
ret = inflate(&strm, Z_NO_FLUSH);
assert(ret != Z_STREAM_ERROR); /* state not clobbered */
switch (ret) {
case Z_NEED_DICT:
ret = Z_DATA_ERROR; /* and fall through */
case Z_DATA_ERROR:
case Z_MEM_ERROR:
(void)inflateEnd(&strm);
return ret;
}
have = CHUNK - strm.avail_out;
if (fwrite(&out, 1, have, dest) != have || ferror(dest)) {
(void)inflateEnd(&strm);
return Z_ERRNO;
}
} while (strm.avail_out == 0);

/* done when inflate() says it's done */
} while (ret != Z_STREAM_END);

/* clean up and return */
(void)inflateEnd(&strm);
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
}


@synthesize username; @synthesize username;
@synthesize password; @synthesize password;
@synthesize domain; @synthesize domain;
Expand All @@ -1246,6 +1372,7 @@ + (NSData *)uncompressZippedData:(NSData*)compressedData
@synthesize useSessionPersistance; @synthesize useSessionPersistance;
@synthesize useCookiePersistance; @synthesize useCookiePersistance;
@synthesize downloadDestinationPath; @synthesize downloadDestinationPath;
@synthesize temporaryFileDownloadPath;
@synthesize didFinishSelector; @synthesize didFinishSelector;
@synthesize didFailSelector; @synthesize didFailSelector;
@synthesize authenticationRealm; @synthesize authenticationRealm;
Expand Down
2 changes: 1 addition & 1 deletion ASIHTTPRequestTests.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@
- (void)testDigestAuthentication; - (void)testDigestAuthentication;
- (void)testCharacterEncoding; - (void)testCharacterEncoding;
- (void)testCompressedResponse; - (void)testCompressedResponse;

- (void)testCompressedResponseDownloadToFile;
@end @end
35 changes: 31 additions & 4 deletions ASIHTTPRequestTests.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -125,17 +125,43 @@ - (void)testDownloadContentLength
} }


- (void)testFileDownload - (void)testFileDownload
{
NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testimage.png"];

NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/i/logo.png"] autorelease];
ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request setDownloadDestinationPath:path];
[request start];

NSString *tempPath = [request temporaryFileDownloadPath];
STAssertNotNil(tempPath,@"Failed to download file to temporary location");

//BOOL success = (![[NSFileManager defaultManager] fileExistsAtPath:tempPath]);
//STAssertTrue(success,@"Failed to remove file from temporary location");

NSImage *image = [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
STAssertNotNil(image,@"Failed to download data to a file");
}

- (void)testCompressedResponseDownloadToFile
{ {
NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testfile"]; NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testfile"];


NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease]; NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease];
ASIHTTPRequest *request1 = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
[request1 setDownloadDestinationPath:path]; [request setDownloadDestinationPath:path];
[request1 start]; [request start];

NSString *tempPath = [request temporaryFileDownloadPath];
STAssertNotNil(tempPath,@"Failed to download file to temporary location");

//BOOL success = (![[NSFileManager defaultManager] fileExistsAtPath:tempPath]);
//STAssertTrue(success,@"Failed to remove file from temporary location");


NSString *s = [NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]];
BOOL success = [[NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]] isEqualToString:@"This is the expected content for the first string"]; BOOL success = [[NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]] isEqualToString:@"This is the expected content for the first string"];
STAssertTrue(success,@"Failed to download data to a file"); STAssertTrue(success,@"Failed to download data to a file");


} }




Expand Down Expand Up @@ -425,4 +451,5 @@ - (void)testCompressedResponse
} }





@end @end
2 changes: 1 addition & 1 deletion QueueViewController.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ - (IBAction)fetchThreeImages:(id)sender


- (void)imageFetchComplete:(ASIHTTPRequest *)request - (void)imageFetchComplete:(ASIHTTPRequest *)request
{ {
UIImage *img = [UIImage imageWithData:[request receivedData]]; UIImage *img = [UIImage imageWithData:[request responseData]];
if (img) { if (img) {
if ([imageView1 image]) { if ([imageView1 image]) {
if ([imageView2 image]) { if ([imageView2 image]) {
Expand Down
4 changes: 2 additions & 2 deletions SynchronousViewController.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ - (IBAction)simpleURLFetch:(id)sender
[request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"]; [request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"];


[request start]; [request start];
if ([request dataString]) { if ([request responseString]) {
[htmlSource setText:[request dataString]]; [htmlSource setText:[request responseString]];
} }
} }


Expand Down
Loading

0 comments on commit 192dc94

Please sign in to comment.