アーカイブ

Posts Tagged ‘zip’

Objective-CでZIPアーカイブを読み取る

ZippedImages Etceteraでは、ZIPアーカイブを解凍せずに、直接読み込んでいます。
いくつかObjective-C用のZIPライブラリを試したのですが、なかなかピンと来る物が見つからず、C言語用のMinizipライブラリを使いました。

まず、Minizipの配布物から以下のファイルをプロジェクトに追加します。

crypt.h
ioapi.c
ioapi.h
mztools.c
mztools.h
unzip.c
unzip.h
zip.c
zip.h

次に、OS X付属の以下のライブラリを追加をします。iPhone OSでは試していませんが、zlibは存在するので同様の手順で大丈夫だと思います。

libz.dylib

インターフェース部分には、次のようなラッパークラスを書きました。ZippedImages Etceteraに必要な部分しか用意していませんが、ZIPアーカイブの内容を一覧して、任意のファイルを取り出せます。単純化したかったので、ストリームには対応していません。

//
//  ZipFile.h
//  ZippedImagesEtcetera
//
//  Created by Kenji Nishishiro  on 10/05/08.
//  Copyright 2010 Kenji Nishishiro. All rights reserved.
//

#import "unzip.h"

@interface ZipFile : NSObject {
	NSString *path_;
	unzFile unzipFile_;
}

- (id)initWithFileAtPath:(NSString *)path;
- (BOOL)open;
- (void)close;
- (NSData *)readWithFileName:(NSString *)fileName maxLength:(NSUInteger)maxLength;
- (NSArray *)fileNames;
@end

//
//  ZipFile.m
//  ZippedImagesEtcetera
//
//  Created by Kenji Nishishiro  on 10/05/08.
//  Copyright 2010 Kenji Nishishiro. All rights reserved.
//

#import "ZipFile.h"

@implementation ZipFile

static const int CASE_SENSITIVITY = 0;
static const unsigned int BUFFER_SIZE = 8192;

- (id)initWithFileAtPath:(NSString *)path {
	NSAssert(path, @"path");

	if (self = [super init]) {
		path_ = [path retain];
		unzipFile_ = NULL;
	}
	return self;
}

- (void)dealloc {
	NSAssert(!unzipFile_, @"!unzipFile_");

	[path_ release];
	[super dealloc];
}

- (BOOL)open {
	NSAssert(!unzipFile_, @"!unzipFile_");

	unzipFile_ = unzOpen64([path_ UTF8String]);
	return unzipFile_ != NULL;
}

- (void)close {
	NSAssert(unzipFile_, @"unzipFile_");

	unzClose(unzipFile_);
	unzipFile_ = NULL;
}

- (NSData *)readWithFileName:(NSString *)fileName maxLength:(NSUInteger)maxLength {
	NSAssert(unzipFile_, @"unzipFile_");
	NSAssert(fileName, @"fileName");

	if (unzLocateFile(unzipFile_, [fileName UTF8String], CASE_SENSITIVITY) != UNZ_OK) {
		return nil;
	}

	if (unzOpenCurrentFile(unzipFile_) != UNZ_OK) {
		return nil;
	}

	NSMutableData *data = [NSMutableData data];
	NSUInteger length = 0;
	void *buffer = (void *)malloc(BUFFER_SIZE);
	while (YES) {
		unsigned size = length + BUFFER_SIZE <= maxLength ? BUFFER_SIZE : maxLength - length;
		int readLength = unzReadCurrentFile(unzipFile_, buffer, size);
		if (readLength  0) {
			[data appendBytes:buffer length:readLength];
			length += readLength;
		}
		if (readLength == 0) {
			break;
		}
	};
	free(buffer);

	unzCloseCurrentFile(unzipFile_);

	return data;
}

- (NSArray *)fileNames {
	NSAssert(unzipFile_, @"unzipFile_");

	NSMutableArray *results = [NSMutableArray array];
	if (unzGoToFirstFile(unzipFile_) != UNZ_OK) {
		return nil;
	}
	while (YES) {
		unz_file_info64 fileInfo;
		char fileName[PATH_MAX];
		if (unzGetCurrentFileInfo64(unzipFile_, &fileInfo, fileName, PATH_MAX, NULL, 0, NULL, 0) != UNZ_OK) {
			return nil;
		}
		[results addObject:[NSString stringWithUTF8String:fileName]];

		int error = unzGoToNextFile(unzipFile_);
		if (error == UNZ_END_OF_LIST_OF_FILE) {
			break;
		}
		if (error != UNZ_OK) {
			return nil;
		}
	}
	return results;
}

@end

使い方は次のような感じです。

ZipFile *zipFile = [[[ZipFile alloc] initWithFileAtPath:@"ZIPアーカイブ名"] autorelease];

// ファイルを開く。
if (![zipFile open]) {
	// エラー処理
}

// ファイルリストを取り出す。パーシャルバスで全ファイルが取得されるので、階層的に扱う必要は無い。
NSArray *fileNames = [zipFile fileNames];
if (!fileNames) {
	// エラー処理
}

// ファイルの内容を取り出す。一度にメモリに読み込むので、ストリーム処理が必要な大きいファイルには向かない。安全のために最大サイズを指定できる。
NSData *data = [zipFile readWithFileName:@"ファイル名" maxLength:読み込む最大サイズ];
if (!data) {
	// エラー処理
}

// ファイルを閉じる。
[zipFile close];

ZIPアーカイブ内の文字コードは、UTF-8を想定しています。
ZIPアーカイブ内のファイル名がShift_JISになっている場合などは、もう少し改良が必要だと思います。

2010/05/23 追記
githubで公開しました。

広告
カテゴリー:開発 タグ: , ,