2011-08-01 09:39:41 -07:00
//
// 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 256 KB 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 ;
2015-09-16 16:53:07 -07:00
NSUInteger bytesProcessedAlready = zStream . total_out ;
2011-08-01 09:39:41 -07:00
while ( zStream . avail_out = = 0 ) {
if ( zStream . total_out - bytesProcessedAlready >= [ outputData length ] ) {
[ outputData increaseLengthBy : halfLength ] ;
}
2011-10-15 15:26:18 -07:00
zStream . next_out = ( Bytef * ) [ outputData mutableBytes ] + zStream . total_out - bytesProcessedAlready ;
2011-08-01 09:39:41 -07:00
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
2013-12-11 18:06:22 -08:00
if ( [ inputStream streamStatus ] = = NSStreamStatusError ) {
2011-08-01 09:39:41 -07:00
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
2015-09-16 16:53:07 -07:00
outputData = [ compressor compressBytes : inputData length : ( NSUInteger ) readLength error : & theError shouldFinish : readLength < DATA_CHUNK _SIZE ] ;
2011-08-01 09:39:41 -07:00
if ( theError ) {
if ( err ) {
* err = theError ;
}
[ compressor closeStream ] ;
return NO ;
}
// Write the deflated data out to the destination file
2011-10-15 15:26:18 -07:00
[ outputStream write : ( const uint8_t * ) [ outputData bytes ] maxLength : [ outputData length ] ] ;
2011-08-01 09:39:41 -07:00
// Make sure nothing went wrong
2013-12-11 18:06:22 -08:00
if ( [ inputStream streamStatus ] = = NSStreamStatusError ) {
2011-08-01 09:39:41 -07:00
if ( err ) {
2011-10-15 15:26:18 -07:00
* 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 ] ] ;
2011-08-01 09:39:41 -07:00
}
[ 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
{
2015-09-16 16:53:07 -07:00
return [ NSError errorWithDomain : NetworkRequestErrorDomain code : ASICompressionError userInfo : [ NSDictionary dictionaryWithObjectsAndKeys : [ NSString stringWithFormat : @ "Compression of data failed with code %d" , code ] , NSLocalizedDescriptionKey , nil ] ] ;
2011-08-01 09:39:41 -07:00
}
@ synthesize streamReady ;
@ end