Ben Copsey

* Fix an issue with ASIDataCompressor that would have caused it to fail to defla…

…te files larger than 256KB
* Move requestFinished: back onto the request thread as running on the main thread caused breakage in subclasses (including S3)
* Tweaks to ASIDataDecompressor to make it match the new behaviour of ASIDataCompressor
* Improve tests for gzip
@@ -22,7 +22,8 @@ @@ -22,7 +22,8 @@
22 + (id)compressor; 22 + (id)compressor;
23 23
24 // Compress the passed chunk of data 24 // Compress the passed chunk of data
25 -- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err; 25 +// Passing YES for shouldFinish will finalize the deflated data - you must pass YES when you are on the last chunk of data
  26 +- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish;
26 27
27 // Convenience method - pass it some data, and you'll get deflated data back 28 // Convenience method - pass it some data, and you'll get deflated data back
28 + (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err; 29 + (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err;
@@ -66,7 +66,7 @@ @@ -66,7 +66,7 @@
66 return nil; 66 return nil;
67 } 67 }
68 68
69 -- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err 69 +- (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish
70 { 70 {
71 if (length == 0) return nil; 71 if (length == 0) return nil;
72 72
@@ -91,17 +91,14 @@ @@ -91,17 +91,14 @@
91 91
92 zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; 92 zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
93 zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); 93 zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
94 - 94 + status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH);
95 - status = deflate(&zStream, Z_FINISH);  
96 95
97 if (status == Z_STREAM_END) { 96 if (status == Z_STREAM_END) {
98 - theError = [self closeStream];  
99 break; 97 break;
100 } else if (status != Z_OK) { 98 } else if (status != Z_OK) {
101 if (err) { 99 if (err) {
102 *err = [[self class] deflateErrorWithCode:status]; 100 *err = [[self class] deflateErrorWithCode:status];
103 } 101 }
104 - [self closeStream];  
105 return NO; 102 return NO;
106 } 103 }
107 } 104 }
@@ -122,7 +119,7 @@ @@ -122,7 +119,7 @@
122 + (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err 119 + (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err
123 { 120 {
124 NSError *theError = nil; 121 NSError *theError = nil;
125 - NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError]; 122 + NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES];
126 if (theError) { 123 if (theError) {
127 if (err) { 124 if (err) {
128 *err = theError; 125 *err = theError;
@@ -170,23 +167,27 @@ @@ -170,23 +167,27 @@
170 167
171 // Read some data from the file 168 // Read some data from the file
172 readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; 169 readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE];
173 -  
174 170
175 // Make sure nothing went wrong 171 // Make sure nothing went wrong
176 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { 172 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
177 - [compressor closeStream];  
178 if (err) { 173 if (err) {
179 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; 174 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
180 } 175 }
  176 + [compressor closeStream];
181 return NO; 177 return NO;
182 } 178 }
  179 + // Have we reached the end of the input data?
  180 + if (!readLength) {
  181 + break;
  182 + }
183 183
184 - // Attempt to inflate the chunk of data 184 + // Attempt to deflate the chunk of data
185 - outputData = [compressor compressBytes:inputData length:readLength error:&theError]; 185 + outputData = [compressor compressBytes:inputData length:readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE ];
186 if (theError) { 186 if (theError) {
187 if (err) { 187 if (err) {
188 *err = theError; 188 *err = theError;
189 } 189 }
  190 + [compressor closeStream];
190 return NO; 191 return NO;
191 } 192 }
192 193
@@ -195,17 +196,25 @@ @@ -195,17 +196,25 @@
195 196
196 // Make sure nothing went wrong 197 // Make sure nothing went wrong
197 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { 198 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
198 - [compressor closeStream];  
199 if (err) { 199 if (err) {
200 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; 200 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
201 } 201 }
  202 + [compressor closeStream];
202 return NO; 203 return NO;
203 } 204 }
204 205
205 } 206 }
206 -  
207 [inputStream close]; 207 [inputStream close];
208 [outputStream close]; 208 [outputStream close];
  209 +
  210 + NSError *error = [compressor closeStream];
  211 + if (error) {
  212 + if (err) {
  213 + *err = error;
  214 + }
  215 + return NO;
  216 + }
  217 +
209 return YES; 218 return YES;
210 } 219 }
211 220
@@ -89,16 +89,14 @@ @@ -89,16 +89,14 @@
89 zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; 89 zStream.next_out = [outputData mutableBytes] + zStream.total_out-bytesProcessedAlready;
90 zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); 90 zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready));
91 91
92 - status = inflate(&zStream, Z_SYNC_FLUSH); 92 + status = inflate(&zStream, Z_NO_FLUSH);
93 93
94 if (status == Z_STREAM_END) { 94 if (status == Z_STREAM_END) {
95 - theError = [self closeStream];  
96 break; 95 break;
97 } else if (status != Z_OK) { 96 } else if (status != Z_OK) {
98 if (err) { 97 if (err) {
99 *err = [[self class] inflateErrorWithCode:status]; 98 *err = [[self class] inflateErrorWithCode:status];
100 } 99 }
101 - [self closeStream];  
102 return nil; 100 return nil;
103 } 101 }
104 } 102 }
@@ -169,12 +167,16 @@ @@ -169,12 +167,16 @@
169 167
170 // Make sure nothing went wrong 168 // Make sure nothing went wrong
171 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { 169 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
172 - [decompressor closeStream];  
173 if (err) { 170 if (err) {
174 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]]; 171 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to read from the source data file",sourcePath],NSLocalizedDescriptionKey,[inputStream streamError],NSUnderlyingErrorKey,nil]];
175 } 172 }
  173 + [decompressor closeStream];
