Skip to content

Commit

Permalink
Added DTZipArchive
Browse files Browse the repository at this point in the history
  • Loading branch information
odrobnik committed Feb 13, 2012
1 parent 4b34946 commit a398f22
Show file tree
Hide file tree
Showing 14 changed files with 6,253 additions and 8 deletions.
10 changes: 4 additions & 6 deletions Core/DTFoundation-Prefix.pch
Expand Up @@ -2,16 +2,14 @@
// Prefix header for all source files of the 'DTFoundation' target in the 'DTFoundation' project
//

#if !__has_feature(objc_arc)
#error THIS CODE MUST BE COMPILED WITH ARC ENABLED!
#endif


#ifdef __OBJC__
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "LoadableCategory.h"

#endif

//#if !__has_feature(objc_arc)
//#error THIS CODE MUST BE COMPILED WITH ARC ENABLED!
//#endif


3 changes: 3 additions & 0 deletions Core/DTFoundation.h
@@ -1,5 +1,8 @@
// Classes
#import "DTAsyncFileDeleter.h"
#import "DTHTMLParser.h"
#import "DTVersion.h"
#import "DTZipArchive.h"

// Categories
#import "NSString+DTFormatNumbers.h"
Expand Down
48 changes: 48 additions & 0 deletions Core/Source/DTZipArchive.h
@@ -0,0 +1,48 @@
//
// DTZipArchive.h
// DTFoundation
//
// Created by Oliver Drobnik on 12.02.12.
// Copyright (c) 2012 Cocoanetics. All rights reserved.
//

/** This is how the enumeration block needs to look like. Setting *stop to YES will stop the enumeration.
*/
typedef void (^DTZipArchiveEnumerationResultsBlock)(NSString *fileName, NSData *data, BOOL *stop);

/** Supported compression schemes
*/
typedef enum
{
DTZipArchiveFormatPKZip = 0,
DTZipArchiveFormatGZip
} DTZipArchiveFormat;

/** This class represents a compressed file in GZIP or PKZIP format. The used format is auto-detected.
*/

@interface DTZipArchive : NSObject

/**-------------------------------------------------------------------------------------
@name Creating A Zip Archive
---------------------------------------------------------------------------------------
*/

/** Creates an instance of DTZipArchive in preparation for enumerating its contents.
Uses the [minizip](http://www.winimage.com/zLibDll/minizip.html) wrapper for zlib to deal with PKZip-format files.
@param path A Path to a compressed file
@returns An instance of DTZipArchive or `nil` if an error occured
*/
- (id)initWithFileAtPath:(NSString *)path;

/** Enumerates through the files contained in the archive.
If stop is set to `YES` in the enumeration block then the enumeration stops. Note that this parameter is ignored for GZip files since those only contain a single file.
@param enumerationBlock An enumeration block that gets executed for each found and decompressed file
*/
- (void)enumerateUncompressedFilesAsDataUsingBlock:(DTZipArchiveEnumerationResultsBlock)enumerationBlock;

@end
269 changes: 269 additions & 0 deletions Core/Source/DTZipArchive.m
@@ -0,0 +1,269 @@
//
// DTZipArchive.m
// DTFoundation
//
// Created by Oliver Drobnik on 12.02.12.
// Copyright (c) 2012 Cocoanetics. All rights reserved.
//

#import "DTZipArchive.h"

#include "zip.h"
#include "unzip.h"

#define BUFFER_SIZE 4096

@interface DTZipArchive()

- (void)_enumerateGZipUsingBlock:(DTZipArchiveEnumerationResultsBlock)enumerationBlock;
- (void)_enumeratePKZipUsingBlock:(DTZipArchiveEnumerationResultsBlock)enumerationBlock;

@end


@implementation DTZipArchive
{
NSString *_path;
DTZipArchiveFormat _format;
NSData *_data;
}

- (id)initWithFileAtPath:(NSString *)path
{
self = [super init];
if (self)
{
_data = [[NSData alloc] initWithContentsOfFile:path options:NSDataReadingMapped error:NULL];

if (!_data)
{
return nil;
}

// remember path for later
_path = [path copy];

// detect file format
const char *bytes = [_data bytes];

if (bytes[0]=='P' && bytes[1]=='K')
{
// first two bytes are 'PK' on .zip files
_format = DTZipArchiveFormatPKZip;
}
else
{
// probably GZip, we'll see if we can inflate it
_format = DTZipArchiveFormatGZip;
}
}

return self;
}

#pragma Public Methods

- (void)enumerateUncompressedFilesAsDataUsingBlock:(DTZipArchiveEnumerationResultsBlock)enumerationBlock
{
NSAssert(enumerationBlock, @"Must set an enumeration block");

switch (_format)
{
case DTZipArchiveFormatGZip:
[self _enumerateGZipUsingBlock:enumerationBlock];
case DTZipArchiveFormatPKZip:
[self _enumeratePKZipUsingBlock:enumerationBlock];
}
}

#pragma mark GZIP
- (NSString *)_inflatedFileName
{
NSString *fileName = [_path lastPathComponent];
NSString *extension = [fileName pathExtension];

// man page mentions suffixes .gz, -gz, .z, -z, _z or .Z
if ([extension isEqualToString:@"gz"] || [extension isEqualToString:@"z"] || [extension isEqualToString:@"Z"])
{
fileName = [fileName stringByDeletingPathExtension];
}
else if ([fileName hasSuffix:@"-gz"])
{
fileName = [fileName substringToIndex:[fileName length]-3];
}
else if ([fileName hasSuffix:@"-z"] || [fileName hasSuffix:@"_z"])
{
fileName = [fileName substringToIndex:[fileName length]-2];
}

return fileName;
}

