* 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
Showing
5 changed files
with
72 additions
and
37 deletions
@@ -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"]; |
-
Please register or login to post a comment