176 return NO; 174 return NO;
177 } 175 }
  176 + // Have we reached the end of the input data?
  177 + if (!readLength) {
  178 + break;
  179 + }
178 180
179 // Attempt to inflate the chunk of data 181 // Attempt to inflate the chunk of data
180 outputData = [decompressor uncompressBytes:inputData length:readLength error:&theError]; 182 outputData = [decompressor uncompressBytes:inputData length:readLength error:&theError];
@@ -182,6 +184,7 @@ @@ -182,6 +184,7 @@
182 if (err) { 184 if (err) {
183 *err = theError; 185 *err = theError;
184 } 186 }
  187 + [decompressor closeStream];
185 return NO; 188 return NO;
186 } 189 }
187 190
@@ -190,10 +193,10 @@ @@ -190,10 +193,10 @@
190 193
191 // Make sure nothing went wrong 194 // Make sure nothing went wrong
192 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) { 195 if ([inputStream streamStatus] == NSStreamEventErrorOccurred) {
193 - [decompressor closeStream];  
194 if (err) { 196 if (err) {
195 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]]; 197 *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Decompression of %@ failed because we were unable to write to the destination data file at &@",sourcePath,destinationPath],NSLocalizedDescriptionKey,[outputStream streamError],NSUnderlyingErrorKey,nil]];
196 } 198 }
  199 + [decompressor closeStream];
197 return NO; 200 return NO;
198 } 201 }
199 202
@@ -201,6 +204,15 @@ @@ -201,6 +204,15 @@
201 204
202 [inputStream close]; 205 [inputStream close];
203 [outputStream close]; 206 [outputStream close];
  207 +
  208 + NSError *error = [decompressor closeStream];
  209 + if (error) {
  210 + if (err) {
  211 + *err = error;
  212 + }
  213 + return NO;
  214 + }
  215 +
204 return YES; 216 return YES;
205 } 217 }
206 218
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 #import "ASIDataCompressor.h" 24 #import "ASIDataCompressor.h"
25 25
26 // Automatically set on build 26 // Automatically set on build
27 -NSString *ASIHTTPRequestVersion = @"v1.8-21 2010-12-04"; 27 +NSString *ASIHTTPRequestVersion = @"v1.8-22 2010-12-13";
28 28
29 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 29 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
30 30
@@ -1930,7 +1930,6 @@ static NSOperationQueue *sharedQueue = nil; @@ -1930,7 +1930,6 @@ static NSOperationQueue *sharedQueue = nil;
1930 } 1930 }
1931 } 1931 }
1932 1932
1933 -/* ALWAYS CALLED ON MAIN THREAD! */  
1934 // Subclasses might override this method to process the result in the same thread 1933 // Subclasses might override this method to process the result in the same thread
1935 // If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done 1934 // If you do this, don't forget to call [super requestFinished] to let the queue / delegate know we're done
1936 - (void)requestFinished 1935 - (void)requestFinished
@@ -1941,18 +1940,23 @@ static NSOperationQueue *sharedQueue = nil; @@ -1941,18 +1940,23 @@ static NSOperationQueue *sharedQueue = nil;
1941 if ([self error] || [self mainRequest]) { 1940 if ([self error] || [self mainRequest]) {
1942 return; 1941 return;
1943 } 1942 }
  1943 + [self performSelectorOnMainThread:@selector(reportFinished) withObject:nil waitUntilDone:[NSThread isMainThread]];
  1944 +}
