// // ASIDataCompressor.m // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest // // Created by Ben Copsey on 17/08/2010. // Copyright 2010 All-Seeing Interactive. All rights reserved. // #import "ASIDataCompressor.h" #import "ASIHTTPRequest.h" #define DATA_CHUNK_SIZE 262144 // Deal with gzipped data in 256KB chunks #define COMPRESSION_AMOUNT Z_DEFAULT_COMPRESSION @interface ASIDataCompressor () + (NSError *)deflateErrorWithCode:(int)code; @end @implementation ASIDataCompressor + (id)compressor { ASIDataCompressor *compressor = [[[self alloc] init] autorelease]; [compressor setupStream]; return compressor; } - (void)dealloc { if (streamReady) { [self closeStream]; } [super dealloc]; } - (NSError *)setupStream { if (streamReady) { return nil; } // Setup the inflate stream zStream.zalloc = Z_NULL; zStream.zfree = Z_NULL; zStream.opaque = Z_NULL; zStream.avail_in = 0; zStream.next_in = 0; int status = deflateInit2(&zStream, COMPRESSION_AMOUNT, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY); if (status != Z_OK) { return [[self class] deflateErrorWithCode:status]; } streamReady = YES; return nil; } - (NSError *)closeStream { if (!streamReady) { return nil; } // Close the deflate stream streamReady = NO; int status = deflateEnd(&zStream); if (status != Z_OK) { return [[self class] deflateErrorWithCode:status]; } return nil; } - (NSData *)compressBytes:(Bytef *)bytes length:(NSUInteger)length error:(NSError **)err shouldFinish:(BOOL)shouldFinish { if (length == 0) return nil; NSUInteger halfLength = length/2; // We'll take a guess that the compressed data will fit in half the size of the original (ie the max to compress at once is half DATA_CHUNK_SIZE), if not, we'll increase it below NSMutableData *outputData = [NSMutableData dataWithLength:length/2]; int status; zStream.next_in = bytes; zStream.avail_in = (unsigned int)length; zStream.avail_out = 0; NSInteger bytesProcessedAlready = zStream.total_out; while (zStream.avail_out == 0) { if (zStream.total_out-bytesProcessedAlready >= [outputData length]) { [outputData increaseLengthBy:halfLength]; } zStream.next_out = (Bytef*)[outputData mutableBytes] + zStream.total_out-bytesProcessedAlready; zStream.avail_out = (unsigned int)([outputData length] - (zStream.total_out-bytesProcessedAlready)); status = deflate(&zStream, shouldFinish ? Z_FINISH : Z_NO_FLUSH); if (status == Z_STREAM_END) { break; } else if (status != Z_OK) { if (err) { *err = [[self class] deflateErrorWithCode:status]; } return NO; } } // Set real length [outputData setLength: zStream.total_out-bytesProcessedAlready]; return outputData; } + (NSData *)compressData:(NSData*)uncompressedData error:(NSError **)err { NSError *theError = nil; NSData *outputData = [[ASIDataCompressor compressor] compressBytes:(Bytef *)[uncompressedData bytes] length:[uncompressedData length] error:&theError shouldFinish:YES]; if (theError) { if (err) { *err = theError; } return nil; } return outputData; } + (BOOL)compressDataFromFile:(NSString *)sourcePath toFile:(NSString *)destinationPath error:(NSError **)err { NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; // Create an empty file at the destination path if (![fileManager createFileAtPath:destinationPath contents:[NSData data] attributes:nil]) { if (err) { *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed because we were to create a file at %@",sourcePath,destinationPath],NSLocalizedDescriptionKey,nil]]; } return NO; } // Ensure the source file exists if (![fileManager fileExistsAtPath:sourcePath]) { if (err) { *err = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of %@ failed the file does not exist",sourcePath],NSLocalizedDescriptionKey,nil]]; } return NO; } UInt8 inputData[DATA_CHUNK_SIZE]; NSData *outputData; NSInteger readLength; NSError *theError = nil; ASIDataCompressor *compressor = [ASIDataCompressor compressor]; NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:sourcePath]; [inputStream open]; NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:destinationPath append:NO]; [outputStream open]; while ([compressor streamReady]) { // Read some data from the file readLength = [inputStream read:inputData maxLength:DATA_CHUNK_SIZE]; // Make sure nothing went wrong if ([inputStream streamStatus] == NSStreamStatusError) { if (err) { *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]]; } [compressor closeStream]; return NO; } // Have we reached the end of the input data? if (!readLength) { break; } // Attempt to deflate the chunk of data outputData = [compressor compressBytes:inputData length:readLength error:&theError shouldFinish:readLength < DATA_CHUNK_SIZE ]; if (theError) { if (err) { *err = theError; } [compressor closeStream]; return NO; } // Write the deflated data out to the destination file [outputStream write:(const uint8_t *)[outputData bytes] maxLength:[outputData length]]; // Make sure nothing went wrong if ([inputStream streamStatus] == NSStreamStatusError) { if (err) { *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]]; } [compressor closeStream]; return NO; } } [inputStream close]; [outputStream close]; NSError *error = [compressor closeStream]; if (error) { if (err) { *err = error; } return NO; } return YES; } + (NSError *)deflateErrorWithCode:(int)code { return [NSError errorWithDomain:NetworkRequestErrorDomain code:ASICompressionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSString stringWithFormat:@"Compression of data failed with code %d",code],NSLocalizedDescriptionKey,nil]]; } @synthesize streamReady; @end