Permalink
Browse files

Added DTZipArchive

  • Loading branch information...
1 parent 3e2cbfc commit ab99128c918b6d647acbb3971721b3f06187739a @odrobnik odrobnik committed Feb 13, 2012
@@ -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
+
View
@@ -1,5 +1,8 @@
// Classes
+#import "DTAsyncFileDeleter.h"
+#import "DTHTMLParser.h"
#import "DTVersion.h"
+#import "DTZipArchive.h"
// Categories
#import "NSString+DTFormatNumbers.h"
View
@@ -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
View
@@ -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
Oops, something went wrong.

0 comments on commit ab99128

Please sign in to comment.