1944 1945
  1946 +/* ALWAYS CALLED ON MAIN THREAD! */
  1947 +- (void)reportFinished
  1948 +{
1945 if (delegate && [delegate respondsToSelector:didFinishSelector]) { 1949 if (delegate && [delegate respondsToSelector:didFinishSelector]) {
1946 [delegate performSelector:didFinishSelector withObject:self]; 1950 [delegate performSelector:didFinishSelector withObject:self];
1947 } 1951 }
1948 if (queue && [queue respondsToSelector:@selector(requestFinished:)]) { 1952 if (queue && [queue respondsToSelector:@selector(requestFinished:)]) {
1949 [queue performSelector:@selector(requestFinished:) withObject:self]; 1953 [queue performSelector:@selector(requestFinished:) withObject:self];
1950 } 1954 }
1951 - #if NS_BLOCKS_AVAILABLE 1955 +#if NS_BLOCKS_AVAILABLE
1952 if(completionBlock){ 1956 if(completionBlock){
1953 completionBlock(); 1957 completionBlock();
1954 } 1958 }
1955 - #endif 1959 +#endif
1956 } 1960 }
1957 1961
1958 /* ALWAYS CALLED ON MAIN THREAD! */ 1962 /* ALWAYS CALLED ON MAIN THREAD! */
@@ -3245,7 +3249,7 @@ static NSOperationQueue *sharedQueue = nil; @@ -3245,7 +3249,7 @@ static NSOperationQueue *sharedQueue = nil;
3245 if (fileError) { 3249 if (fileError) {
3246 [self failWithError:fileError]; 3250 [self failWithError:fileError];
3247 } else { 3251 } else {
3248 - [self performSelectorOnMainThread:@selector(requestFinished) withObject:nil waitUntilDone:[NSThread isMainThread]]; 3252 + [self requestFinished];
3249 } 3253 }
3250 3254
3251 [self markAsFinished]; 3255 [self markAsFinished];
@@ -3344,7 +3348,7 @@ static NSOperationQueue *sharedQueue = nil; @@ -3344,7 +3348,7 @@ static NSOperationQueue *sharedQueue = nil;
3344 } 3348 }
3345 3349
3346 [theRequest updateProgressIndicators]; 3350 [theRequest updateProgressIndicators];
3347 - [theRequest performSelectorOnMainThread:@selector(requestFinished) withObject:nil waitUntilDone:[NSThread isMainThread]]; 3351 + [theRequest requestFinished];
3348 [theRequest markAsFinished]; 3352 [theRequest markAsFinished];
3349 if ([self mainRequest]) { 3353 if ([self mainRequest]) {
3350 [self markAsFinished]; 3354 [self markAsFinished];
@@ -14,17 +14,25 @@ @@ -14,17 +14,25 @@
14 14
15 @implementation ASIDataCompressorTests 15 @implementation ASIDataCompressorTests
16 16
  17 +- (void)setUp
  18 +{
  19 + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
  20 +
  21 + // Download a 1.7MB text file
  22 + NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"];
  23 + if (![fileManager fileExistsAtPath:filePath] || [[[fileManager attributesOfItemAtPath:filePath error:NULL] objectForKey:NSFileSize] unsignedLongLongValue] < 1693961) {
  24 + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_hound_of_the_baskervilles.text"]];
  25 + [request setDownloadDestinationPath:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"]];
  26 + [request startSynchronous];
  27 + }
  28 +}
17 29
18 - (void)testInflateData 30 - (void)testInflateData
19 { 31 {
20 - // Test in-memory inflate using uncompressData:error: 32 +
21 - NSUInteger i; 33 + NSString *originalString = [NSString stringWithContentsOfFile:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"] encoding:NSUTF8StringEncoding error:NULL];
22 -  
23 - NSString *originalString = [NSString string];  
24 - for (i=0; i<1000; i++) {  
25 - originalString = [originalString stringByAppendingFormat:@"This is line %i\r\n",i];  
26 - }  
27 34
  35 + // Test in-memory inflate using uncompressData:error:
28 NSError *error = nil; 36 NSError *error = nil;
29 NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"]; 37 NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"];
30 NSString *gzippedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt.gz"]; 38 NSString *gzippedFilePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt.gz"];
@@ -79,13 +87,10 @@ @@ -79,13 +87,10 @@
79 87
80 - (void)testDeflateData 88 - (void)testDeflateData
81 { 89 {
82 - // Test in-memory deflate using compressData:error: 90 +
83 - NSUInteger i; 91 + NSString *originalString = [NSString stringWithContentsOfFile:[[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"story.txt"] encoding:NSUTF8StringEncoding error:NULL];
84 92
85 - NSString *originalString = [NSString string]; 93 + // Test in-memory deflate using compressData:error:
86 - for (i=0; i<1000; i++) {  
87 - originalString = [originalString stringByAppendingFormat:@"This is line %i\r\n",i];  
88 - }  
89 NSError *error = nil; 94 NSError *error = nil;
90 NSData *deflatedData = [ASIDataCompressor compressData:[originalString dataUsingEncoding:NSUTF8StringEncoding] error:&error]; 95 NSData *deflatedData = [ASIDataCompressor compressData:[originalString dataUsingEncoding:NSUTF8StringEncoding] error:&error];
91 if (error) { 96 if (error) {
@@ -104,6 +109,10 @@ @@ -104,6 +109,10 @@
104 } 109 }
105 110
106 NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"]; 111 NSString *filePath = [[self filePathForTemporaryTestFiles] stringByAppendingPathComponent:@"uncompressed_file.txt"];
  112 + [ASIHTTPRequest removeFileAtPath:filePath error:&error];
  113 + if (error) {
  114 + GHFail(@"Failed to remove old file, cannot proceed with test");
  115 + }
107 116
108 NSTask *task = [[[NSTask alloc] init] autorelease]; 117 NSTask *task = [[[NSTask alloc] init] autorelease];
109 [task setLaunchPath:@"/usr/bin/gzip"]; 118 [task setLaunchPath:@"/usr/bin/gzip"];