2828 lines
115 KiB
Objective-C
2828 lines
115 KiB
Objective-C
//
|
|
// UZKArchive.m
|
|
// UnzipKit
|
|
//
|
|
//
|
|
|
|
#import "UZKArchive.h"
|
|
|
|
#import "zip.h"
|
|
|
|
#import "UZKFileInfo.h"
|
|
#import "UZKFileInfo_Private.h"
|
|
#import "UnzipKitMacros.h"
|
|
#import "NSURL+UnzipKitExtensions.h"
|
|
|
|
|
|
NSString *UZKErrorDomain = @"UZKErrorDomain";
|
|
|
|
#define FILE_IN_ZIP_MAX_NAME_LENGTH (512)
|
|
|
|
|
|
typedef NS_ENUM(NSUInteger, UZKFileMode) {
|
|
UZKFileModeUnassigned = -1,
|
|
UZKFileModeUnzip = 0,
|
|
UZKFileModeCreate,
|
|
UZKFileModeAppend
|
|
};
|
|
|
|
static NSBundle *_resources = nil;
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wundef"
|
|
#if UNIFIED_LOGGING_SUPPORTED
|
|
os_log_t unzipkit_log;
|
|
BOOL unzipkitIsAtLeast10_13SDK;
|
|
#endif
|
|
#pragma clang diagnostic pop
|
|
|
|
|
|
@interface UZKArchive ()
|
|
|
|
- (instancetype)init NS_UNAVAILABLE;
|
|
|
|
- (instancetype)initWithFile:(NSURL *)fileURL password:(NSString*)password error:(NSError * __autoreleasing*)error
|
|
#if (TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_7_0) || MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_9
|
|
NS_DESIGNATED_INITIALIZER
|
|
#endif
|
|
;
|
|
|
|
@property (strong) NSData *fileBookmark;
|
|
@property (strong) NSURL *fallbackURL;
|
|
|
|
@property (assign) NSInteger openCount;
|
|
|
|
@property (assign) UZKFileMode mode;
|
|
@property (assign) zipFile zipFile;
|
|
@property (assign) unzFile unzFile;
|
|
@property (strong) NSDictionary *archiveContents;
|
|
|
|
@property (strong) NSObject *threadLock;
|
|
|
|
@property (assign) BOOL commentRetrieved;
|
|
|
|
@end
|
|
|
|
|
|
@implementation UZKArchive
|
|
|
|
@synthesize comment = _comment;
|
|
|
|
|
|
#pragma mark - Deprecated Convenience Methods
|
|
|
|
|
|
+ (UZKArchive *)zipArchiveAtPath:(NSString *)filePath
|
|
{
|
|
return [[UZKArchive alloc] initWithPath:filePath error:nil];
|
|
}
|
|
|
|
+ (UZKArchive *)zipArchiveAtURL:(NSURL *)fileURL
|
|
{
|
|
return [[UZKArchive alloc] initWithURL:fileURL error:nil];
|
|
}
|
|
|
|
+ (UZKArchive *)zipArchiveAtPath:(NSString *)filePath password:(NSString *)password
|
|
{
|
|
return [[UZKArchive alloc] initWithPath:filePath password:password error:nil];
|
|
}
|
|
|
|
+ (UZKArchive *)zipArchiveAtURL:(NSURL *)fileURL password:(NSString *)password
|
|
{
|
|
return [[UZKArchive alloc] initWithURL:fileURL password:password error:nil];
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Initializers
|
|
|
|
+ (void)initialize {
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
NSBundle *mainBundle = [NSBundle mainBundle];
|
|
NSURL *resourcesURL = [mainBundle URLForResource:@"UnzipKitResources" withExtension:@"bundle"];
|
|
|
|
_resources = (resourcesURL
|
|
? [NSBundle bundleWithURL:resourcesURL]
|
|
: mainBundle);
|
|
|
|
UZKLogInit();
|
|
});
|
|
}
|
|
|
|
- (instancetype)init {
|
|
NSAssert(NO, @"Do not use -init. Use one of the -initWithPath or -initWithURL variants", nil);
|
|
@throw nil;
|
|
}
|
|
|
|
- (instancetype)initWithPath:(NSString *)filePath error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self initWithFile:[NSURL fileURLWithPath:filePath] error:error];
|
|
}
|
|
|
|
- (instancetype)initWithURL:(NSURL *)fileURL error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self initWithFile:fileURL error:error];
|
|
}
|
|
|
|
- (instancetype)initWithPath:(NSString *)filePath password:(NSString *)password error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self initWithFile:[NSURL fileURLWithPath:filePath]
|
|
password:password
|
|
error:error];
|
|
}
|
|
|
|
- (instancetype)initWithURL:(NSURL *)fileURL password:(NSString *)password error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self initWithFile:fileURL password:password error:error];
|
|
}
|
|
|
|
- (instancetype)initWithFile:(NSURL *)fileURL error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self initWithFile:fileURL password:nil error:error];
|
|
}
|
|
|
|
- (instancetype)initWithFile:(NSURL *)fileURL password:(NSString*)password error:(NSError * __autoreleasing*)error
|
|
{
|
|
if ((self = [super init])) {
|
|
UZKCreateActivity("Init Archive");
|
|
|
|
if (!fileURL) {
|
|
UZKLogError("Nil fileURL passed to UZKArchive initializer")
|
|
return nil;
|
|
}
|
|
|
|
UZKLogInfo("Initializing archive with URL %{public}@, path %{public}@, password %{public}@", fileURL, fileURL.path, [password length] != 0 ? @"given" : @"not given");
|
|
|
|
if ([fileURL checkResourceIsReachableAndReturnError:NULL]) {
|
|
NSError *bookmarkError = nil;
|
|
if (![self storeFileBookmark:fileURL error:&bookmarkError]) {
|
|
UZKLogError("Error creating bookmark to ZIP archive: %{public}@", bookmarkError);
|
|
|
|
if (error) {
|
|
*error = bookmarkError;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
} else {
|
|
UZKLogInfo("URL %{public}@ doesn't yet exist", fileURL)
|
|
}
|
|
|
|
UZKLogDebug("Initializing private fields");
|
|
|
|
_openCount = 0;
|
|
_mode = UZKFileModeUnassigned;
|
|
|
|
_fallbackURL = fileURL;
|
|
_password = password;
|
|
_threadLock = [[NSObject alloc] init];
|
|
|
|
_commentRetrieved = NO;
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Properties
|
|
|
|
|
|
- (NSURL *)fileURL
|
|
{
|
|
UZKCreateActivity("Read Archive URL");
|
|
|
|
NSError *checkExistsError = nil;
|
|
|
|
if (!self.fileBookmark
|
|
|| (self.fallbackURL && [self.fallbackURL checkResourceIsReachableAndReturnError:&checkExistsError]))
|
|
{
|
|
UZKLogDebug("checkResourceIsReachableAndReturnError returned false with error: %{public}@", checkExistsError);
|
|
UZKLogInfo("Returning fallback URL for archive");
|
|
return self.fallbackURL;
|
|
}
|
|
|
|
UZKLogInfo("Resolving archive bookmark (base64):\n%{public}@", [self.fileBookmark base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]);
|
|
|
|
BOOL bookmarkIsStale = NO;
|
|
NSError *error = nil;
|
|
|
|
NSURL *result = [NSURL URLByResolvingBookmarkData:self.fileBookmark
|
|
options:(NSURLBookmarkResolutionOptions)0
|
|
relativeToURL:nil
|
|
bookmarkDataIsStale:&bookmarkIsStale
|
|
error:&error];
|
|
|
|
|
|
if (!result) {
|
|
UZKLogFault("Error resolving bookmark to ZIP archive: %{public}@", error);
|
|
return nil;
|
|
}
|
|
|
|
UZKLogDebug("Resolved bookmark. URL: %{public}@, isStale: %{public}@", result, bookmarkIsStale ? @"YES" : @"NO");
|
|
|
|
if (bookmarkIsStale) {
|
|
UZKLogDebug("Refreshing stale bookmark");
|
|
self.fallbackURL = result;
|
|
|
|
if (![self storeFileBookmark:result
|
|
error:&error]) {
|
|
UZKLogFault("Error creating fresh bookmark to ZIP archive: %{public}@", error);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (NSString *)filename
|
|
{
|
|
UZKCreateActivity("Read Archive Filename");
|
|
|
|
NSURL *url = self.fileURL;
|
|
|
|
if (!url) {
|
|
return nil;
|
|
}
|
|
|
|
return url.path;
|
|
}
|
|
|
|
- (NSString *)comment
|
|
{
|
|
UZKCreateActivity("Read Archive Comment");
|
|
|
|
if (self.commentRetrieved) {
|
|
UZKLogDebug("Returning cached comment");
|
|
return _comment;
|
|
}
|
|
|
|
_comment = [self readGlobalComment];
|
|
return _comment;
|
|
}
|
|
|
|
- (void)setComment:(NSString *)comment
|
|
{
|
|
UZKCreateActivity("Write Archive Comment");
|
|
|
|
_comment = comment;
|
|
self.commentRetrieved = YES;
|
|
|
|
UZKLogInfo("Opening archive in Append mode with comment set to write it");
|
|
|
|
NSError *error = nil;
|
|
BOOL success = [self performActionWithArchiveOpen:nil
|
|
inMode:UZKFileModeAppend
|
|
error:&error];
|
|
|
|
if (!success) {
|
|
UZKLogError("Failed to write comment to archive: %{public}@", error);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Zip file detection
|
|
|
|
|
|
+ (BOOL)pathIsAZip:(NSString *)filePath
|
|
{
|
|
UZKCreateActivity("Determining File Type (Path)");
|
|
|
|
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath];
|
|
|
|
if (!handle) {
|
|
UZKLogError("No file handle returned for path: %{public}@", filePath);
|
|
return NO;
|
|
}
|
|
|
|
@try {
|
|
NSData *fileData = [handle readDataOfLength:4];
|
|
|
|
if (fileData.length < 4) {
|
|
UZKLogDebug("File is not a ZIP. Less than 4 bytes of data");
|
|
return NO;
|
|
}
|
|
|
|
const unsigned char *dataBytes = fileData.bytes;
|
|
|
|
// First two bytes must equal 'PK'
|
|
if (dataBytes[0] != 0x50 || dataBytes[1] != 0x4b) {
|
|
UZKLogDebug("File is not a ZIP. First two bytes are not PK");
|
|
return NO;
|
|
}
|
|
|
|
// Check for standard Zip
|
|
if (dataBytes[2] == 0x03 &&
|
|
dataBytes[3] == 0x04) {
|
|
UZKLogDebug("File is a standard ZIP");
|
|
return YES;
|
|
}
|
|
|
|
// Check for empty Zip
|
|
if (dataBytes[2] == 0x05 &&
|
|
dataBytes[3] == 0x06) {
|
|
UZKLogDebug("File is an empty ZIP");
|
|
return YES;
|
|
}
|
|
|
|
// Check for spanning Zip
|
|
if (dataBytes[2] == 0x07 &&
|
|
dataBytes[3] == 0x08) {
|
|
UZKLogDebug("File is a spanning ZIP");
|
|
return YES;
|
|
}
|
|
|
|
UZKLogDebug("File is not a ZIP. Unknown contents in 3rd and 4th bytes (%02X %02X)", dataBytes[2], dataBytes[3]);
|
|
}
|
|
@finally {
|
|
[handle closeFile];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
+ (BOOL)urlIsAZip:(NSURL *)fileURL
|
|
{
|
|
UZKCreateActivity("Determining File Type (URL)");
|
|
|
|
if (!fileURL || !fileURL.path) {
|
|
UZKLogDebug("File is not a ZIP: nil URL or path");
|
|
return NO;
|
|
}
|
|
|
|
return [UZKArchive pathIsAZip:(NSString* _Nonnull)fileURL.path];
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Read Methods
|
|
|
|
|
|
- (NSArray<NSString*> *)listFilenames:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Listing Filenames");
|
|
|
|
NSArray *zipInfos = [self listFileInfo:error];
|
|
|
|
if (!zipInfos) {
|
|
UZKLogDebug("No file info returned");
|
|
return nil;
|
|
}
|
|
|
|
return (NSArray* _Nonnull)[zipInfos valueForKeyPath:@"filename"];
|
|
}
|
|
|
|
- (NSArray<UZKFileInfo*> *)listFileInfo:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Listing File Info");
|
|
|
|
if (error) {
|
|
*error = nil;
|
|
}
|
|
|
|
NSError *checkExistsError = nil;
|
|
if (![self.fileURL checkResourceIsReachableAndReturnError:&checkExistsError]) {
|
|
UZKLogError("File %{public}@ doesn't exist: %{public}@", self.fileURL, checkExistsError);
|
|
return @[];
|
|
}
|
|
|
|
NSError *unzipError;
|
|
|
|
__weak UZKArchive *welf = self;
|
|
NSMutableArray *zipInfos = [NSMutableArray array];
|
|
|
|
BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Finding File Info Items");
|
|
|
|
UZKLogInfo("Getting global info...");
|
|
unzGoToNextFile(welf.unzFile);
|
|
|
|
unz_global_info gi;
|
|
int err = unzGetGlobalInfo(welf.unzFile, &gi);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting global info (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeArchiveNotFound: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeArchiveNotFound
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
NSUInteger fileCount = gi.number_entry;
|
|
UZKLogDebug("fileCount: %lu", (unsigned long)fileCount);
|
|
|
|
UZKLogInfo("Going to first file...");
|
|
err = unzGoToFirstFile(welf.unzFile);
|
|
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error going to first file in archive (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeFileNavigationError: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeFileNavigationError
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
for (NSUInteger i = 0; i < fileCount; i++) {
|
|
UZKLogDebug("Iterating through file info (iteration #%lu)", (unsigned long)i+1);
|
|
UZKFileInfo *info = [welf currentFileInZipInfo:innerError];
|
|
|
|
if (info) {
|
|
UZKLogDebug("Info found: %{public}@", info.filename);
|
|
[zipInfos addObject:info];
|
|
} else {
|
|
UZKLogDebug("Info not found");
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Going to next file...");
|
|
err = unzGoToNextFile(welf.unzFile);
|
|
if (err == UNZ_END_OF_LIST_OF_FILE) {
|
|
UZKLogInfo("End of file found");
|
|
return;
|
|
}
|
|
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error navigating to next file (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeFileNavigationError: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeFileNavigationError
|
|
detail:detail];
|
|
return;
|
|
}
|
|
}
|
|
} inMode:UZKFileModeUnzip error:&unzipError];
|
|
|
|
if (!success) {
|
|
if (error) {
|
|
*error = unzipError;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
return [zipInfos copy];
|
|
}
|
|
|
|
- (BOOL)extractFilesTo:(NSString *)destinationDirectory
|
|
overwrite:(BOOL)overwrite
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
return [self extractFilesTo:destinationDirectory
|
|
overwrite:overwrite
|
|
progress:nil
|
|
error:error];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (BOOL)extractFilesTo:(NSString *)destinationDirectory
|
|
overwrite:(BOOL)overwrite
|
|
progress:(void (^)(UZKFileInfo *currentFile, CGFloat percentArchiveDecompressed))progressBlock
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Extracting Files to Directory");
|
|
|
|
NSError *listError = nil;
|
|
NSArray *fileInfo = [self listFileInfo:&listError];
|
|
|
|
if (!fileInfo || listError) {
|
|
UZKLogError("Error listing contents of archive: %{public}@", listError);
|
|
|
|
if (error) {
|
|
*error = listError;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
NSFileManager *fm = [[NSFileManager alloc] init];
|
|
|
|
NSNumber *totalSize = [fileInfo valueForKeyPath:@"@sum.uncompressedSize"];
|
|
UZKLogDebug("totalSize: %lld", totalSize.longLongValue);
|
|
__block long long bytesDecompressed = 0;
|
|
__block NSInteger filesExtracted = 0;
|
|
|
|
NSProgress *progress = [self beginProgressOperation:totalSize.longLongValue];
|
|
progress.kind = NSProgressKindFile;
|
|
|
|
__weak UZKArchive *welf = self;
|
|
NSError *extractError = nil;
|
|
|
|
BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Performing Extraction");
|
|
|
|
NSError *strongError = nil;
|
|
|
|
@try {
|
|
for (UZKFileInfo *info in fileInfo) {
|
|
UZKLogDebug("Extracting %{public}@ to disk", info.filename);
|
|
|
|
if (progress.isCancelled) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error locating file '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"),
|
|
info.filename];
|
|
UZKLogError("Halted file extraction due to user cancellation: %{public}@", detail);
|
|
[welf assignError:&strongError code:UZKErrorCodeUserCancelled
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
@autoreleasepool {
|
|
if (progressBlock) {
|
|
progressBlock(info, bytesDecompressed / totalSize.doubleValue);
|
|
}
|
|
|
|
if (![self locateFileInZip:info.filename error:&strongError]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error locating file '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"),
|
|
info.filename];
|
|
UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail);
|
|
[welf assignError:&strongError code:UZKErrorCodeFileNotFoundInArchive
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
NSString *extractPath = [destinationDirectory stringByAppendingPathComponent:info.filename];
|
|
UZKLogDebug("Extracting to %{public}@", extractPath);
|
|
if ([fm fileExistsAtPath:extractPath] && !overwrite) {
|
|
UZKLogDebug("File exists and overwrite==NO. Skipping file");
|
|
return;
|
|
}
|
|
|
|
NSString *extractDir = (info.isDirectory
|
|
? extractPath
|
|
: extractPath.stringByDeletingLastPathComponent);
|
|
if (![fm fileExistsAtPath:extractDir]) {
|
|
UZKLogDebug("Creating directories for path %{public}@", extractDir);
|
|
BOOL directoriesCreated = [fm createDirectoryAtPath:extractDir
|
|
withIntermediateDirectories:YES
|
|
attributes:nil
|
|
error:error];
|
|
if (!directoriesCreated) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to create destination directory: %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
extractDir];
|
|
UZKLogError("UZKErrorCodeOutputError: %{public}@", detail);
|
|
[welf assignError:&strongError code:UZKErrorCodeOutputError
|
|
detail:detail];
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (info.isDirectory) {
|
|
UZKLogDebug("Created empty directory")
|
|
continue;
|
|
}
|
|
|
|
NSURL *deflatedDirectoryURL = [NSURL fileURLWithPath:destinationDirectory];
|
|
NSURL *deflatedFileURL = [deflatedDirectoryURL URLByAppendingPathComponent:info.filename];
|
|
[progress setUserInfoObject:deflatedFileURL
|
|
forKey:NSProgressFileURLKey];
|
|
[progress setUserInfoObject:info
|
|
forKey:UZKProgressInfoKeyFileInfoExtracting];
|
|
NSString *path = deflatedFileURL.path;
|
|
|
|
UZKLogDebug("Creating empty file at path %{public}@", path);
|
|
BOOL createSuccess = [fm createFileAtPath:path
|
|
contents:nil
|
|
attributes:nil];
|
|
|
|
if (!createSuccess) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error creating current file (%d) '%@'", @"UnzipKit", _resources, @"Detailed error string"),
|
|
strongError, info.filename];
|
|
UZKLogError("UZKErrorCodeOutputError: %{public}@", detail);
|
|
[welf assignError:&strongError code:UZKErrorCodeOutputError
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Opening file handle for URL %{public}@", deflatedFileURL);
|
|
NSFileHandle *deflatedFileHandle = [NSFileHandle fileHandleForWritingToURL:deflatedFileURL
|
|
error:&strongError];
|
|
|
|
|
|
if (!deflatedFileHandle) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error writing to file: %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
deflatedFileURL];
|
|
UZKLogError("UZKErrorCodeOutputError: %{public}@", detail);
|
|
[welf assignError:&strongError code:UZKErrorCodeOutputError
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Extracting buffered data");
|
|
BOOL extractSuccess = [welf extractBufferedDataFromFile:info.filename
|
|
error:&strongError
|
|
action:
|
|
^(NSData *dataChunk, CGFloat percentDecompressed) {
|
|
UZKLogDebug("Writing data chunk of size %lu (%lld total so far)", (unsigned long)dataChunk.length, bytesDecompressed);
|
|
bytesDecompressed += dataChunk.length;
|
|
[deflatedFileHandle writeData:dataChunk];
|
|
if (progressBlock) {
|
|
progressBlock(info, (double)bytesDecompressed / totalSize.doubleValue);
|
|
}
|
|
}];
|
|
|
|
UZKLogDebug("Closing file handle");
|
|
[deflatedFileHandle closeFile];
|
|
|
|
// Restore the timestamp and permission attributes of the file
|
|
NSDictionary* attribs = @{NSFileModificationDate: info.timestamp,
|
|
NSFilePosixPermissions: @(info.posixPermissions)};
|
|
[[NSFileManager defaultManager] setAttributes:attribs ofItemAtPath:path error:nil];
|
|
|
|
if (!extractSuccess) {
|
|
UZKLogError("Error extracting file (%ld): %{public}@", (long)strongError.code, strongError.localizedDescription);
|
|
|
|
UZKLogInfo("Cleaning up target directory after failure: %{public}@", deflatedFileURL);
|
|
// Remove the directory we were going to unzip to if it fails.
|
|
[fm removeItemAtURL:deflatedDirectoryURL
|
|
error:nil];
|
|
return;
|
|
}
|
|
|
|
[progress setUserInfoObject:@(++filesExtracted)
|
|
forKey:NSProgressFileCompletedCountKey];
|
|
[progress setUserInfoObject:@(fileInfo.count)
|
|
forKey:NSProgressFileTotalCountKey];
|
|
progress.completedUnitCount = bytesDecompressed;
|
|
}
|
|
}
|
|
}
|
|
@finally {
|
|
if (strongError && innerError) {
|
|
*innerError = strongError;
|
|
}
|
|
}
|
|
} inMode:UZKFileModeUnzip error:&extractError];
|
|
|
|
if (error) {
|
|
*error = extractError ? extractError : nil;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
- (nullable NSData *)extractData:(UZKFileInfo *)fileInfo
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self extractDataFromFile:fileInfo.filename
|
|
error:error];
|
|
}
|
|
|
|
- (nullable NSData *)extractData:(UZKFileInfo *)fileInfo
|
|
progress:(void (^)(CGFloat))progress
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self extractDataFromFile:fileInfo.filename
|
|
progress:progress
|
|
error:error];
|
|
}
|
|
|
|
- (nullable NSData *)extractDataFromFile:(NSString *)filePath
|
|
error:(NSError * __autoreleasing *)error
|
|
{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
return [self extractDataFromFile:filePath
|
|
progress:nil
|
|
error:error];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (nullable NSData *)extractDataFromFile:(NSString *)filePath
|
|
progress:(void (^)(CGFloat))progressBlock
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Extracting Data from File");
|
|
|
|
NSMutableData *result = [NSMutableData data];
|
|
|
|
UZKLogInfo("Extracting buffered data from file %{public}@", filePath);
|
|
|
|
NSError *extractError = nil;
|
|
BOOL success = [self extractBufferedDataFromFile:filePath
|
|
error:&extractError
|
|
action:^(NSData *dataChunk, CGFloat percentDecompressed) {
|
|
UZKLogDebug("Appending data chunk of size %lu (%.3f%% complete)", (unsigned long)dataChunk.length, (double)percentDecompressed * 100);
|
|
|
|
if (progressBlock) {
|
|
progressBlock(percentDecompressed);
|
|
}
|
|
|
|
[result appendData:dataChunk];
|
|
}];
|
|
|
|
if (progressBlock) {
|
|
UZKLogDebug("Declaring extraction progress as completed");
|
|
progressBlock(1.0);
|
|
}
|
|
|
|
if (success) {
|
|
return [NSData dataWithData:result];
|
|
}
|
|
|
|
UZKLogError("Error extracting file (%ld): %{public}@", (long)extractError.code, extractError.localizedDescription);
|
|
|
|
if (error) {
|
|
*error = extractError;
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (BOOL)performOnFilesInArchive:(void (^)(UZKFileInfo *, BOOL *))action
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Performing Action on Each File");
|
|
|
|
UZKLogInfo("Listing file info");
|
|
|
|
NSError *listError = nil;
|
|
NSArray *fileInfo = [self listFileInfo:&listError];
|
|
|
|
if (listError || !fileInfo) {
|
|
UZKLogError("Failed to list the files in the archive: %{public}@", listError);
|
|
|
|
if (error) {
|
|
*error = listError;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
NSProgress *progress = [self beginProgressOperation:fileInfo.count];
|
|
|
|
UZKLogInfo("Sorting file info by name/path");
|
|
|
|
NSArray *sortedFileInfo = [fileInfo sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"filename" ascending:YES]]];
|
|
|
|
BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Iterating Each File Info");
|
|
|
|
BOOL stop = NO;
|
|
|
|
for (UZKFileInfo *info in sortedFileInfo) {
|
|
if (progress.isCancelled) {
|
|
UZKLogInfo("File info iteration was cancelled");
|
|
break;
|
|
}
|
|
UZKLogDebug("Performing action on %{public}@", info.filename);
|
|
action(info, &stop);
|
|
progress.completedUnitCount += 1;
|
|
|
|
if (stop) {
|
|
UZKLogInfo("Action dictated an early stop");
|
|
progress.completedUnitCount = progress.totalUnitCount;
|
|
break;
|
|
}
|
|
}
|
|
} inMode:UZKFileModeUnzip error:error];
|
|
|
|
if (progress.isCancelled) {
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"User cancelled operation", @"UnzipKit", _resources, @"Detailed error string");
|
|
UZKLogError("UZKErrorCodeUserCancelled: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeUserCancelled
|
|
detail:detail];
|
|
return NO;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)performOnDataInArchive:(void (^)(UZKFileInfo *, NSData *, BOOL *))action
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Performing Action on Each File's Data");
|
|
|
|
__weak UZKArchive *welf = self;
|
|
|
|
return [self performOnFilesInArchive:^(UZKFileInfo *fileInfo, BOOL *stop) {
|
|
UZKLogInfo("Locating file %{public}@", fileInfo.filename);
|
|
|
|
if (![welf locateFileInZip:fileInfo.filename error:error]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to locate '%@' in archive during-perform on-data operation", @"UnzipKit", _resources, @"Detailed error string"),
|
|
fileInfo.filename];
|
|
UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail);
|
|
[welf assignError:error code:UZKErrorCodeFileNotFoundInArchive
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogInfo("Reading file from archive");
|
|
|
|
NSData *fileData = [welf readFile:fileInfo.filename
|
|
length:fileInfo.uncompressedSize
|
|
error:error];
|
|
|
|
if (!fileData) {
|
|
UZKLogError("Error reading file %{public}@ in archive", fileInfo.filename);
|
|
return;
|
|
}
|
|
|
|
UZKLogInfo("Performing action on file data");
|
|
action(fileInfo, fileData, stop);
|
|
} error:error];
|
|
}
|
|
|
|
- (BOOL)extractBufferedDataFromFile:(NSString *)filePath
|
|
error:(NSError * __autoreleasing*)error
|
|
action:(void (^)(NSData *, CGFloat))action
|
|
{
|
|
UZKCreateActivity("Extracting Data into Buffer");
|
|
|
|
NSProgress *progress = [self beginProgressOperation:0];
|
|
|
|
__weak UZKArchive *welf = self;
|
|
NSUInteger bufferSize = 1024 * 256; // 256 kb, arbitrary
|
|
|
|
BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) {
|
|
if (![welf locateFileInZip:filePath error:innerError]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to locate '%@' in archive during buffered read", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath];
|
|
UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeFileNotFoundInArchive
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogInfo("Getting file info");
|
|
UZKFileInfo *info = [welf currentFileInZipInfo:innerError];
|
|
|
|
if (!info) {
|
|
UZKLogError("Failed to get info of file %{public}@ in archive", filePath);
|
|
return;
|
|
}
|
|
|
|
progress.totalUnitCount = info.uncompressedSize;
|
|
|
|
UZKLogInfo("Opening file");
|
|
if (![welf openFile:innerError]) {
|
|
UZKLogError("Failed to open file %{public}@ in archive", filePath);
|
|
return;
|
|
}
|
|
|
|
long long bytesDecompressed = 0;
|
|
|
|
NSError *strongInnerError = nil;
|
|
|
|
for (;;)
|
|
{
|
|
if (progress.isCancelled) {
|
|
UZKLogInfo("Buffered data read cancelled");
|
|
return;
|
|
}
|
|
|
|
@autoreleasepool {
|
|
UZKLogDebug("Reading file data");
|
|
NSMutableData *data = [NSMutableData dataWithLength:bufferSize];
|
|
int bytesRead = unzReadCurrentFile(welf.unzFile, data.mutableBytes, (unsigned)bufferSize);
|
|
|
|
if (bytesRead < 0) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to read file %@ in zip", @"UnzipKit", _resources, @"Detailed error string"),
|
|
info.filename];
|
|
UZKLogError("Error reading data (code %d): %{public}@", bytesRead, detail);
|
|
[welf assignError:&strongInnerError code:bytesRead
|
|
detail:detail];
|
|
break;
|
|
}
|
|
else if (bytesRead == 0) {
|
|
UZKLogDebug("Done reading file");
|
|
break;
|
|
}
|
|
|
|
UZKLogDebug("bytesRead: %{iec-bytes}d (%d bytes)", bytesRead, bytesRead);
|
|
|
|
data.length = bytesRead;
|
|
bytesDecompressed += bytesRead;
|
|
|
|
if (action) {
|
|
UZKLogDebug("Performing action on chunk of data");
|
|
action([data copy], bytesDecompressed / (CGFloat)info.uncompressedSize);
|
|
}
|
|
|
|
progress.completedUnitCount = bytesDecompressed;
|
|
}
|
|
}
|
|
|
|
if (strongInnerError) {
|
|
*innerError = strongInnerError;
|
|
return;
|
|
}
|
|
|
|
UZKLogInfo("Closing file...");
|
|
int err = unzCloseCurrentFile(welf.unzFile);
|
|
if (err != UNZ_OK) {
|
|
if (err == UZKErrorCodeCRCError) {
|
|
err = UZKErrorCodeInvalidPassword;
|
|
}
|
|
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"Error closing current file during buffered read", @"UnzipKit", _resources, @"Detailed error string");
|
|
UZKLogError("Error closing file (code %d): %{public}@", err, detail);
|
|
[welf assignError:innerError code:err
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
} inMode:UZKFileModeUnzip error:error];
|
|
|
|
if (progress.isCancelled) {
|
|
UZKLogError("User cancelled data extraction");
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"User cancelled data read", @"UnzipKit", _resources, @"Detailed error string");
|
|
[self assignError:error code:UZKErrorCodeUserCancelled
|
|
detail:detail];
|
|
return NO;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)isPasswordProtected
|
|
{
|
|
UZKCreateActivity("Checking Password Protection");
|
|
|
|
NSError *error = nil;
|
|
NSArray *fileInfos = [self listFileInfo:&error];
|
|
|
|
if (error) {
|
|
UZKLogError("Error checking whether file is password protected: %{public}@", error);
|
|
return NO;
|
|
}
|
|
|
|
for (UZKFileInfo *fileInfo in fileInfos) {
|
|
if (fileInfo.isEncryptedWithPassword) {
|
|
UZKLogDebug("File %{public}@ is encrypted. Not checking any others", fileInfo.filename);
|
|
return YES;
|
|
}
|
|
|
|
UZKLogDebug("File %{public}@ is NOT encrypted. Checking remaining files", fileInfo.filename);
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)validatePassword
|
|
{
|
|
UZKCreateActivity("Validating Password");
|
|
|
|
if (!self.isPasswordProtected) {
|
|
UZKLogInfo("Archive is not password protected. There is no password to validate");
|
|
return YES;
|
|
}
|
|
|
|
NSError *error = nil;
|
|
NSArray *fileInfos = [self listFileInfo:&error];
|
|
|
|
if (error) {
|
|
UZKLogError("Error checking whether file is password protected: %{public}@", error);
|
|
return NO;
|
|
}
|
|
|
|
if (!fileInfos || fileInfos.count == 0) {
|
|
UZKLogInfo("There are no files in the archive");
|
|
return NO;
|
|
}
|
|
|
|
UZKFileInfo *smallest = [fileInfos sortedArrayUsingComparator:^NSComparisonResult(UZKFileInfo *file1, UZKFileInfo *file2) {
|
|
if (file1.uncompressedSize < file2.uncompressedSize)
|
|
return NSOrderedAscending;
|
|
if (file1.uncompressedSize > file2.uncompressedSize)
|
|
return NSOrderedDescending;
|
|
return NSOrderedSame;
|
|
}].firstObject;
|
|
|
|
UZKLogDebug("Decrypting smallest file in archive: %{public}@", smallest.filename);
|
|
|
|
NSData *smallestData = [self extractData:(UZKFileInfo* _Nonnull)smallest
|
|
error:&error];
|
|
|
|
if (error || !smallestData) {
|
|
UZKLogInfo("Error while checking password: %{public}@", error);
|
|
return NO;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)checkDataIntegrity
|
|
{
|
|
return [self checkDataIntegrityOfFile:(NSString * _Nonnull)nil];
|
|
}
|
|
|
|
- (BOOL)checkDataIntegrityOfFile:(NSString *)filePath
|
|
{
|
|
UZKCreateActivity("Checking data integrity");
|
|
|
|
UZKLogInfo("Checking integrity of %{public}@", filePath ? filePath : @"all files in archive");
|
|
|
|
NSError *performOnDataError = nil;
|
|
__block BOOL dataIsValid = NO;
|
|
|
|
BOOL success = [self performOnDataInArchive:
|
|
^(UZKFileInfo *fileInfo, NSData *fileData, BOOL *stop) {
|
|
// Only set this once we've reached this point, validating the archive's structures
|
|
dataIsValid = YES;
|
|
|
|
if (filePath && ![filePath isEqualToString:fileInfo.filename]) {
|
|
UZKLogDebug("Skipping '%{public}@' != %{public}@", fileInfo.filename, filePath);
|
|
return;
|
|
}
|
|
|
|
uLong extractedCRC = crc32(0, fileData.bytes, (uInt)fileData.length);
|
|
|
|
if (extractedCRC != fileInfo.CRC) {
|
|
UZKLogError("CRC mismatch in '%{public}@': expected %010lu, found %010lu",
|
|
fileInfo.filename, (unsigned long)fileInfo.CRC, extractedCRC)
|
|
dataIsValid = NO;
|
|
}
|
|
|
|
if (!dataIsValid || filePath) {
|
|
*stop = YES;
|
|
}
|
|
}
|
|
error:&performOnDataError];
|
|
|
|
if (!success) {
|
|
UZKLogError("Failed to iterate through data: %{public}@", performOnDataError);
|
|
}
|
|
|
|
return success && dataIsValid;
|
|
}
|
|
|
|
|
|
#pragma mark - Write Methods
|
|
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:nil
|
|
compressionMethod:UZKCompressionMethodDefault
|
|
password:nil
|
|
overwrite:YES
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
progress:(void (^)(CGFloat percentCompressed))progress
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:nil
|
|
compressionMethod:UZKCompressionMethodDefault
|
|
password:nil
|
|
overwrite:YES
|
|
progress:progress
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
compressionMethod:UZKCompressionMethodDefault
|
|
password:nil
|
|
overwrite:YES
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
progress:(void (^)(CGFloat percentCompressed))progress
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
compressionMethod:UZKCompressionMethodDefault
|
|
password:nil
|
|
overwrite:YES
|
|
progress:progress
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(NSString *)password
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:YES
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(NSString *)password
|
|
progress:(void (^)(CGFloat percentCompressed))progress
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:YES
|
|
progress:progress
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(NSString *)password
|
|
overwrite:(BOOL)overwrite
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:overwrite
|
|
progress:nil
|
|
error:error];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(NSString *)password
|
|
overwrite:(BOOL)overwrite
|
|
progress:(void (^)(CGFloat percentCompressed))progressBlock
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:0
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:overwrite
|
|
progress:progressBlock
|
|
error:error];
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(nullable NSDate *)fileDate
|
|
posixPermissions:(short)permissions
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(nullable NSString *)password
|
|
overwrite:(BOOL)overwrite
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
return [self writeData:data
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:permissions
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:overwrite
|
|
progress:nil
|
|
error:error];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (BOOL)writeData:(NSData *)data
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
posixPermissions:(short)permissions
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(NSString *)password
|
|
overwrite:(BOOL)overwrite
|
|
progress:(void (^)(CGFloat percentCompressed))progressBlock
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Writing Data");
|
|
|
|
UZKLogInfo("Writing data to archive. filePath: %{public}@, fileDate: %{time_t}ld, compressionMethod: %ld, password: %{public}@, "
|
|
"overwrite: %{public}@, progress block specified: %{public}@, error pointer specified: %{public}@",
|
|
filePath, lrint(fileDate.timeIntervalSince1970), (long)method, password != nil ? @"<specified>" : @"(null)", overwrite ? @"YES" : @"NO",
|
|
progressBlock ? @"YES" : @"NO", error ? @"YES" : @"NO");
|
|
|
|
const NSUInteger bufferSize = 4096; //Arbitrary
|
|
const void *bytes = data.bytes;
|
|
|
|
NSProgress *progress = [self beginProgressOperation:data.length];
|
|
progress.cancellable = NO;
|
|
|
|
if (progressBlock) {
|
|
UZKLogDebug("Calling progress block with zero");
|
|
progressBlock(0);
|
|
}
|
|
|
|
__weak UZKArchive *welf = self;
|
|
uLong calculatedCRC = crc32(0, data.bytes, (uInt)data.length);
|
|
UZKLogDebug("Calculated CRC: %010lu", calculatedCRC);
|
|
|
|
BOOL success = [self performWriteAction:^int(uLong *crc, NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Performing File Write");
|
|
|
|
NSAssert(crc, @"No CRC reference passed", nil);
|
|
*crc = calculatedCRC;
|
|
|
|
UZKLogInfo("Iterating through all data, in %lu chunks", (unsigned long)bufferSize);
|
|
|
|
for (NSUInteger i = 0; i <= data.length; i += bufferSize) {
|
|
UZKLogDebug("Writing chunk starting at byte %lu", (unsigned long)i);
|
|
|
|
unsigned int dataRemaining = (unsigned int)(data.length - i);
|
|
unsigned int size = (unsigned int)(dataRemaining < bufferSize ? dataRemaining : bufferSize);
|
|
int err = zipWriteInFileInZip(welf.zipFile, (const char *)bytes + i, size);
|
|
|
|
if (err != ZIP_OK) {
|
|
UZKLogError("Error writing data: %d", err);
|
|
return err;
|
|
}
|
|
|
|
progress.completedUnitCount += size;
|
|
|
|
if (progressBlock) {
|
|
double percentComplete = i / (double)data.length;
|
|
UZKLogDebug("Calling progress block at %.3f%%", percentComplete * 100);
|
|
progressBlock(percentComplete);
|
|
}
|
|
}
|
|
|
|
return ZIP_OK;
|
|
}
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:permissions
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:overwrite
|
|
CRC:calculatedCRC
|
|
error:error];
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
error:(NSError * __autoreleasing*)error
|
|
block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action
|
|
{
|
|
return [self writeIntoBuffer:filePath
|
|
fileDate:nil
|
|
posixPermissions:0
|
|
compressionMethod:UZKCompressionMethodDefault
|
|
overwrite:YES
|
|
CRC:0
|
|
password:nil
|
|
error:error
|
|
block:action];
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
error:(NSError * __autoreleasing*)error
|
|
block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action
|
|
{
|
|
return [self writeIntoBuffer:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:0
|
|
compressionMethod:UZKCompressionMethodDefault
|
|
overwrite:YES
|
|
CRC:0
|
|
password:nil
|
|
error:error
|
|
block:action];
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
error:(NSError * __autoreleasing*)error
|
|
block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action
|
|
{
|
|
return [self writeIntoBuffer:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:0
|
|
compressionMethod:method
|
|
overwrite:YES
|
|
CRC:0
|
|
password:nil
|
|
error:error
|
|
block:action];
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
overwrite:(BOOL)overwrite
|
|
error:(NSError * __autoreleasing*)error
|
|
block:(BOOL(^)(BOOL(^writeData)(const void *bytes, unsigned int length), NSError * __autoreleasing*actionError))action
|
|
{
|
|
return [self writeIntoBuffer:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:0
|
|
compressionMethod:method
|
|
overwrite:overwrite
|
|
CRC:0
|
|
password:nil
|
|
error:error
|
|
block:action];
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
overwrite:(BOOL)overwrite
|
|
CRC:(uLong)preCRC
|
|
error:(NSError *__autoreleasing *)error
|
|
block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action
|
|
{
|
|
return [self writeIntoBuffer:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:0
|
|
compressionMethod:method
|
|
overwrite:overwrite
|
|
CRC:preCRC
|
|
password:nil
|
|
error:error
|
|
block:action];
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
overwrite:(BOOL)overwrite
|
|
CRC:(uLong)preCRC
|
|
password:(NSString *)password
|
|
error:(NSError *__autoreleasing *)error
|
|
block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action
|
|
{
|
|
return [self writeIntoBuffer:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:0
|
|
compressionMethod:method
|
|
overwrite:overwrite
|
|
CRC:preCRC
|
|
password:password
|
|
error:error
|
|
block:action];
|
|
}
|
|
|
|
- (BOOL)writeIntoBuffer:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
posixPermissions:(short)permissions
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
overwrite:(BOOL)overwrite
|
|
CRC:(uLong)preCRC
|
|
password:(NSString *)password
|
|
error:(NSError *__autoreleasing *)error
|
|
block:(BOOL (^)(BOOL (^)(const void *, unsigned int), NSError *__autoreleasing *))action
|
|
{
|
|
UZKCreateActivity("Writing Into Buffer");
|
|
|
|
UZKLogInfo("Writing data into buffer. filePath: %{public}@, fileDate: %{time_t}ld, compressionMethod: %ld, "
|
|
"overwrite: %{public}@, CRC: %010lu, password: %{public}@, error pointer specified: %{public}@",
|
|
filePath, lrint(fileDate.timeIntervalSince1970), (long)method, overwrite ? @"YES" : @"NO", preCRC,
|
|
password != nil ? @"<specified>" : @"(null)", error ? @"YES" : @"NO");
|
|
|
|
NSAssert(preCRC != 0 || ([password length] == 0 && [self.password length] == 0),
|
|
@"Cannot provide a password when writing into a buffer, "
|
|
"unless a CRC is provided up front for inclusion in the header", nil);
|
|
|
|
__weak UZKArchive *welf = self;
|
|
|
|
BOOL success = [self performWriteAction:^int(uLong *crc, NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Performing File Write");
|
|
|
|
NSAssert(crc, @"No CRC reference passed", nil);
|
|
|
|
if (!action) {
|
|
UZKLogInfo("No write action specified. This is unusual, but not fatal");
|
|
return ZIP_OK;
|
|
}
|
|
|
|
BOOL result = action(^BOOL(const void *bytes, unsigned int length) {
|
|
UZKLogInfo("Writing %{iec-bytes}u (%u bytes) into archive from buffer", length, length);
|
|
int writeErr = zipWriteInFileInZip(self.zipFile, bytes, length);
|
|
if (writeErr != ZIP_OK) {
|
|
UZKLogError("Error writing data from buffer: %d", writeErr);
|
|
return NO;
|
|
}
|
|
|
|
uLong oldCRC = *crc;
|
|
*crc = crc32(oldCRC, bytes, (uInt)length);
|
|
UZKLogDebug("Calculated new CRC: %010lu from old CRC: %010lu", *crc, oldCRC);
|
|
|
|
return YES;
|
|
}, innerError);
|
|
|
|
if (preCRC != 0 && *crc != preCRC) {
|
|
uLong calculatedCRC = *crc;
|
|
NSString *preCRCStr = [NSString stringWithFormat:@"%010lu", preCRC];
|
|
NSString *calculatedCRCStr = [NSString stringWithFormat:@"%010lu", calculatedCRC];
|
|
NSString *detail = [NSString stringWithFormat:
|
|
NSLocalizedStringFromTableInBundle(@"Incorrect CRC provided\n%@ given\n%@ calculated", @"UnzipKit", _resources, @"CRC mismatch error detail"),
|
|
preCRCStr, calculatedCRCStr];
|
|
UZKLogError("UZKErrorCodePreCRCMismatch: %{public}@", detail);
|
|
return [welf assignError:innerError code:UZKErrorCodePreCRCMismatch
|
|
detail:detail];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
filePath:filePath
|
|
fileDate:fileDate
|
|
posixPermissions:permissions
|
|
compressionMethod:method
|
|
password:password
|
|
overwrite:overwrite
|
|
CRC:preCRC
|
|
error:error];
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)deleteFile:(NSString *)filePath error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Deleting File");
|
|
|
|
// Thanks to Ivan A. Krestinin for much of the code below: http://www.winimage.com/zLibDll/del.cpp
|
|
|
|
UZKLogInfo("Deleting file %{public}@ from archive", filePath);
|
|
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
|
|
if (!self.filename || ![fm fileExistsAtPath:(NSString* _Nonnull)self.filename]) {
|
|
UZKLogError("No archive exists at path %{public}@, when trying to delete %{public}@", self.filename, filePath);
|
|
return YES;
|
|
}
|
|
|
|
NSString *randomString = [NSString stringWithFormat:@"%@.zip", [[NSProcessInfo processInfo] globallyUniqueString]];
|
|
NSURL *temporaryURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()] URLByAppendingPathComponent:randomString];
|
|
|
|
UZKLogInfo("Writing new archive without deleted file to %{public}@", temporaryURL.path);
|
|
|
|
const char *original_filename = self.filename.UTF8String;
|
|
const char *del_file = filePath.UTF8String;
|
|
const char *temp_filename = temporaryURL.path.UTF8String;
|
|
|
|
// Open source and destination files
|
|
|
|
UZKLogInfo("Opening original archive at %{public}s", original_filename);
|
|
zipFile source_zip = unzOpen(original_filename);
|
|
if (source_zip == NULL) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening the source file while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogInfo("Opening temporary archive at %{public}s", temp_filename);
|
|
zipFile dest_zip = zipOpen(temp_filename, APPEND_STATUS_CREATE);
|
|
if (dest_zip == NULL) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening the destination file while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip");
|
|
unzClose(source_zip);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Get global commentary
|
|
|
|
UZKLogInfo("Getting global info from source zip");
|
|
unz_global_info global_info;
|
|
int err = unzGetGlobalInfo(source_zip, &global_info);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting the global info of the source file while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
char *global_comment = NULL;
|
|
|
|
if (global_info.size_comment > 0)
|
|
{
|
|
UZKLogInfo("Getting global comment from source zip");
|
|
global_comment = (char*)malloc(global_info.size_comment+1);
|
|
if ((global_comment == NULL) && (global_info.size_comment != 0)) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error reading the global comment of the source file while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
if ((unsigned int)unzGetGlobalComment(source_zip, global_comment, global_info.size_comment + 1) != global_info.size_comment) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error reading the global comment of the source file while deleting %@ (wrong size)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
}
|
|
|
|
BOOL noFilesDeleted = YES;
|
|
int filesCopied = 0;
|
|
|
|
NSString *filenameToDelete = [UZKArchive figureOutCString:del_file];
|
|
|
|
UZKLogInfo("Navigating to first file in source archive");
|
|
int nextFileReturnValue = unzGoToFirstFile(source_zip);
|
|
|
|
while (nextFileReturnValue == UNZ_OK)
|
|
{
|
|
// Get zipped file info
|
|
char filename_inzip[FILE_IN_ZIP_MAX_NAME_LENGTH];
|
|
unz_file_info64 unzipInfo;
|
|
|
|
UZKLogDebug("Getting file info");
|
|
err = unzGetCurrentFileInfo64(source_zip, &unzipInfo, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting file info of file while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
NSString *currentFileName = [UZKArchive figureOutCString:filename_inzip];
|
|
UZKLogDebug("Current file is %{public}@", currentFileName);
|
|
|
|
// If this is the file to delete
|
|
if ([filenameToDelete isEqualToString:currentFileName.decomposedStringWithCanonicalMapping]) {
|
|
UZKLogDebug("This file is the one we're deleting");
|
|
noFilesDeleted = NO;
|
|
} else {
|
|
UZKLogDebug("Allocating extra field");
|
|
char *extra_field = (char*)malloc(unzipInfo.size_file_extra);
|
|
if ((extra_field == NULL) && (unzipInfo.size_file_extra != 0)) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error allocating extra_field info of %@ while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Allocating commentary");
|
|
char *commentary = (char*)malloc(unzipInfo.size_file_comment);
|
|
if ((commentary == NULL) && (unzipInfo.size_file_comment != 0)) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error allocating commentary info of %@ while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Getting file info");
|
|
err = unzGetCurrentFileInfo64(source_zip, &unzipInfo, filename_inzip, FILE_IN_ZIP_MAX_NAME_LENGTH, extra_field, unzipInfo.size_file_extra, commentary, unzipInfo.size_file_comment);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error reading extra_field and commentary info of %@ while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary");
|
|
free(extra_field);
|
|
free(commentary);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Open source archive for raw reading
|
|
|
|
int method;
|
|
int level;
|
|
UZKLogDebug("Opening file in source archive");
|
|
err = unzOpenCurrentFile2(source_zip, &method, &level, 1);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening %@ for raw reading while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Getting local extra field size");
|
|
int size_local_extra = unzGetLocalExtrafield(source_zip, NULL, 0);
|
|
if (size_local_extra < 0) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting size_local_extra for file while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Allocating local extra field");
|
|
void *local_extra = malloc(size_local_extra);
|
|
if ((local_extra == NULL) && (size_local_extra != 0)) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error allocating local_extra for file %@ while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Getting local extra field");
|
|
if (unzGetLocalExtrafield(source_zip, local_extra, size_local_extra) < 0) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting local_extra for file %@ while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// This malloc may fail if the file is very large
|
|
UZKLogDebug("Allocating data read buffer");
|
|
void *buf = malloc((unsigned long)unzipInfo.compressed_size);
|
|
if ((buf == NULL) && (unzipInfo.compressed_size != 0)) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error allocating buffer for file %@ while deleting %@. Is it too large?", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Read file
|
|
UZKLogDebug("Reading data into buffer");
|
|
int size = unzReadCurrentFile(source_zip, buf, (uInt)unzipInfo.compressed_size);
|
|
if ((unsigned int)size != unzipInfo.compressed_size) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error reading %@ into buffer while deleting %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra, buf");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
free(buf);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Open destination archive
|
|
|
|
UZKLogDebug("Filling zip_fileinfo struct");
|
|
zip_fileinfo zipInfo;
|
|
memcpy (&zipInfo.tmz_date, &unzipInfo.tmu_date, sizeof(tm_unz));
|
|
zipInfo.dosDate = unzipInfo.dosDate;
|
|
zipInfo.internal_fa = unzipInfo.internal_fa;
|
|
zipInfo.external_fa = unzipInfo.external_fa;
|
|
|
|
UZKLogDebug("Opening file in destination archive");
|
|
err = zipOpenNewFileInZip2(dest_zip, filename_inzip, &zipInfo,
|
|
local_extra, size_local_extra, extra_field, (uInt)unzipInfo.size_file_extra, commentary,
|
|
method, level, 1);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening %@ in destination zip while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra, buf");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
free(buf);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Write file
|
|
UZKLogDebug("Writing file in destination archive");
|
|
err = zipWriteInFileInZip(dest_zip, buf, (uInt)unzipInfo.compressed_size);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error writing %@ to destination zip while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra, buf");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
free(buf);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Close destination archive
|
|
UZKLogDebug("Closing file in destination archive");
|
|
err = zipCloseFileInZipRaw64(dest_zip, unzipInfo.uncompressed_size, unzipInfo.crc);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing %@ in destination zip while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra, buf");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
free(buf);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Close source archive
|
|
UZKLogDebug("Closing source archive");
|
|
err = unzCloseCurrentFile(source_zip);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing %@ in source zip while deleting %@ (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
currentFileName, filePath, err];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Closing source_zip, dest_zip, freeing global_comment, extra_field, commentary, local_extra, buf");
|
|
zipClose(dest_zip, NULL);
|
|
unzClose(source_zip);
|
|
free(global_comment);
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
free(buf);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Freeing extra_field, commentary, local_extra, buf");
|
|
free(extra_field);
|
|
free(commentary);
|
|
free(local_extra);
|
|
free(buf);
|
|
|
|
++filesCopied;
|
|
}
|
|
|
|
UZKLogDebug("Going to next file");
|
|
nextFileReturnValue = unzGoToNextFile(source_zip);
|
|
}
|
|
|
|
UZKLogDebug("Closing source_zip, dest_zip (writing global comment)");
|
|
zipClose(dest_zip, global_comment);
|
|
unzClose(source_zip);
|
|
|
|
UZKLogDebug("Freeing global_comment");
|
|
free(global_comment);
|
|
|
|
// Don't swap the files
|
|
if (noFilesDeleted) {
|
|
UZKLogInfo("No files deleted. Not replacing the original archive with the copy");
|
|
return YES;
|
|
}
|
|
|
|
// Failure
|
|
if (nextFileReturnValue != UNZ_END_OF_LIST_OF_FILE)
|
|
{
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to seek to the next file, while deleting %@ from the archive", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filenameToDelete];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
UZKLogDebug("Removing temp_filename");
|
|
remove(temp_filename);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail];
|
|
}
|
|
|
|
// Replace old file with the new (trimmed) one
|
|
NSURL *newURL;
|
|
|
|
NSString *temporaryVolume = temporaryURL.volumeName;
|
|
NSString *destinationVolume = self.fileURL.volumeName;
|
|
|
|
if ([temporaryVolume isEqualToString:destinationVolume]) {
|
|
UZKLogInfo("Temporary file URL and destination URL share a volume. Replacing one file with another");
|
|
NSError *replaceError = nil;
|
|
BOOL result = [fm replaceItemAtURL:(NSURL* _Nonnull)self.fileURL
|
|
withItemAtURL:temporaryURL
|
|
backupItemName:nil
|
|
options:NSFileManagerItemReplacementWithoutDeletingBackupItem
|
|
resultingItemURL:&newURL
|
|
error:&replaceError];
|
|
|
|
if (!result) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to replace the old archive with the new one, after deleting '%@' from it (%@)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filenameToDelete, replaceError.localizedDescription];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail
|
|
underlyer:replaceError];
|
|
}
|
|
} else {
|
|
UZKLogInfo("Temporary file URL and destination URL reside on different volumes. Will remove original archive and copy over the replacement");
|
|
newURL = self.fileURL;
|
|
|
|
UZKLogDebug("Removing original archive: %{public}@", newURL);
|
|
NSError *deleteError = nil;
|
|
if (![fm removeItemAtURL:(NSURL* _Nonnull)newURL
|
|
error:&deleteError]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to remove original archive from external volume '%@', after deleting '%@' from a new version to replace it (%@)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
destinationVolume, filenameToDelete, deleteError.localizedDescription];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail
|
|
underlyer:deleteError];
|
|
}
|
|
|
|
UZKLogDebug("Copying temporary archive from %{public}@ to destination %{public}@", temporaryURL, newURL);
|
|
NSError *copyError = nil;
|
|
if (![fm copyItemAtURL:temporaryURL
|
|
toURL:(NSURL* _Nonnull)newURL
|
|
error:©Error]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to copy archive to external volume '%@', after deleting '%@' from it (%@)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
destinationVolume, filenameToDelete, copyError.localizedDescription];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail
|
|
underlyer:copyError];
|
|
}
|
|
}
|
|
|
|
UZKLogInfo("Updating archive bookmark");
|
|
NSError *bookmarkError = nil;
|
|
if (![self storeFileBookmark:newURL
|
|
error:&bookmarkError]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to store the new file bookmark to the archive after deleting '%@' from it: %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filenameToDelete, bookmarkError.localizedDescription];
|
|
UZKLogError("UZKErrorCodeDeleteFile: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeDeleteFile
|
|
detail:detail
|
|
underlyer:bookmarkError];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Private Methods
|
|
|
|
|
|
- (BOOL)performActionWithArchiveOpen:(void(^)(NSError * __autoreleasing*innerError))action
|
|
inMode:(UZKFileMode)mode
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Performing Action With Archive Open");
|
|
|
|
@synchronized(self.threadLock) {
|
|
if (error) {
|
|
*error = nil;
|
|
}
|
|
|
|
NSError *openError = nil;
|
|
NSError *actionError = nil;
|
|
|
|
@try {
|
|
if (![self openFile:self.filename
|
|
inMode:mode
|
|
withPassword:self.password
|
|
error:&openError])
|
|
{
|
|
UZKLogDebug("Archive failed to open. Reporting error");
|
|
|
|
if (error) {
|
|
*error = openError;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
if (action) {
|
|
UZKLogDebug("Performing action");
|
|
action(&actionError);
|
|
}
|
|
}
|
|
@finally {
|
|
NSError *closeError = nil;
|
|
if (![self closeFile:&closeError inMode:mode]) {
|
|
UZKLogDebug("Archive failed to close");
|
|
|
|
if (error && !actionError && !openError) {
|
|
*error = closeError;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
if (error && actionError && !openError) {
|
|
*error = actionError;
|
|
}
|
|
|
|
return !actionError;
|
|
}
|
|
}
|
|
|
|
- (BOOL)performWriteAction:(int(^)(uLong *crc, NSError * __autoreleasing*innerError))write
|
|
filePath:(NSString *)filePath
|
|
fileDate:(NSDate *)fileDate
|
|
posixPermissions:(short)permissions
|
|
compressionMethod:(UZKCompressionMethod)method
|
|
password:(NSString *)password
|
|
overwrite:(BOOL)overwrite
|
|
CRC:(uLong)crc
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Performing Write");
|
|
|
|
if (overwrite) {
|
|
UZKLogInfo("Overwriting %{public}@ if it already exists. Will look for existing file to delete", filePath);
|
|
|
|
NSError *listFilesError = nil;
|
|
NSArray *existingFiles;
|
|
|
|
@autoreleasepool {
|
|
UZKLogDebug("Listing file info");
|
|
existingFiles = [self listFileInfo:&listFilesError];
|
|
}
|
|
|
|
if (existingFiles) {
|
|
UZKLogDebug("Existing files found. Looking for matches to filePath %{public}@", filePath);
|
|
NSIndexSet *matchingFiles = [existingFiles indexesOfObjectsPassingTest:
|
|
^BOOL(UZKFileInfo *info, NSUInteger idx, BOOL *stop) {
|
|
if ([info.filename isEqualToString:filePath]) {
|
|
*stop = YES;
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}];
|
|
|
|
if (matchingFiles.count > 0 && ![self deleteFile:filePath error:error]) {
|
|
UZKLogError("Failed to delete %{public}@ before writing new data for it", filePath);
|
|
return NO;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!password) {
|
|
UZKLogDebug("No password specified for file. Using archive's password: %{public}@", password != nil ? @"<hidden>" : @"(null)");
|
|
password = self.password;
|
|
}
|
|
|
|
__weak UZKArchive *welf = self;
|
|
|
|
BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Performing Write Action");
|
|
|
|
UZKLogDebug("Making zip_fileinfo struct for date %{time_t}ld", lrint(fileDate.timeIntervalSince1970));
|
|
zip_fileinfo zi = [UZKArchive zipFileInfoForDate:fileDate
|
|
posixPermissions:permissions];
|
|
|
|
const char *passwordStr = NULL;
|
|
|
|
if (password) {
|
|
UZKLogDebug("Converting password to NSISOLatin1StringEncoding");
|
|
passwordStr = [password cStringUsingEncoding:NSISOLatin1StringEncoding];
|
|
}
|
|
|
|
UZKLogDebug("Opening new file...");
|
|
int err = zipOpenNewFileInZip3(welf.zipFile,
|
|
filePath.UTF8String,
|
|
&zi,
|
|
NULL, 0, NULL, 0, NULL,
|
|
(method != UZKCompressionMethodNone) ? Z_DEFLATED : 0,
|
|
method,
|
|
0,
|
|
-MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
|
|
passwordStr,
|
|
crc);
|
|
|
|
if (err != ZIP_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening file '%@' for write (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath, err];
|
|
UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeFileOpenForWrite
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Writing file");
|
|
uLong outCRC = 0;
|
|
err = write(&outCRC, innerError);
|
|
if (err < 0) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error writing to file '%@' (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath, err];
|
|
UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeFileWrite
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Closing file...");
|
|
err = zipCloseFileInZipRaw(self.zipFile, 0, outCRC);
|
|
if (err != ZIP_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing file '%@' for write (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath, err];
|
|
UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail);
|
|
[welf assignError:innerError code:UZKErrorCodeFileWrite
|
|
detail:detail];
|
|
return;
|
|
}
|
|
|
|
} inMode:UZKFileModeAppend error:error];
|
|
|
|
return success;
|
|
}
|
|
|
|
- (BOOL)openFile:(NSString *)zipFile
|
|
inMode:(UZKFileMode)mode
|
|
withPassword:(NSString *)aPassword
|
|
error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("Opening File");
|
|
|
|
UZKLogDebug("Opening file in mode %lu", (unsigned long)mode);
|
|
|
|
if (error) {
|
|
*error = nil;
|
|
}
|
|
|
|
if (self.mode != UZKFileModeUnassigned && self.mode != mode) {
|
|
NSString *message;
|
|
|
|
if (self.mode == UZKFileModeUnzip) {
|
|
message = NSLocalizedStringFromTableInBundle(@"Unable to begin writing to the archive until all read operations have completed", @"UnzipKit", _resources, @"Detailed error string");
|
|
} else {
|
|
message = NSLocalizedStringFromTableInBundle(@"Unable to begin reading from the archive until all write operations have completed", @"UnzipKit", _resources, @"Detailed error string");
|
|
}
|
|
|
|
UZKLogError("UZKErrorCodeMixedModeAccess: %{public}@", message);
|
|
return [self assignError:error code:UZKErrorCodeMixedModeAccess detail:message];
|
|
}
|
|
|
|
if (mode != UZKFileModeUnzip && self.openCount > 0) {
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"Attempted to write to the archive while another write operation is already in progress", @"UnzipKit", _resources, @"Detailed error string");
|
|
UZKLogError("UZKErrorCodeFileWrite: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeFileWrite
|
|
detail:detail];
|
|
}
|
|
|
|
// Always initialize comment, so it can be read when the file is closed
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
|
if (!self.commentRetrieved) {
|
|
UZKLogDebug("Retrieving comment");
|
|
self.commentRetrieved = YES;
|
|
_comment = [self readGlobalComment];
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
if (self.openCount++ > 0) {
|
|
UZKLogDebug("File is already open. Not going any further");
|
|
return YES;
|
|
}
|
|
|
|
self.mode = mode;
|
|
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
|
|
switch (mode) {
|
|
case UZKFileModeUnzip: {
|
|
if (![fm fileExistsAtPath:zipFile]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"No file found at path %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
zipFile];
|
|
UZKLogError("UZKErrorCodeArchiveNotFound: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeArchiveNotFound
|
|
detail:detail];
|
|
return NO;
|
|
}
|
|
|
|
UZKLogDebug("Opening file for read...");
|
|
self.unzFile = unzOpen(self.filename.UTF8String);
|
|
if (self.unzFile == NULL) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening zip file %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
zipFile];
|
|
UZKLogError("UZKErrorCodeBadZipFile: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeBadZipFile
|
|
detail:detail];
|
|
return NO;
|
|
}
|
|
|
|
UZKLogDebug("Seeking to first file...");
|
|
int err = unzGoToFirstFile(self.unzFile);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error going to first file in archive (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeFileNavigationError: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeFileNavigationError
|
|
detail:detail];
|
|
return NO;
|
|
}
|
|
|
|
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
|
|
|
|
UZKLogInfo("Reading file info to cache file positions");
|
|
|
|
do {
|
|
@autoreleasepool {
|
|
UZKLogDebug("Reading file info for current file in zip");
|
|
UZKFileInfo *info = [self currentFileInZipInfo:error];
|
|
|
|
if (!info) {
|
|
UZKLogDebug("No info returned. Exiting loop");
|
|
return NO;
|
|
}
|
|
|
|
UZKLogDebug("Got info for %{public}@", info.filename);
|
|
|
|
unz_file_pos pos;
|
|
int err = unzGetFilePos(self.unzFile, &pos);
|
|
if (err == UNZ_OK && info.filename) {
|
|
NSValue *dictValue = [NSValue valueWithBytes:&pos
|
|
objCType:@encode(unz_file_pos)];
|
|
dic[info.filename.decomposedStringWithCanonicalMapping] = dictValue;
|
|
}
|
|
}
|
|
} while (unzGoToNextFile (self.unzFile) != UNZ_END_OF_LIST_OF_FILE);
|
|
|
|
self.archiveContents = [dic copy];
|
|
break;
|
|
}
|
|
case UZKFileModeCreate:
|
|
case UZKFileModeAppend:
|
|
if (![fm fileExistsAtPath:zipFile]) {
|
|
NSError *createFileError = nil;
|
|
|
|
UZKLogDebug("Creating empty file, since it doesn't exist yet");
|
|
if (![[NSData data] writeToFile:zipFile options:NSDataWritingAtomic error:&createFileError]) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Failed to create new file for archive: %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
createFileError.localizedDescription];
|
|
UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeFileOpenForWrite
|
|
detail:detail
|
|
underlyer:createFileError];
|
|
}
|
|
|
|
UZKLogDebug("Storing bookmark for newly created file");
|
|
NSError *bookmarkError = nil;
|
|
if (![self storeFileBookmark:[NSURL fileURLWithPath:zipFile]
|
|
error:&bookmarkError])
|
|
{
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error creating bookmark to new archive file: %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
bookmarkError.localizedDescription];
|
|
UZKLogError("UZKErrorCodeFileOpenForWrite: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeFileOpenForWrite
|
|
detail:detail
|
|
underlyer:bookmarkError];
|
|
}
|
|
}
|
|
|
|
int appendStatus = mode == UZKFileModeCreate ? APPEND_STATUS_CREATE : APPEND_STATUS_ADDINZIP;
|
|
|
|
UZKLogDebug("Opening archive for write");
|
|
self.zipFile = zipOpen(self.filename.UTF8String, appendStatus);
|
|
if (self.zipFile == NULL) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening zip file for write: %@", @"UnzipKit", _resources, @"Detailed error string"),
|
|
zipFile];
|
|
UZKLogError("UZKErrorCodeArchiveNotFound: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeArchiveNotFound
|
|
detail:detail];
|
|
return NO;
|
|
}
|
|
break;
|
|
|
|
case UZKFileModeUnassigned:
|
|
NSAssert(NO, @"Cannot call -openFile:inMode:withPassword:error: with a mode of UZKFileModeUnassigned (%lu)", (unsigned long)mode);
|
|
break;
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)closeFile:(NSError * __autoreleasing*)error
|
|
inMode:(UZKFileMode)mode
|
|
{
|
|
UZKCreateActivity("Closing File");
|
|
|
|
if (mode != self.mode) {
|
|
UZKLogInfo("Closing archive for mode %lu, but archive is currently in mode %lu", (unsigned long)mode, (unsigned long)self.mode);
|
|
return NO;
|
|
}
|
|
|
|
if (--self.openCount > 0) {
|
|
UZKLogDebug("Not closing file, as there have been more calls to open it than to close it");
|
|
return YES;
|
|
}
|
|
|
|
int err;
|
|
const char *cmt;
|
|
const char *logverb;
|
|
|
|
BOOL closeSucceeded = YES;
|
|
|
|
switch (self.mode) {
|
|
case UZKFileModeUnzip:
|
|
if (!self.unzFile) {
|
|
UZKLogDebug("self.unzFile is nil. File already closed?");
|
|
break;
|
|
}
|
|
UZKLogDebug("Closing file in read mode...");
|
|
err = unzClose(self.unzFile);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing file in archive after read (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeZLibError: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeZLibError
|
|
detail:detail];
|
|
closeSucceeded = NO;
|
|
}
|
|
break;
|
|
|
|
case UZKFileModeCreate:
|
|
case UZKFileModeAppend:
|
|
logverb = self.mode == UZKFileModeCreate ? "create" : "append";
|
|
|
|
if (!self.zipFile) {
|
|
UZKLogDebug("self.zipFile is nil. File already closed?");
|
|
break;
|
|
}
|
|
cmt = self.comment.UTF8String;
|
|
UZKLogDebug("Closing file in %{public}s mode with comment %{public}s...", logverb, cmt);
|
|
err = zipClose(self.zipFile, cmt);
|
|
if (err != ZIP_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error closing file in archive in write mode %lu (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
self.mode, err];
|
|
UZKLogError("UZKErrorCodeZLibError: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeZLibError
|
|
detail:detail];
|
|
closeSucceeded = NO;
|
|
}
|
|
break;
|
|
|
|
case UZKFileModeUnassigned:
|
|
NSAssert(NO, @"Unbalanced call to -closeFile:, openCount == %ld", (long)self.openCount);
|
|
break;
|
|
}
|
|
|
|
if (self.openCount == 0) {
|
|
self.mode = UZKFileModeUnassigned;
|
|
}
|
|
|
|
return closeSucceeded;
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Zip File Navigation
|
|
|
|
|
|
- (UZKFileInfo *)currentFileInZipInfo:(NSError * __autoreleasing*)error {
|
|
UZKCreateActivity("currentFileInZipInfo");
|
|
|
|
char filename_inzip[FILE_IN_ZIP_MAX_NAME_LENGTH];
|
|
unz_file_info64 file_info;
|
|
|
|
UZKLogDebug("Getting file info...");
|
|
int err = unzGetCurrentFileInfo64(self.unzFile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting current file info (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeArchiveNotFound: %{public}@", detail);
|
|
[self assignError:error code:UZKErrorCodeArchiveNotFound
|
|
detail:detail];
|
|
return nil;
|
|
}
|
|
|
|
NSString *filename = [UZKArchive figureOutCString:filename_inzip];
|
|
return [UZKFileInfo fileInfo:&file_info filename:filename];
|
|
}
|
|
|
|
- (BOOL)locateFileInZip:(NSString *)fileNameInZip error:(NSError * __autoreleasing*)error {
|
|
UZKCreateActivity("locateFileInZip");
|
|
|
|
UZKLogDebug("Looking up file position");
|
|
NSValue *filePosValue = self.archiveContents[fileNameInZip.decomposedStringWithCanonicalMapping];
|
|
|
|
if (!filePosValue) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"No file position found for '%@'", @"UnzipKit", _resources, @"Detailed error string"),
|
|
fileNameInZip];
|
|
UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeFileNotFoundInArchive
|
|
detail:detail];
|
|
}
|
|
|
|
unz_file_pos pos;
|
|
[filePosValue getValue:&pos];
|
|
|
|
UZKLogDebug("Going to file position");
|
|
int err = unzGoToFilePos(self.unzFile, &pos);
|
|
|
|
if (err == UNZ_END_OF_LIST_OF_FILE) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"File '%@' not found in archive", @"UnzipKit", _resources, @"Detailed error string"),
|
|
fileNameInZip];
|
|
UZKLogError("UZKErrorCodeFileNotFoundInArchive: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeFileNotFoundInArchive
|
|
detail:detail];
|
|
}
|
|
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error seeking to file position (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("%{public}@", detail);
|
|
return [self assignError:error code:err
|
|
detail:detail];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Zip File Operations
|
|
|
|
|
|
- (BOOL)openFile:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("openFile");
|
|
|
|
char filename_inzip[FILE_IN_ZIP_MAX_NAME_LENGTH];
|
|
unz_file_info64 file_info;
|
|
|
|
UZKLogDebug("Getting file info");
|
|
int err = unzGetCurrentFileInfo64(self.unzFile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting current file info for archive (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeInternalError: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeInternalError
|
|
detail:detail];
|
|
}
|
|
|
|
const char *passwordStr = NULL;
|
|
|
|
if (self.password) {
|
|
UZKLogDebug("Encoding password in NSISOLatin1StringEncoding");
|
|
passwordStr = [self.password cStringUsingEncoding:NSISOLatin1StringEncoding];
|
|
}
|
|
|
|
if ([self isDeflate64:file_info]) {
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"Cannot open archive, since it was compressed using the Deflate64 algorithm (method ID 9)", @"UnzipKit", _resources, @"Error message");
|
|
UZKLogError("UZKErrorCodeDeflate64: %{public}@", detail);
|
|
return [self assignError:error code:UZKErrorCodeDeflate64
|
|
detail:detail];
|
|
}
|
|
|
|
UZKLogDebug("Opening file...");
|
|
err = unzOpenCurrentFilePassword(self.unzFile, passwordStr);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error opening archive (%d)", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("%{public}@", detail);
|
|
return [self assignError:error code:err
|
|
detail:detail];
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
|
|
- (NSData *)readFile:(NSString *)filePath length:(unsigned long long int)length error:(NSError * __autoreleasing*)error {
|
|
UZKCreateActivity("readFile");
|
|
|
|
UZKLogDebug("Opening file");
|
|
if (![self openFile:error]) {
|
|
return nil;
|
|
}
|
|
|
|
UZKLogDebug("Reading data...");
|
|
NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)length];
|
|
int bytes = unzReadCurrentFile(self.unzFile, data.mutableBytes, (unsigned)length);
|
|
|
|
if (bytes < 0) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error reading data from '%@' in archive", @"UnzipKit", _resources, @"Detailed error string"),
|
|
filePath];
|
|
UZKLogError("Error code %d: %{public}@", bytes, detail);
|
|
[self assignError:error code:bytes
|
|
detail:detail];
|
|
return nil;
|
|
}
|
|
|
|
UZKLogDebug("%{iec-bytes}d (%d bytes) read", bytes, bytes);
|
|
data.length = bytes;
|
|
return data;
|
|
}
|
|
|
|
- (NSString *)readGlobalComment {
|
|
UZKCreateActivity("readGlobalComment");
|
|
|
|
UZKLogDebug("Checking archive exists");
|
|
|
|
NSError *checkExistsError = nil;
|
|
if (![self.fileURL checkResourceIsReachableAndReturnError:&checkExistsError]) {
|
|
UZKLogDebug("Archive not found");
|
|
return nil;
|
|
}
|
|
|
|
__weak UZKArchive *welf = self;
|
|
__block NSString *comment = nil;
|
|
NSError *error = nil;
|
|
|
|
BOOL success = [self performActionWithArchiveOpen:^(NSError * __autoreleasing*innerError) {
|
|
UZKCreateActivity("Perform Action");
|
|
|
|
UZKLogDebug("Getting global info...");
|
|
unz_global_info global_info;
|
|
int err = unzGetGlobalInfo(welf.unzFile, &global_info);
|
|
if (err != UNZ_OK) {
|
|
NSString *detail = [NSString localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"Error getting global info of archive during comment read: %d", @"UnzipKit", _resources, @"Detailed error string"),
|
|
err];
|
|
UZKLogError("UZKErrorCodeReadComment: %{public}@", detail);
|
|
UZKLogDebug("Closing archive...");
|
|
unzClose(welf.unzFile);
|
|
|
|
[welf assignError:innerError code:UZKErrorCodeReadComment detail:detail];
|
|
return;
|
|
}
|
|
|
|
char *global_comment = NULL;
|
|
|
|
if (global_info.size_comment > 0)
|
|
{
|
|
UZKLogDebug("Allocating global comment...");
|
|
global_comment = (char*)malloc(global_info.size_comment+1);
|
|
if ((global_comment == NULL) && (global_info.size_comment != 0)) {
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"Error allocating the global comment during comment read", @"UnzipKit", _resources, @"Detailed error string");
|
|
UZKLogError("UZKErrorCodeReadComment: %{public}@", detail);
|
|
UZKLogDebug("Closing archive...");
|
|
unzClose(welf.unzFile);
|
|
|
|
[welf assignError:innerError code:UZKErrorCodeReadComment detail:detail];
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Reading global comment...");
|
|
if ((unsigned int)unzGetGlobalComment(welf.unzFile, global_comment, global_info.size_comment + 1) != global_info.size_comment) {
|
|
NSString *detail = NSLocalizedStringFromTableInBundle(@"Error reading the comment (readGlobalComment)", @"UnzipKit", _resources, @"Detailed error string");
|
|
UZKLogError("UZKErrorCodeReadComment: %{public}@", detail);
|
|
UZKLogDebug("Closing archive and freeing global_comment...");
|
|
unzClose(welf.unzFile);
|
|
free(global_comment);
|
|
|
|
[welf assignError:innerError code:UZKErrorCodeReadComment detail:@"Error reading global comment (unzGetGlobalComment)"];
|
|
return;
|
|
}
|
|
|
|
UZKLogDebug("Turning C string into NSString");
|
|
comment = [UZKArchive figureOutCString:global_comment];
|
|
free(global_comment);
|
|
}
|
|
} inMode:UZKFileModeUnzip error:&error];
|
|
|
|
self.commentRetrieved = YES;
|
|
|
|
if (!success) {
|
|
return nil;
|
|
}
|
|
|
|
return comment;
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Misc. Private Methods
|
|
|
|
|
|
- (BOOL)storeFileBookmark:(NSURL *)fileURL error:(NSError * __autoreleasing*)error
|
|
{
|
|
UZKCreateActivity("storeFileBookmark");
|
|
|
|
UZKLogDebug("Creating bookmark");
|
|
NSError *bookmarkError = nil;
|
|
self.fileBookmark = [fileURL bookmarkDataWithOptions:(NSURLBookmarkCreationOptions)0
|
|
includingResourceValuesForKeys:@[]
|
|
relativeToURL:nil
|
|
error:&bookmarkError];
|
|
|
|
if (bookmarkError) {
|
|
UZKLogFault("Error creating bookmark for URL %{public}@: %{public}@", fileURL, bookmarkError);
|
|
}
|
|
|
|
if (error) {
|
|
*error = bookmarkError ? bookmarkError : nil;
|
|
}
|
|
|
|
return bookmarkError == nil;
|
|
}
|
|
|
|
+ (NSString *)figureOutCString:(const char *)filenameBytes
|
|
{
|
|
UZKCreateActivity("figureOutCString");
|
|
|
|
UZKLogDebug("Trying out UTF-8");
|
|
NSString *stringValue = [NSString stringWithUTF8String:filenameBytes];
|
|
|
|
if (!stringValue) {
|
|
UZKLogDebug("Trying out NSWindowsCP1252StringEncoding");
|
|
stringValue = [NSString stringWithCString:filenameBytes
|
|
encoding:NSWindowsCP1252StringEncoding];
|
|
}
|
|
|
|
if (!stringValue) {
|
|
UZKLogDebug("Trying out defaultCStringEncoding");
|
|
stringValue = [NSString stringWithCString:filenameBytes
|
|
encoding:[NSString defaultCStringEncoding]];
|
|
}
|
|
|
|
UZKLogDebug("Returning decomposedStringWithCanonicalMapping");
|
|
return [stringValue decomposedStringWithCanonicalMapping];
|
|
}
|
|
|
|
+ (NSString *)errorNameForErrorCode:(NSInteger)errorCode
|
|
{
|
|
NSString *errorName;
|
|
|
|
switch (errorCode) {
|
|
case UZKErrorCodeZLibError:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error reading/writing file", @"UnzipKit", _resources, @"UZKErrorCodeZLibError");
|
|
break;
|
|
|
|
case UZKErrorCodeParameterError:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Parameter error", @"UnzipKit", _resources, @"UZKErrorCodeParameterError");
|
|
break;
|
|
|
|
case UZKErrorCodeBadZipFile:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Bad zip file", @"UnzipKit", _resources, @"UZKErrorCodeBadZipFile");
|
|
break;
|
|
|
|
case UZKErrorCodeInternalError:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Internal error", @"UnzipKit", _resources, @"UZKErrorCodeInternalError");
|
|
break;
|
|
|
|
case UZKErrorCodeCRCError:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"The data got corrupted during decompression", @"UnzipKit", _resources, @"UZKErrorCodeCRCError");
|
|
break;
|
|
|
|
case UZKErrorCodeArchiveNotFound:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Can't open archive", @"UnzipKit", _resources, @"UZKErrorCodeArchiveNotFound");
|
|
break;
|
|
|
|
case UZKErrorCodeFileNavigationError:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error navigating through the archive", @"UnzipKit", _resources, @"UZKErrorCodeFileNavigationError");
|
|
break;
|
|
|
|
case UZKErrorCodeFileNotFoundInArchive:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Can't find a file in the archive", @"UnzipKit", _resources, @"UZKErrorCodeFileNotFoundInArchive");
|
|
break;
|
|
|
|
case UZKErrorCodeOutputError:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error extracting files from the archive", @"UnzipKit", _resources, @"UZKErrorCodeOutputError");
|
|
break;
|
|
|
|
case UZKErrorCodeOutputErrorPathIsAFile:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Attempted to extract the archive to a path that is a file, not a directory", @"UnzipKit", _resources, @"UZKErrorCodeOutputErrorPathIsAFile");
|
|
break;
|
|
|
|
case UZKErrorCodeInvalidPassword:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Incorrect password provided", @"UnzipKit", _resources, @"UZKErrorCodeInvalidPassword");
|
|
break;
|
|
|
|
case UZKErrorCodeFileRead:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error reading a file in the archive", @"UnzipKit", _resources, @"UZKErrorCodeFileRead");
|
|
break;
|
|
|
|
case UZKErrorCodeFileOpenForWrite:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error opening a file in the archive to write it", @"UnzipKit", _resources, @"UZKErrorCodeFileOpenForWrite");
|
|
break;
|
|
|
|
case UZKErrorCodeFileWrite:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error writing a file in the archive", @"UnzipKit", _resources, @"UZKErrorCodeFileWrite");
|
|
break;
|
|
|
|
case UZKErrorCodeFileCloseWriting:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error clonsing a file in the archive after writing it", @"UnzipKit", _resources, @"UZKErrorCodeFileCloseWriting");
|
|
break;
|
|
|
|
case UZKErrorCodeDeleteFile:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Error deleting a file in the archive", @"UnzipKit", _resources, @"UZKErrorCodeDeleteFile");
|
|
break;
|
|
|
|
case UZKErrorCodeMixedModeAccess:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"Attempted to read before all writes have completed, or vise-versa", @"UnzipKit", _resources, @"UZKErrorCodeMixedModeAccess");
|
|
break;
|
|
|
|
case UZKErrorCodePreCRCMismatch:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"The CRC given up front doesn't match the calculated CRC", @"UnzipKit", _resources, @"UZKErrorCodePreCRCMismatch");
|
|
break;
|
|
|
|
case UZKErrorCodeDeflate64:
|
|
errorName = NSLocalizedStringFromTableInBundle(@"The archive was compressed with the Deflate64 method, which isn't supported", @"UnzipKit", _resources, @"UZKErrorCodeDeflate64");
|
|
break;
|
|
|
|
default:
|
|
errorName = [NSString localizedStringWithFormat:
|
|
NSLocalizedStringFromTableInBundle(@"Unknown error code: %ld", @"UnzipKit", _resources, @"UnknownErrorCode"), errorCode];
|
|
break;
|
|
}
|
|
|
|
return errorName;
|
|
}
|
|
|
|
+ (zip_fileinfo)zipFileInfoForDate:(NSDate *)fileDate
|
|
posixPermissions:(short)permissions
|
|
{
|
|
NSCalendar *calendar = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian];
|
|
|
|
// Use "now" if no date given
|
|
if (!fileDate) {
|
|
fileDate = [NSDate date];
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wassign-enum"
|
|
|
|
NSDateComponents *date = [calendar components:(NSCalendarUnitSecond |
|
|
NSCalendarUnitMinute |
|
|
NSCalendarUnitHour |
|
|
NSCalendarUnitDay |
|
|
NSCalendarUnitMonth |
|
|
NSCalendarUnitYear)
|
|
fromDate:fileDate];
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
zip_fileinfo zi;
|
|
zi.tmz_date.tm_sec = (uInt)date.second;
|
|
zi.tmz_date.tm_min = (uInt)date.minute;
|
|
zi.tmz_date.tm_hour = (uInt)date.hour;
|
|
zi.tmz_date.tm_mday = (uInt)date.day;
|
|
zi.tmz_date.tm_mon = (uInt)date.month - 1; // 0-indexed
|
|
zi.tmz_date.tm_year = (uInt)date.year;
|
|
zi.internal_fa = 0;
|
|
zi.external_fa = 0;
|
|
zi.dosDate = 0;
|
|
|
|
if (permissions > 0) {
|
|
unsigned long permissionsMask = (permissions & 0777) << 16;
|
|
zi.external_fa |= permissionsMask;
|
|
}
|
|
|
|
return zi;
|
|
}
|
|
|
|
/**
|
|
* @return Always returns NO
|
|
*/
|
|
- (BOOL)assignError:(NSError * __autoreleasing*)error
|
|
code:(NSInteger)errorCode
|
|
detail:(NSString *)errorDetail
|
|
{
|
|
return [self assignError:error
|
|
code:errorCode
|
|
detail:errorDetail
|
|
underlyer:nil];
|
|
}
|
|
|
|
/**
|
|
* @return Always returns NO
|
|
*/
|
|
- (BOOL)assignError:(NSError * __autoreleasing*)error
|
|
code:(NSInteger)errorCode
|
|
detail:(NSString *)errorDetail
|
|
underlyer:(NSError *)underlyingError
|
|
{
|
|
if (error) {
|
|
NSString *errorName = [UZKArchive errorNameForErrorCode:errorCode];
|
|
|
|
// If this error is being re-wrapped, include the original error
|
|
if (!underlyingError && *error && [*error isKindOfClass:[NSError class]]) {
|
|
underlyingError = *error;
|
|
}
|
|
|
|
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary:
|
|
@{NSLocalizedFailureReasonErrorKey: errorName,
|
|
NSLocalizedDescriptionKey: errorName,
|
|
NSLocalizedRecoverySuggestionErrorKey: errorDetail}];
|
|
|
|
if (self.fileURL) {
|
|
userInfo[NSURLErrorKey] = self.fileURL;
|
|
}
|
|
|
|
if (underlyingError) {
|
|
userInfo[NSUnderlyingErrorKey] = underlyingError;
|
|
}
|
|
|
|
*error = [NSError errorWithDomain:UZKErrorDomain
|
|
code:errorCode
|
|
userInfo:[NSDictionary dictionaryWithDictionary:userInfo]];
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)isDeflate64:(unz_file_info64)file_info
|
|
{
|
|
UZKCreateActivity("isDeflate64");
|
|
|
|
UZKLogDebug("Compression method: %lu", file_info.compression_method);
|
|
return file_info.compression_method == 9;
|
|
}
|
|
|
|
- (NSProgress *)beginProgressOperation:(unsigned long long)totalUnitCount
|
|
{
|
|
UZKCreateActivity("-beginProgressOperation:");
|
|
|
|
NSProgress *progress;
|
|
progress = self.progress;
|
|
self.progress = nil;
|
|
|
|
if (!progress) {
|
|
progress = [[NSProgress alloc] initWithParent:[NSProgress currentProgress]
|
|
userInfo:nil];
|
|
}
|
|
|
|
if (totalUnitCount > 0) {
|
|
progress.totalUnitCount = totalUnitCount;
|
|
}
|
|
|
|
progress.cancellable = YES;
|
|
progress.pausable = NO;
|
|
|
|
return progress;
|
|
}
|
|
|
|
|
|
@end
|
|
|
|
|