// adapted from http://www.cocoadev.com/index.pl?NSDataCategory
- (void)_enumerateGZipUsingBlock:(DTZipArchiveEnumerationResultsBlock)enumerationBlock
{
NSUInteger dataLength = [_data length];
NSUInteger halfLength = dataLength / 2;

NSMutableData *decompressed = [NSMutableData dataWithLength: dataLength + halfLength];
BOOL done = NO;
int status;

z_stream strm;
strm.next_in = (Bytef *)[_data bytes];
strm.avail_in = (uInt)dataLength;
strm.total_out = 0;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;

// inflateInit2 knows how to deal with gzip format
if (inflateInit2(&strm, (15+32)) != Z_OK)
{
return;
}

while (!done)
{
// extend decompressed if too short
if (strm.total_out >= [decompressed length])
{
[decompressed increaseLengthBy: halfLength];
}

strm.next_out = [decompressed mutableBytes] + strm.total_out;
strm.avail_out = (uInt)[decompressed length] - (uInt)strm.total_out;

// Inflate another chunk.
status = inflate (&strm, Z_SYNC_FLUSH);

if (status == Z_STREAM_END)
{
done = YES;
}
else if (status != Z_OK)
{
break;
}
}

if (inflateEnd (&strm) != Z_OK || !done)
{
return;
}

// set actual length
[decompressed setLength:strm.total_out];

// call back block
enumerationBlock([self _inflatedFileName], decompressed, NULL);
}

#pragma mark PKZIP


// adapted from: http://code.google.com/p/ziparchive
- (void)_enumeratePKZipUsingBlock:(DTZipArchiveEnumerationResultsBlock)enumerationBlock
{
unsigned char buffer[BUFFER_SIZE] = {0};

// open the file for unzipping
unzFile _unzFile = unzOpen((const char *)[_path UTF8String]);

// return if failed
if (!_unzFile)
{
return;
}

// get file info
unz_global_info globalInfo = {0};

if (!unzGetGlobalInfo(_unzFile, &globalInfo )==UNZ_OK )
{
// there's a problem
return;
}

if (unzGoToFirstFile(_unzFile)!=UNZ_OK)
{
// unable to go to first file
return;
}

// enum block can stop loop
BOOL shouldStop = NO;

// iterate through all files
do
{
unz_file_info zipInfo ={0};

if (unzOpenCurrentFile(_unzFile) != UNZ_OK)
{
// error uncompressing this file
return;
}

// first call for file info so that we know length of file name
if (unzGetCurrentFileInfo(_unzFile, &zipInfo, NULL, 0, NULL, 0, NULL, 0) != UNZ_OK)
{
// cannot get file info
unzCloseCurrentFile(_unzFile);
return;
}

// reserve space for file name
char *fileNameC = (char *)malloc(zipInfo.size_filename+1);

// second call to get actual file name
unzGetCurrentFileInfo(_unzFile, &zipInfo, fileNameC, zipInfo.size_filename + 1, NULL, 0, NULL, 0);
fileNameC[zipInfo.size_filename] = '\0';
NSString *fileName = [NSString stringWithUTF8String:fileNameC];
free(fileNameC);

/*
// get the file date
NSDateComponents *comps = [[NSDateComponents alloc] init];
// NOTE: zips have no time zone
if (zipInfo.dosDate)
{
// dosdate spec: http://msdn.microsoft.com/en-us/library/windows/desktop/ms724247(v=vs.85).aspx
comps.year = ((zipInfo.dosDate>>25)&127) + 1980; // 7 bits
comps.month = (zipInfo.dosDate>>21)&15; // 4 bits
comps.day = (zipInfo.dosDate>>16)&31; // 5 bits
comps.hour = (zipInfo.dosDate>>11)&31; // 5 bits
comps.minute = (zipInfo.dosDate>>5)&63; // 6 bits
comps.second = (zipInfo.dosDate&31) * 2; // 5 bits
}
else
{
comps.day = zipInfo.tmu_date.tm_mday;
comps.month = zipInfo.tmu_date.tm_mon + 1;
comps.year = zipInfo.tmu_date.tm_year;
comps.hour = zipInfo.tmu_date.tm_hour;
comps.minute = zipInfo.tmu_date.tm_min;
comps.second = zipInfo.tmu_date.tm_sec;
}
NSDate *fileDate = [[NSCalendar currentCalendar] dateFromComponents:comps];
*/

NSMutableData *tmpData = [[NSMutableData alloc] init];

int readBytes;
while((readBytes = unzReadCurrentFile(_unzFile, buffer, BUFFER_SIZE)) > 0)
{
[tmpData appendBytes:buffer length:readBytes];
}

// call the enum block
enumerationBlock(fileName, tmpData, &shouldStop);

// close the current file
unzCloseCurrentFile(_unzFile);
}
while (!shouldStop && unzGoToNextFile(_unzFile )==UNZ_OK);
}

@end

0 comments on commit a398f22

Please sign in to comment.