Ben Copsey

Make gzipped file downloads work

Fix iphone sample to work with renamed properties
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 #if TARGET_OS_IPHONE 15 #if TARGET_OS_IPHONE
16 #import <CFNetwork/CFNetwork.h> 16 #import <CFNetwork/CFNetwork.h>
17 #endif 17 #endif
18 -#import <zlib.h> 18 +#import <stdio.h>
19 19
20 typedef enum _ASINetworkErrorType { 20 typedef enum _ASINetworkErrorType {
21 ASIConnectionFailureErrorType = 1, 21 ASIConnectionFailureErrorType = 1,
@@ -24,7 +24,8 @@ typedef enum _ASINetworkErrorType { @@ -24,7 +24,8 @@ typedef enum _ASINetworkErrorType {
24 ASIRequestCancelledErrorType = 4, 24 ASIRequestCancelledErrorType = 4,
25 ASIUnableToCreateRequestErrorType = 5, 25 ASIUnableToCreateRequestErrorType = 5,
26 ASIInternalErrorWhileBuildingRequestType = 6, 26 ASIInternalErrorWhileBuildingRequestType = 6,
27 - ASIInternalErrorWhileApplyingCredentialsType = 7 27 + ASIInternalErrorWhileApplyingCredentialsType = 7,
  28 + ASIFileManagementError = 8
28 29
29 } ASINetworkErrorType; 30 } ASINetworkErrorType;
30 31
@@ -70,6 +71,9 @@ typedef enum _ASINetworkErrorType { @@ -70,6 +71,9 @@ typedef enum _ASINetworkErrorType {
70 // If downloadDestinationPath is not set, download data will be stored in memory 71 // If downloadDestinationPath is not set, download data will be stored in memory
71 NSString *downloadDestinationPath; 72 NSString *downloadDestinationPath;
72 73
  74 + //The location that files will be downloaded to. Once a download is complete, files will be decompressed (if necessary) and moved to downloadDestinationPath
  75 + NSString *temporaryFileDownloadPath;
  76 +
73 // Used for writing data to a file when downloadDestinationPath is set 77 // Used for writing data to a file when downloadDestinationPath is set
74 NSOutputStream *outputStream; 78 NSOutputStream *outputStream;
75 79
@@ -195,6 +199,9 @@ typedef enum _ASINetworkErrorType { @@ -195,6 +199,9 @@ typedef enum _ASINetworkErrorType {
195 // Response data, automatically uncompressed where appropriate 199 // Response data, automatically uncompressed where appropriate
196 - (NSData *)responseData; 200 - (NSData *)responseData;
197 201
  202 +// Returns true if the response was gzip compressed
  203 +- (BOOL)isResponseCompressed;
  204 +
198 #pragma mark request logic 205 #pragma mark request logic
199 206
200 // Start loading the request 207 // Start loading the request
@@ -284,6 +291,9 @@ typedef enum _ASINetworkErrorType { @@ -284,6 +291,9 @@ typedef enum _ASINetworkErrorType {
284 // Uncompress gzipped data with zlib 291 // Uncompress gzipped data with zlib
285 + (NSData *)uncompressZippedData:(NSData*)compressedData; 292 + (NSData *)uncompressZippedData:(NSData*)compressedData;
286 293
  294 +// Uncompress gzipped data from a file into another file, used when downloading to a file
  295 ++ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath;
  296 ++ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest;
287 297
288 @property (retain) NSString *username; 298 @property (retain) NSString *username;
289 @property (retain) NSString *password; 299 @property (retain) NSString *password;
@@ -296,6 +306,7 @@ typedef enum _ASINetworkErrorType { @@ -296,6 +306,7 @@ typedef enum _ASINetworkErrorType {
296 @property (assign) BOOL useKeychainPersistance; 306 @property (assign) BOOL useKeychainPersistance;
297 @property (assign) BOOL useSessionPersistance; 307 @property (assign) BOOL useSessionPersistance;
298 @property (retain) NSString *downloadDestinationPath; 308 @property (retain) NSString *downloadDestinationPath;
  309 +@property (retain,readonly) NSString *temporaryFileDownloadPath;
299 @property (assign) SEL didFinishSelector; 310 @property (assign) SEL didFinishSelector;
300 @property (assign) SEL didFailSelector; 311 @property (assign) SEL didFailSelector;
301 @property (retain,readonly) NSString *authenticationRealm; 312 @property (retain,readonly) NSString *authenticationRealm;
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 12
13 #import "ASIHTTPRequest.h" 13 #import "ASIHTTPRequest.h"
14 #import "NSHTTPCookieAdditions.h" 14 #import "NSHTTPCookieAdditions.h"
  15 +#import <zlib.h>
15 16
16 // We use our own custom run loop mode as CoreAnimation seems to want to hijack our threads otherwise 17 // We use our own custom run loop mode as CoreAnimation seems to want to hijack our threads otherwise
17 static CFStringRef ASIHTTPRequestRunMode = CFSTR("ASIHTTPRequest"); 18 static CFStringRef ASIHTTPRequestRunMode = CFSTR("ASIHTTPRequest");
@@ -105,6 +106,7 @@ static NSError *ASIUnableToCreateRequestError; @@ -105,6 +106,7 @@ static NSError *ASIUnableToCreateRequestError;
105 [requestHeaders release]; 106 [requestHeaders release];
106 [requestCookies release]; 107 [requestCookies release];
107 [downloadDestinationPath release]; 108 [downloadDestinationPath release];
  109 + [temporaryFileDownloadPath release];
108 [outputStream release]; 110 [outputStream release];
109 [username release]; 111 [username release];
110 [password release]; 112 [password release];
@@ -178,11 +180,15 @@ static NSError *ASIUnableToCreateRequestError; @@ -178,11 +180,15 @@ static NSError *ASIUnableToCreateRequestError;
178 return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease]; 180 return [[[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding:[self responseEncoding]] autorelease];
179 } 181 }
180 182
  183 +- (BOOL)isResponseCompressed
  184 +{
  185 + NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"];
  186 + return encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound;
  187 +}
181 188
182 - (NSData *)responseData 189 - (NSData *)responseData
183 { 190 {
184 - NSString *encoding = [[self responseHeaders] objectForKey:@"Content-Encoding"]; 191 + if ([self isResponseCompressed]) {
185 - if(encoding && [encoding rangeOfString:@"gzip"].location != NSNotFound) {  
186 return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]]; 192 return [ASIHTTPRequest uncompressZippedData:[self rawResponseData]];
187 } else { 193 } else {
188 return [self rawResponseData]; 194 return [self rawResponseData];
@@ -417,9 +423,9 @@ static NSError *ASIUnableToCreateRequestError; @@ -417,9 +423,9 @@ static NSError *ASIUnableToCreateRequestError;
417 [self setRawResponseData:nil]; 423 [self setRawResponseData:nil];
418 424
419 // If we were downloading to a file, let's remove it 425 // If we were downloading to a file, let's remove it
420 - } else if (downloadDestinationPath) { 426 + } else if (temporaryFileDownloadPath) {
421 [outputStream close]; 427 [outputStream close];
422 - [[NSFileManager defaultManager] removeItemAtPath:downloadDestinationPath error:NULL]; 428 + [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:NULL];
423 } 429 }
424 430
425 [self setResponseHeaders:nil]; 431 [self setResponseHeaders:nil];
@@ -1036,7 +1042,9 @@ static NSError *ASIUnableToCreateRequestError; @@ -1036,7 +1042,9 @@ static NSError *ASIUnableToCreateRequestError;
1036 // Are we downloading to a file? 1042 // Are we downloading to a file?
1037 if (downloadDestinationPath) { 1043 if (downloadDestinationPath) {
1038 if (!outputStream) { 1044 if (!outputStream) {
1039 - outputStream = [[NSOutputStream alloc] initToFileAtPath:downloadDestinationPath append:NO]; 1045 + [temporaryFileDownloadPath release];
  1046 + temporaryFileDownloadPath = [[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] retain];
  1047 + outputStream = [[NSOutputStream alloc] initToFileAtPath:temporaryFileDownloadPath append:NO];
1040 [outputStream open]; 1048 [outputStream open];
1041 } 1049 }
1042 [outputStream write:buffer maxLength:bytesRead]; 1050 [outputStream write:buffer maxLength:bytesRead];
@@ -1046,15 +1054,10 @@ static NSError *ASIUnableToCreateRequestError; @@ -1046,15 +1054,10 @@ static NSError *ASIUnableToCreateRequestError;
1046 [rawResponseData appendBytes:buffer length:bytesRead]; 1054 [rawResponseData appendBytes:buffer length:bytesRead];
1047 } 1055 }
1048 } 1056 }
1049 -  
1050 -  
1051 } 1057 }
1052 1058
1053 -  
1054 - (void)handleStreamComplete 1059 - (void)handleStreamComplete
1055 { 1060 {
1056 -  
1057 -  
1058 //Try to read the headers (if this is a HEAD request handleBytesAvailable available may not be called) 1061 //Try to read the headers (if this is a HEAD request handleBytesAvailable available may not be called)
1059 if (!responseHeaders) { 1062 if (!responseHeaders) {
1060 if ([self readResponseHeadersReturningAuthenticationFailure]) { 1063 if ([self readResponseHeadersReturningAuthenticationFailure]) {
@@ -1074,13 +1077,51 @@ static NSError *ASIUnableToCreateRequestError; @@ -1074,13 +1077,51 @@ static NSError *ASIUnableToCreateRequestError;
1074 readStream = NULL; 1077 readStream = NULL;
1075 } 1078 }
1076 1079
  1080 + NSError *fileError = nil;
  1081 +
1077 // Close the output stream as we're done writing to the file 1082 // Close the output stream as we're done writing to the file
1078 - if (downloadDestinationPath) { 1083 + if (temporaryFileDownloadPath) {
1079 [outputStream close]; 1084 [outputStream close];
  1085 +
  1086 + // Decompress the file (if necessary) directly to the destination path
  1087 + if ([self isResponseCompressed]) {
  1088 + int decompressionStatus = [ASIHTTPRequest uncompressZippedDataFromFile:temporaryFileDownloadPath toFile:downloadDestinationPath];
  1089 + if (decompressionStatus != Z_OK) {
  1090 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed with code %hi",temporaryFileDownloadPath,decompressionStatus],NSLocalizedDescriptionKey,nil]];
  1091 + }
  1092 +
  1093 + //Remove the temporary file
  1094 + NSError *removeError = nil;
  1095 + [[NSFileManager defaultManager] removeItemAtPath:temporaryFileDownloadPath error:&removeError];
  1096 + if (removeError) {
  1097 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to delete file at %@ with error: %@",temporaryFileDownloadPath,removeError],NSLocalizedDescriptionKey,removeError,NSUnderlyingErrorKey,nil]];
  1098 + }
  1099 + } else {
  1100 +
  1101 + //Remove any file at the destination path
  1102 + NSError *moveError = nil;
  1103 + if ([[NSFileManager defaultManager] fileExistsAtPath:downloadDestinationPath]) {
  1104 + [[NSFileManager defaultManager] removeItemAtPath:downloadDestinationPath error:&moveError];
  1105 + if (moveError) {
  1106 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Unable to remove file at path '%@'",downloadDestinationPath],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
  1107 + }
  1108 + }
  1109 +
  1110 + //Move the temporary file to the destination path
  1111 + if (!fileError) {
  1112 + [[NSFileManager defaultManager] moveItemAtPath:temporaryFileDownloadPath toPath:downloadDestinationPath error:&moveError];
  1113 + if (moveError) {
  1114 + fileError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIFileManagementError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Failed to move file from '%@' to '%@'",temporaryFileDownloadPath,downloadDestinationPath],NSLocalizedDescriptionKey,moveError,NSUnderlyingErrorKey,nil]];
  1115 + }
  1116 + }
  1117 + }
1080 } 1118 }
1081 [progressLock unlock]; 1119 [progressLock unlock];
1082 - [self requestFinished]; 1120 + if (fileError) {
1083 - 1121 + [self failWithError:fileError];
  1122 + } else {
  1123 + [self requestFinished];
  1124 + }
1084 } 1125 }
1085 1126
1086 1127
@@ -1235,6 +1276,91 @@ static NSError *ASIUnableToCreateRequestError; @@ -1235,6 +1276,91 @@ static NSError *ASIUnableToCreateRequestError;
1235 } 1276 }
1236 } 1277 }
1237 1278
  1279 +
  1280 ++ (int)uncompressZippedDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath
  1281 +{
  1282 + // Get a FILE struct for the source file
  1283 + FILE *source = fdopen([[NSFileHandle fileHandleForReadingAtPath:sourcePath] fileDescriptor], "r");
  1284 +
  1285 + // Create an empty file at the destination path
  1286 + [[NSFileManager defaultManager] createFileAtPath:destinationPath contents:[NSData data] attributes:nil];
  1287 +
  1288 + // Get a FILE struct for the destination path
  1289 + FILE *dest = fdopen([[NSFileHandle fileHandleForWritingAtPath:destinationPath] fileDescriptor], "w");
  1290 +
  1291 + // Uncompress data in source and save in destination
  1292 + int status = [ASIHTTPRequest uncompressZippedDataFromSource:source toDestination:dest];
  1293 +
  1294 + // Close the files
  1295 + fclose(source);
  1296 + fclose(dest);
  1297 + return status;
  1298 +}
  1299 +
  1300 +//
  1301 +// From the zlib sample code by Mark Adler, code here:
  1302 +// http://www.zlib.net/zpipe.c
  1303 +//
  1304 +#define CHUNK 16384
  1305 ++ (int)uncompressZippedDataFromSource:(FILE *)source toDestination:(FILE *)dest
  1306 +{
  1307 + int ret;
  1308 + unsigned have;
  1309 + z_stream strm;
  1310 + unsigned char in[CHUNK];
  1311 + unsigned char out[CHUNK];
  1312 +
  1313 + /* allocate inflate state */
  1314 + strm.zalloc = Z_NULL;
  1315 + strm.zfree = Z_NULL;
  1316 + strm.opaque = Z_NULL;
  1317 + strm.avail_in = 0;
  1318 + strm.next_in = Z_NULL;
  1319 + ret = inflateInit2(&strm, (15+32));
  1320 + if (ret != Z_OK)
  1321 + return ret;
  1322 +
  1323 + /* decompress until deflate stream ends or end of file */
  1324 + do {
  1325 + strm.avail_in = fread(in, 1, CHUNK, source);
  1326 + if (ferror(source)) {
  1327 + (void)inflateEnd(&strm);
  1328 + return Z_ERRNO;
  1329 + }
  1330 + if (strm.avail_in == 0)
  1331 + break;
  1332 + strm.next_in = in;
  1333 +
  1334 + /* run inflate() on input until output buffer not full */
  1335 + do {
  1336 + strm.avail_out = CHUNK;
  1337 + strm.next_out = out;
  1338 + ret = inflate(&strm, Z_NO_FLUSH);
  1339 + assert(ret != Z_STREAM_ERROR); /* state not clobbered */
  1340 + switch (ret) {
  1341 + case Z_NEED_DICT:
  1342 + ret = Z_DATA_ERROR; /* and fall through */
  1343 + case Z_DATA_ERROR:
  1344 + case Z_MEM_ERROR:
  1345 + (void)inflateEnd(&strm);
  1346 + return ret;
  1347 + }
  1348 + have = CHUNK - strm.avail_out;
  1349 + if (fwrite(&out, 1, have, dest) != have || ferror(dest)) {
  1350 + (void)inflateEnd(&strm);
  1351 + return Z_ERRNO;
  1352 + }
  1353 + } while (strm.avail_out == 0);
  1354 +
  1355 + /* done when inflate() says it's done */
  1356 + } while (ret != Z_STREAM_END);
  1357 +
  1358 + /* clean up and return */
  1359 + (void)inflateEnd(&strm);
  1360 + return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
  1361 +}
  1362 +
  1363 +
1238 @synthesize username; 1364 @synthesize username;
1239 @synthesize password; 1365 @synthesize password;
1240 @synthesize domain; 1366 @synthesize domain;
@@ -1246,6 +1372,7 @@ static NSError *ASIUnableToCreateRequestError; @@ -1246,6 +1372,7 @@ static NSError *ASIUnableToCreateRequestError;
1246 @synthesize useSessionPersistance; 1372 @synthesize useSessionPersistance;
1247 @synthesize useCookiePersistance; 1373 @synthesize useCookiePersistance;
1248 @synthesize downloadDestinationPath; 1374 @synthesize downloadDestinationPath;
  1375 +@synthesize temporaryFileDownloadPath;
1249 @synthesize didFinishSelector; 1376 @synthesize didFinishSelector;
1250 @synthesize didFailSelector; 1377 @synthesize didFailSelector;
1251 @synthesize authenticationRealm; 1378 @synthesize authenticationRealm;
@@ -26,5 +26,5 @@ @@ -26,5 +26,5 @@
26 - (void)testDigestAuthentication; 26 - (void)testDigestAuthentication;
27 - (void)testCharacterEncoding; 27 - (void)testCharacterEncoding;
28 - (void)testCompressedResponse; 28 - (void)testCompressedResponse;
29 - 29 +- (void)testCompressedResponseDownloadToFile;
30 @end 30 @end
@@ -126,16 +126,42 @@ @@ -126,16 +126,42 @@
126 126
127 - (void)testFileDownload 127 - (void)testFileDownload
128 { 128 {
  129 + NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testimage.png"];
  130 +
  131 + NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/i/logo.png"] autorelease];
  132 + ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
  133 + [request setDownloadDestinationPath:path];
  134 + [request start];
  135 +
  136 + NSString *tempPath = [request temporaryFileDownloadPath];
  137 + STAssertNotNil(tempPath,@"Failed to download file to temporary location");
  138 +
  139 + //BOOL success = (![[NSFileManager defaultManager] fileExistsAtPath:tempPath]);
  140 + //STAssertTrue(success,@"Failed to remove file from temporary location");
  141 +
  142 + NSImage *image = [[[NSImage alloc] initWithContentsOfFile:path] autorelease];
  143 + STAssertNotNil(image,@"Failed to download data to a file");
  144 +}
  145 +
  146 +- (void)testCompressedResponseDownloadToFile
  147 +{
129 NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testfile"]; 148 NSString *path = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"testfile"];
130 149
131 NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease]; 150 NSURL *url = [[[NSURL alloc] initWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/first"] autorelease];
132 - ASIHTTPRequest *request1 = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease]; 151 + ASIHTTPRequest *request = [[[ASIHTTPRequest alloc] initWithURL:url] autorelease];
133 - [request1 setDownloadDestinationPath:path]; 152 + [request setDownloadDestinationPath:path];
134 - [request1 start]; 153 + [request start];
  154 +
  155 + NSString *tempPath = [request temporaryFileDownloadPath];
  156 + STAssertNotNil(tempPath,@"Failed to download file to temporary location");
  157 +
  158 + //BOOL success = (![[NSFileManager defaultManager] fileExistsAtPath:tempPath]);
  159 + //STAssertTrue(success,@"Failed to remove file from temporary location");
135 160
136 - NSString *s = [NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]];  
137 BOOL success = [[NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]] isEqualToString:@"This is the expected content for the first string"]; 161 BOOL success = [[NSString stringWithContentsOfURL:[NSURL fileURLWithPath:path]] isEqualToString:@"This is the expected content for the first string"];
138 STAssertTrue(success,@"Failed to download data to a file"); 162 STAssertTrue(success,@"Failed to download data to a file");
  163 +
  164 +
139 } 165 }
140 166
141 167
@@ -425,4 +451,5 @@ @@ -425,4 +451,5 @@
425 } 451 }
426 452
427 453
  454 +
428 @end 455 @end
@@ -46,7 +46,7 @@ @@ -46,7 +46,7 @@
46 46
47 - (void)imageFetchComplete:(ASIHTTPRequest *)request 47 - (void)imageFetchComplete:(ASIHTTPRequest *)request
48 { 48 {
49 - UIImage *img = [UIImage imageWithData:[request receivedData]]; 49 + UIImage *img = [UIImage imageWithData:[request responseData]];
50 if (img) { 50 if (img) {
51 if ([imageView1 image]) { 51 if ([imageView1 image]) {
52 if ([imageView2 image]) { 52 if ([imageView2 image]) {
@@ -19,8 +19,8 @@ @@ -19,8 +19,8 @@
19 [request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"]; 19 [request addRequestHeader:@"User-Agent" value:@"ASIHTTPRequest"];
20 20
21 [request start]; 21 [request start];
22 - if ([request dataString]) { 22 + if ([request responseString]) {
23 - [htmlSource setText:[request dataString]]; 23 + [htmlSource setText:[request responseString]];
24 } 24 }
25 } 25 }
26 26
This diff was suppressed by a .gitattributes entry.