220 lines
6.6 KiB
Objective-C
220 lines
6.6 KiB
Objective-C
//
|
|
// 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
|