Skip to content

Commit

Permalink
Improve archive init API
Browse files Browse the repository at this point in the history
* No more separate mutable archive subclass (ref #24).

* Initializers and factory methods may now return nil, along with error information.

* Initializers now take an options dictionary, which allows for expansion. For now, you can set ZZOpenOptionsEncodingKey for string encoding and ZZOpenOptionsCreateIfMissingKey to create the archive file if it is missing.

* Drop the public load: method, as initializers and updateEntries: do any necessary loading. It’s fairly cheap to create a new archive object to reload changed contents, and we no longer lazily load contents and entries.

* The redesign prevents creating erroneous objects e.g. file not found may be a legitimate problem, and prevents swallowing of read errors during lazy loading.

* Update podspec to version 8.0.
  • Loading branch information
pixelglow committed Sep 4, 2014
1 parent 3fb8d79 commit b59b5e7
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 117 deletions.
8 changes: 4 additions & 4 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

The zip file is an ideal container for compound Objective-C documents. Zip files are widely used and well understood. You can randomly access their parts. The format compresses decently and has extensive operating system and tool support. So we want to make this format an even easier choice for you. Thus, the library features:

* **Easy-to-use interface**: The public API offers just three classes! Yet you can look through zip files using familiar *NSArray* collections and properties. And you can zip, unzip and rezip zip files through familiar *NSData*, *NSStream* and Image I/O classes.
* **Easy-to-use interface**: The public API offers just two classes! Yet you can look through zip files using familiar *NSArray* collections and properties. And you can zip, unzip and rezip zip files through familiar *NSData*, *NSStream* and Image I/O classes.
* **Efficient implementation**: We've optimized zip file reading and writing to reduce virtual memory pressure and disk file thrashing. Depending on how your compound document is organized, updating a single entry can be faster than writing the same data to a separate file.
* **File format compatibility**: Since *zipzap* closely follows the [zip file format specification](http://www.pkware.com/documents/casestudies/APPNOTE.TXT), it works with most Mac, Linux and Windows zip tools.

Expand Down Expand Up @@ -37,14 +37,14 @@ Header includes:

Reading an existing zip file:

ZZArchive* oldArchive = [ZZArchive archiveWithContentsOfURL:[NSURL fileURLWithPath:@"/tmp/old.zip"]];
ZZArchive* oldArchive = [ZZArchive archiveWithURL:[NSURL fileURLWithPath:@"/tmp/old.zip"] error:nil];
ZZArchiveEntry* firstArchiveEntry = oldArchive.entries[0];
NSLog(@"The first entry's uncompressed size is %lu bytes.", firstArchiveEntry.uncompressedSize);
NSLog(@"The first entry's data is: %@.", [firstArchiveEntry newData]);

Writing a new zip file:

ZZMutableArchive* newArchive = [ZZMutableArchive archiveWithContentsOfURL:[NSURL fileURLWithPath:@"/tmp/new.zip"]];
ZZArchive* newArchive = [ZZArchive archiveWithURL:[NSURL fileURLWithPath:@"/tmp/new.zip"] error:nil];
[newArchive updateEntries:
@[
[ZZArchiveEntry archiveEntryWithFileName:@"first.text"
Expand All @@ -58,7 +58,7 @@ Writing a new zip file:

Updating an existing zip file:

ZZMutableArchive* oldArchive = [ZZMutableArchive archiveWithContentsOfURL:[NSURL fileURLWithPath:@"/tmp/old.zip"]];
ZZArchive* oldArchive = [ZZArchive archiveWithURL:[NSURL fileURLWithPath:@"/tmp/old.zip"] error:nil];
[oldArchive updateEntries:
[oldArchive.entries arrayByAddingObject:
[ZZArchiveEntry archiveEntryWithFileName:@"second.text"
Expand Down
4 changes: 2 additions & 2 deletions zipzap.podspec.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "zipzap",
"version": "7.0",
"version": "8.0",
"authors": {
"Pixelglow Software": "[email protected]"
},
"license": "BSD",
"homepage": "https://github.com/pixelglow/zipzap",
"source": {
"git": "https://github.com/pixelglow/zipzap.git",
"tag": "7.0"
"tag": "8.0"
},
"summary": "zipzap is a zip file I/O library for Mac OS X and iOS.",
"description": "The zip file is an ideal container for compound Objective-C documents. Zip files are widely used and well understood. You can randomly access their parts. The format compresses decently and has extensive operating system and tool support. So we want to make this format an even easier choice for you. Thus, the library features:\n\n- Easy-to-use interface: The public API offers just three classes! Yet you can look through zip files using familiar NSArray collections and properties. And you can zip, unzip and rezip zip files through familiar NSData, NSStream and Image I/O classes.\n- Efficient implementation: We've optimized zip file reading and writing to reduce virtual memory pressure and disk file thrashing. Depending on how your compound document is organized, updating a single entry can be faster than writing the same data to a separate file.\n- File format compatibility: Since zipzap closely follows the zip file format specification, it is works with most Mac, Linux and Windows zip tools.\n",
Expand Down
6 changes: 6 additions & 0 deletions zipzap.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
D84A44CF16A0F8B400CF00A9 /* ZZDataChannelOutput.h in Headers */ = {isa = PBXBuildFile; fileRef = D84A44CD16A0F8B400CF00A9 /* ZZDataChannelOutput.h */; };
D84A44D016A0F8B400CF00A9 /* ZZDataChannelOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = D84A44CE16A0F8B400CF00A9 /* ZZDataChannelOutput.m */; };
D84A44D116A0F8B400CF00A9 /* ZZDataChannelOutput.m in Sources */ = {isa = PBXBuildFile; fileRef = D84A44CE16A0F8B400CF00A9 /* ZZDataChannelOutput.m */; };
D85CB4D119B5A91000815A36 /* ZZConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = D85CB4D019B5A91000815A36 /* ZZConstants.m */; };
D85CB4D219B5A91000815A36 /* ZZConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = D85CB4D019B5A91000815A36 /* ZZConstants.m */; };
D86B400C18A8A0BA00017A91 /* ZZConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B50392518706368002B2B12 /* ZZConstants.h */; settings = {ATTRIBUTES = (Public, ); }; };
D86B400D18A8A0DA00017A91 /* ZZConstants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5B50392518706368002B2B12 /* ZZConstants.h */; };
D86B400E18A8A0FF00017A91 /* ZZError.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = D8366ACC16B1F8B400BC012E /* ZZError.h */; };
Expand Down Expand Up @@ -146,6 +148,7 @@
D84A44AE16A0E8D600CF00A9 /* ZZDataChannel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZZDataChannel.m; sourceTree = "<group>"; };
D84A44CD16A0F8B400CF00A9 /* ZZDataChannelOutput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZZDataChannelOutput.h; sourceTree = "<group>"; };
D84A44CE16A0F8B400CF00A9 /* ZZDataChannelOutput.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZZDataChannelOutput.m; sourceTree = "<group>"; };
D85CB4D019B5A91000815A36 /* ZZConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ZZConstants.m; sourceTree = "<group>"; };
D88E8DFD16375C66002207B3 /* dog.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = dog.jpg; sourceTree = "<group>"; };
D88E8DFE16375C66002207B3 /* dog.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = dog.png; sourceTree = "<group>"; };
D88E8E05163816FD002207B3 /* ZZOldArchiveEntry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZZOldArchiveEntry.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -239,6 +242,7 @@
D899CFD8162C608300112F49 /* ZZArchiveEntry.h */,
D899CFD9162C608300112F49 /* ZZArchiveEntry.m */,
5B50392518706368002B2B12 /* ZZConstants.h */,
D85CB4D019B5A91000815A36 /* ZZConstants.m */,
D8366ACC16B1F8B400BC012E /* ZZError.h */,
D8366ACD16B1F8B400BC012E /* ZZError.m */,
);
Expand Down Expand Up @@ -554,6 +558,7 @@
D899CFEE162C608300112F49 /* ZZArchive.mm in Sources */,
D899CFF4162C608300112F49 /* ZZDeflateOutputStream.m in Sources */,
D899CFF7162C608300112F49 /* ZZStoreOutputStream.m in Sources */,
D85CB4D119B5A91000815A36 /* ZZConstants.m in Sources */,
D899CFFA162C608300112F49 /* ZZOldArchiveEntryWriter.mm in Sources */,
D899CFFE162C608300112F49 /* ZZNewArchiveEntry.mm in Sources */,
D834256318767E1900E7A559 /* ZZStandardDecryptInputStream.mm in Sources */,
Expand Down Expand Up @@ -588,6 +593,7 @@
D899CFEF162C608300112F49 /* ZZArchive.mm in Sources */,
D899CFF5162C608300112F49 /* ZZDeflateOutputStream.m in Sources */,
5BC58CAC18745D22002FAE04 /* ZZStandardCryptoEngine.cpp in Sources */,
D85CB4D219B5A91000815A36 /* ZZConstants.m in Sources */,
D899CFF8162C608300112F49 /* ZZStoreOutputStream.m in Sources */,
D834256418767E1900E7A559 /* ZZStandardDecryptInputStream.mm in Sources */,
D899CFFB162C608300112F49 /* ZZOldArchiveEntryWriter.mm in Sources */,
Expand Down
54 changes: 22 additions & 32 deletions zipzap/ZZArchive.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,60 +56,50 @@
/**
* Creates a new archive with the zip file at the given file URL.
*
* The archive will use UTF-8 encoding for reading entry file names and comments.
* The archive will use UTF-8 encoding for reading entry file names and comments, and will not create the file if it is missing.
*
* @param URL The file URL of the zip file.
* @return The initialized archive. If the zip file does not exist, this will have no entries.
* @param error The error information when an error occurs. Pass in nil if you do not want error information.
* @return The initialized archive. Returns nil if the archive cannot be initialized.
*/
+ (instancetype)archiveWithContentsOfURL:(NSURL*)URL;
+ (instancetype)archiveWithURL:(NSURL*)URL
error:(out NSError**)error;

/**
* Creates a new archive with the raw zip file data given.
*
* The archive will use UTF-8 encoding for reading entry file names and comments.
* The archive will use UTF-8 encoding for reading entry file names and comments, and will not create the data if it is missing.
*
* @param data The raw data of the zip file
* @return The initialized archive.
* @param data The raw data of the zip file.
* @param error The error information when an error occurs. Pass in nil if you do not want error information.
* @return The initialized archive. Returns nil if the archive cannot be initialized.
*/
+ (instancetype)archiveWithData:(NSData*)data;
+ (instancetype)archiveWithData:(NSData*)data
error:(out NSError**)error;

/**
* Initializes a new archive with the zip file at the given file URL.
*
* @param URL The file URL of the zip file.
* @param encoding The encoding for reading entry file names and comments.
* @return The initialized archive. If the zip file does not exist, this will have no entries.
* @param options The options to consider when opening the zip file.
* @param error The error information when an error occurs. Pass in nil if you do not want error information.
* @return The initialized archive. Returns nil if the archive cannot be initialized.
*/
- (id)initWithContentsOfURL:(NSURL*)URL
encoding:(NSStringEncoding)encoding;
- (id)initWithURL:(NSURL*)URL
options:(NSDictionary*)options
error:(out NSError**)error;

/**
* Initializes a new archive with the raw zip file data given.
*
* @param data The raw data of the zip file
* @param encoding The encoding for reading entry file names and comments.
* @return The initialized archive.
*/
- (id)initWithData:(NSData*)data
encoding:(NSStringEncoding)encoding;

/**
* Loads the contents and entries from the source. Any old entries will then be considered invalid.
*
* Whenever you access entries or contents, the receiver will load them as needed.
* You only need to call this method to force a reload from source, or to check for errors when loading.
*
* @param options The options to consider when opening the zip file.
* @param error The error information when an error occurs. Pass in nil if you do not want error information.
* @return Whether the load was successful or not.
* @return The initialized archive. Returns nil if the archive cannot be initialized.
*/
- (BOOL)load:(out NSError**)error;

@end

/**
* The ZZMutableArchive class represents a zip file for reading and writing.
*/
@interface ZZMutableArchive : ZZArchive
- (id)initWithData:(NSData*)data
options:(NSDictionary*)options
error:(out NSError**)error;

/**
* Updates the entries and writes them to the source.
Expand Down
120 changes: 60 additions & 60 deletions zipzap/ZZArchive.mm
Original file line number Diff line number Diff line change
Expand Up @@ -23,48 +23,69 @@
static const size_t ENDOFCENTRALDIRECTORY_MINSEARCH = sizeof(ZZEndOfCentralDirectory) - sizeof(ZZEndOfCentralDirectory::signature);

@interface ZZArchive ()
{
@protected
id<ZZChannel> _channel;
NSStringEncoding _encoding;
NSData* _contents;
NSArray* _entries;
}

- (id)initWithChannel:(id<ZZChannel>)channel
options:(NSDictionary*)options
error:(out NSError**)error;

- (BOOL)loadCanMiss:(BOOL)canMiss error:(out NSError**)error;

@end

@implementation ZZArchive
{
id<ZZChannel> _channel;
NSStringEncoding _encoding;
}

+ (instancetype)archiveWithContentsOfURL:(NSURL*)URL
+ (instancetype)archiveWithURL:(NSURL*)URL
error:(out NSError**)error
{
return [[self alloc] initWithContentsOfURL:URL
encoding:NSUTF8StringEncoding];
return [[self alloc] initWithChannel:[[ZZFileChannel alloc] initWithURL:URL]
options:nil
error:error];
}

+ (instancetype)archiveWithData:(NSData*)data
error:(out NSError**)error
{
return [[self alloc] initWithData:data
encoding:NSUTF8StringEncoding];
return [[self alloc] initWithChannel:[[ZZDataChannel alloc] initWithData:data]
options:nil
error:error];
}

- (id)initWithContentsOfURL:(NSURL*)URL
encoding:(NSStringEncoding)encoding
- (id)initWithURL:(NSURL*)URL
options:(NSDictionary*)options
error:(out NSError**)error
{
if ((self = [super init]))
{
_channel = [[ZZFileChannel alloc] initWithURL:URL];
_encoding = encoding;
}
return self;
return [self initWithChannel:[[ZZFileChannel alloc] initWithURL:URL]
options:options
error:error];
}

- (id)initWithData:(NSData*)data
encoding:(NSStringEncoding)encoding
options:(NSDictionary*)options
error:(out NSError**)error
{
return [self initWithChannel:[[ZZDataChannel alloc] initWithData:data]
options:options
error:error];
}

- (id)initWithChannel:(id<ZZChannel>)channel
options:(NSDictionary*)options
error:(out NSError**)error
{
if ((self = [super init]))
{
_channel = [[ZZDataChannel alloc] initWithData:data];
_encoding = encoding;
_channel = channel;

NSNumber* encoding = options[ZZOpenOptionsEncodingKey];
_encoding = encoding ? encoding.unsignedIntegerValue : NSUTF8StringEncoding;

NSNumber* createIfMissing = options[ZZOpenOptionsCreateIfMissingKey];
if (![self loadCanMiss:createIfMissing.boolValue error:error])
return nil;
}
return self;
}
Expand All @@ -74,32 +95,23 @@ - (NSURL*)URL
return _channel.URL;
}

- (NSData*)contents
{
// lazily load in contents + refresh entries
if (!_contents)
[self load:nil];

return _contents;
}

- (NSArray*)entries
{
// lazily load in contents + refresh entries
if (!_contents)
[self load:nil];

return _entries;
}

- (BOOL)load:(NSError**)error
- (BOOL)loadCanMiss:(BOOL)canMiss error:(out NSError**)error
{
// memory-map the contents from the zip file
NSError* __autoreleasing readError;
NSData* contents = [_channel newInput:&readError];
if (!contents)
return ZZRaiseErrorNo(error, ZZOpenReadErrorCode, @{NSUnderlyingErrorKey : readError});

{
if (canMiss && readError.code == NSFileReadNoSuchFileError && [readError.domain isEqualToString:NSCocoaErrorDomain])
{
_contents = nil;
_entries = nil;
return YES;
}
else
return ZZRaiseErrorNo(error, ZZOpenReadErrorCode, @{NSUnderlyingErrorKey : readError});
}

// search for the end of directory signature in last 64K of file
const uint8_t* beginContent = (const uint8_t*)contents.bytes;
const uint8_t* endContent = beginContent + contents.length;
Expand Down Expand Up @@ -160,22 +172,13 @@ - (BOOL)load:(NSError**)error

// having successfully negotiated the new contents + entries, replace in one go
_contents = contents;
_entries = [NSArray arrayWithArray:entries];
_entries = entries;
return YES;
}

@end

@implementation ZZMutableArchive

- (BOOL)updateEntries:(NSArray*)newEntries
error:(NSError**)error
error:(out NSError**)error
{
// NOTE: we want to avoid loading at all when entries are being overwritten, even in the face of lazy loading:
// consider that nil _contents implies that no valid entries have been loaded, and newEntries cannot possibly contain any of our old entries
// therefore, if _contents are nil, we don't need to lazily load them in since these newEntries are meant to totally overwrite the archive
// or, if _contents are non-nil, the contents have already been loaded and we also don't need to lazily load them in

// determine how many entries to skip, where initial old and new entries match
NSUInteger oldEntriesCount = _entries.count;
NSUInteger newEntriesCount = newEntries.count;
Expand Down Expand Up @@ -270,11 +273,8 @@ - (BOOL)updateEntries:(NSArray*)newEntries
error:&underlyingError])
return ZZRaiseErrorNo(error, ZZReplaceWriteErrorCode, @{NSUnderlyingErrorKey : underlyingError});

// clear entries + content
_contents = nil;
_entries = nil;

return YES;
// reload entries + content
return [self loadCanMiss:NO error:error];
}

@end
Expand Down
2 changes: 1 addition & 1 deletion zipzap/ZZArchiveEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
@protocol ZZArchiveEntryWriter;

/**
* The ZZArchiveEntry class represents an entry in the ZZArchive or ZZMutableArchive instance.
* The ZZArchiveEntry class represents an entry in the ZZArchive instance.
*/
@interface ZZArchiveEntry : NSObject

Expand Down
12 changes: 11 additions & 1 deletion zipzap/ZZConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,14 @@ typedef NS_ENUM(uint8_t, ZZAESEncryptionStrength)
* Use 256-bit AES for encryption.
*/
ZZAESEncryptionStrength256 = 0x03
};
};

/**
* An NSNumber object that contains the string encoding to use for entry file names and comments. Default is NSUTF8StringEncoding.
*/
extern NSString* const ZZOpenOptionsEncodingKey;

/**
* An NSNumber object that determines whether to create the archive file if it is missing. Creation occurs during -[ZZArchive updateEntries:error:]. Default is @NO.
*/
extern NSString* const ZZOpenOptionsCreateIfMissingKey;
13 changes: 13 additions & 0 deletions zipzap/ZZConstants.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// ZZConstants.m
// zipzap
//
// Created by Glen Low on 2/09/14.
//
//

#import "ZZConstants.h"

NSString* const ZZOpenOptionsEncodingKey = @"ZZOpenOptionsEncodingKey";

NSString* const ZZOpenOptionsCreateIfMissingKey = @"ZZOpenOptionsCreateIfMissingKey";
Loading

0 comments on commit b59b5e7

Please sign in to comment.