required libs:
* ZipArchive - Obj-C impl of zip
* asi-http-request : http request help to assist with asynchoronous downloading of files
* minizip : support for ZipArchive
*
Added default splash screen for iOS app. (using the Wagic background to keep it neutral to module)
TODO: refine handling for iPad splash screen
* add selection screen and input screen for location of downloadable content. (ie core files, image files, etc )
* add support to opt out of backing up to iCloud for core files. Right now iOS will automatically backup all files under Documents folder to iCloud. Consider only allowing player data to be backed up to iCloud. All graphics and other assets are considered volatile.
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] == NSStreamEventErrorOccurred) {
|
|
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] == NSStreamEventErrorOccurred) {
|
|
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 %hi",code],NSLocalizedDescriptionKey,nil]];
|
|
}
|
|
|
|
@synthesize streamReady;
|
|
@end
|