Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
b83ad8f
Excluded this branch from Travis, since it's only meant as a demo for…
abbeycode Feb 4, 2017
2230b22
Added tests, headers, and stubs for multi-volume archives (PR #38)
abbeycode Feb 9, 2017
9804c76
Merge branch 'v2.9' into multivolume
abbeycode Aug 17, 2017
02aaf7c
Unexcluded multivolume branch from Travis
abbeycode Aug 17, 2017
f923d3e
Merged v2.9 into multivolume
abbeycode Sep 16, 2017
b8217c0
Synced upstream changes from v2.9 branch
abbeycode Sep 28, 2017
96ad093
Fixed merge error
abbeycode Sep 28, 2017
0415896
Added support for volume check
aonez Jan 9, 2017
9196576
Improved detection of new format volume name scheme
aonez Jan 10, 2017
2b143c1
Fixed nullable result
aonez Feb 5, 2017
1061278
Excluded this branch from Travis, since it's only meant as a demo for…
abbeycode Feb 4, 2017
124e167
Added tests, headers, and stubs for multi-volume archives (PR #38)
abbeycode Feb 9, 2017
c054bfe
Adapted to multivolume branch
aonez Aug 17, 2017
8d3e7b0
Silenced two warnings caused by an update to unpack30.cpp (Issue #43)
abbeycode Jun 21, 2017
c0fd271
Ignoring analyzer issues for Unrar library files (Issue #43)
abbeycode Jun 22, 2017
061c2f3
Replaced simulator ID with name, since duplicate simulators have been…
abbeycode Jun 22, 2017
949a7fa
Switched from Travis-After-All to the officially supported Build Stag…
abbeycode Aug 8, 2017
2e8dd20
Added release note [ci skip]
abbeycode Aug 10, 2017
1feda29
Unexcluded multivolume branch from Travis
abbeycode Aug 17, 2017
0a8591b
Rebased to multivolume branch
aonez Aug 18, 2017
ed799b3
Trying with .travis.yml from master
aonez Aug 18, 2017
f74850c
Revert "Trying with .travis.yml from master"
aonez Aug 18, 2017
27eb8b2
Was at master, merging branch…
aonez Aug 19, 2017
1f8a963
Implementing hasMultipleVolumes
aonez Aug 19, 2017
b1b86a1
Implementing isVolume methods
aonez Aug 19, 2017
9d5e717
Implementing listVolumePaths
aonez Aug 19, 2017
8586292
Improved firstVolumePath
aonez Aug 19, 2017
9ce9134
Fixed wrong scape
aonez Aug 19, 2017
844377e
Something that must be corrected
aonez Aug 19, 2017
4a585be
Second dummy try…
aonez Aug 19, 2017
fa3139f
Removed isVolume
aonez Aug 19, 2017
9a26401
Removed fileURL overloads
aonez Aug 19, 2017
071d0bc
Unnecessary __block flag removed
aonez Aug 19, 2017
5f96585
Using NSMutableSet to get unique paths faster
aonez Aug 19, 2017
b447aec
Printing hasMultipleVolumes error
aonez Aug 19, 2017
50700ac
Opening first volume to list parts if needed
aonez Aug 19, 2017
97a24bc
Comparing unordered volume lists
aonez Aug 19, 2017
0f0e6ac
isVolume method removed
aonez Aug 19, 2017
e3c7718
Removing extra space
aonez Aug 19, 2017
83683a1
Added macros for unified logging and activity tracing, and switched t…
abbeycode Aug 8, 2017
dcaf9c9
Added localization script, empty strings file, and wiring for resourc…
abbeycode Sep 6, 2017
0f23417
Added support for NSProgress with cancellation to extraction methods,…
abbeycode Sep 18, 2017
0060bc1
Added NSProgress+cancellation support to a couple more methods for wh…
abbeycode Sep 18, 2017
560a631
Implemented NSProgressReporting protocol to make conformance more app…
abbeycode Sep 20, 2017
9587b99
Added more details to extractFiles progress reporting, along with som…
abbeycode Sep 21, 2017
99ff47c
Added progress reporting to performOnData, and additional documentati…
abbeycode Sep 21, 2017
b8b0baa
Whoops. Committed header changes for progress property
abbeycode Sep 23, 2017
9786f81
Fixed documentation
abbeycode Sep 23, 2017
3c4d1d2
Added reporting of URKFileInfo objects to progress reporting for extr…
abbeycode Sep 23, 2017
65ffbba
Deprecated methods that take a progress block argument, and created v…
abbeycode Sep 23, 2017
2144a1e
Fixed documentation
abbeycode Sep 24, 2017
ec31928
Moved ProgressReportingTests.m into place in the project
abbeycode Sep 24, 2017
660b9b1
Updated documentation
abbeycode Sep 24, 2017
5784477
Removed SWIFT_VERSION overrides, and specified 4.0 at the project level
abbeycode Sep 28, 2017
09c21db
Fixed merge error
abbeycode Sep 28, 2017
ce1717e
Excluded this branch from Travis, since it's only meant as a demo for…
abbeycode Feb 4, 2017
b090e89
Added tests, headers, and stubs for multi-volume archives (PR #38)
abbeycode Feb 9, 2017
13effa0
Adapted to multivolume branch
aonez Aug 17, 2017
602bb90
Updated to RAR lib v5.5.5 (Issue #43)
abbeycode Jun 21, 2017
df360db
Updated Travis build to Xcode 8.3
abbeycode Jun 21, 2017
3753993
Added release notes for 2.8.1 (Issue #43)
abbeycode Jun 22, 2017
d102983
Rebased to multivolume branch
aonez Aug 18, 2017
61ac50b
Was at master, merging branch…
aonez Aug 19, 2017
b3b05ad
Implementing isVolume methods
aonez Aug 19, 2017
53311f2
isVolume method removed
aonez Aug 19, 2017
614f35f
Removing extra space
aonez Aug 19, 2017
0f89b94
Rebase to @abbeycode multivolume branch
Sep 29, 2017
e83d7af
Fillet multivolume methods
Sep 29, 2017
7043e0d
Using new error method
Sep 29, 2017
8364d76
Using NSMutableOrderedSet for sorted array
Sep 29, 2017
8f4992e
Fixed bad travis config
Sep 29, 2017
e5d4743
Added newline break
Sep 29, 2017
41a6107
Removed unnecessary parenthesis
Sep 29, 2017
8231542
Checking first part on archive init
Sep 29, 2017
d129c73
Merge pull request #38 from aonez/master
abbeycode Oct 2, 2017
8d70f06
Reduced public API surface area for multivolume archives
abbeycode Oct 2, 2017
26149d0
Hardened regex code checking in +firstVolumeURL:
abbeycode Oct 2, 2017
f23e2ed
Added test to validate zero-padding in firstVolumeURL calculation for…
abbeycode Oct 2, 2017
be7a251
Cleaned up firstVolumeURL Part01 generation
abbeycode Oct 2, 2017
41f7d88
Fixed some logging messages
abbeycode Oct 2, 2017
9657b7e
Renamed some variables
abbeycode Oct 2, 2017
7acfc7e
Synced upstream changes from 'v2.9' branch
abbeycode Oct 3, 2017
07091c1
Added missing activity tracing [CI skip]
abbeycode Oct 3, 2017
2bc269d
Added to changelog
abbeycode Oct 3, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 2.9

* Added support for `NSProgress` and `NSProgressReporting` in all extraction and iteration methods (Issue #34)
* Added enhanced support for multivolume archives (PRs #59, #38 - Thanks to [@aonez](https://github.com/aonez) for the idea and implementation!)
* Switched to Travis Build Stages instead of the unofficial Travis-After-All (Issue #42)
* Added detailed logging using new unified logging framework. See [the readme](README.md) for more details (Issue #35)
* Added localized details to returned `NSError` objects (Issue #45)
Expand Down
14 changes: 14 additions & 0 deletions Classes/URKArchive.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ extern NSString *URKErrorDomain;
*/
@property(nullable, readonly) NSNumber *compressedSize;

/**
* True if the file is one volume of a multi-part archive
*/
@property(readonly) BOOL hasMultipleVolumes;

/**
* Can be used for progress reporting, but it's not necessary. You can also use
* implicit progress reporting. If you don't use it, one will still be created,
Expand Down Expand Up @@ -281,6 +286,15 @@ extern NSString *URKErrorDomain;
*/
- (nullable NSArray<URKFileInfo*> *)listFileInfo:(NSError **)error;

/**
* Lists the URLs of volumes in a single- or multi-volume archive
*
* @param error Contains an NSError object when there was an error reading the archive
*
* @return Returns the list of URLs of all volumes of the archive
*/
- (nullable NSArray<NSURL*> *)listVolumeURLs:(NSError **)error;

/**
* Writes all files in the archive to the given path. Supports NSProgress for progress reporting, which also
* allows cancellation in the middle of extraction. Use the progress property (as explained in the README) to
Expand Down
127 changes: 126 additions & 1 deletion Classes/URKArchive.mm
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,13 @@ - (instancetype)initWithFile:(NSURL *)fileURL password:(NSString*)password error
if (error) {
*error = nil;
}


NSURL *firstVolumeURL = [URKArchive firstVolumeURL:fileURL];
if (firstVolumeURL && ![firstVolumeURL.absoluteString isEqualToString:fileURL.absoluteString]) {
URKLogDebug("Overriding fileURL with first volume URL: %{public}@", firstVolumeURL);
fileURL = firstVolumeURL;
}

URKLogDebug("Initializing private fields");

NSError *bookmarkError = nil;
Expand Down Expand Up @@ -253,6 +259,21 @@ - (NSNumber *)compressedSize
return [NSNumber numberWithUnsignedLongLong:attributes.fileSize];
}

- (BOOL)hasMultipleVolumes
{
URKCreateActivity("Check If Multi-Volume Archive");

NSError *listError = nil;
NSArray<NSURL*> *volumeURLs = [self listVolumeURLs:&listError];

if (!volumeURLs) {
URKLogError("Error getting file volumes list: %{public}@", listError);
return false;
}

return volumeURLs.count > 1;
}



#pragma mark - Zip file detection
Expand Down Expand Up @@ -380,6 +401,33 @@ + (BOOL)urlIsARAR:(NSURL *)fileURL
return [NSArray arrayWithArray:fileInfos];
}

- (nullable NSArray<NSURL*> *)listVolumeURLs:(NSError **)error
{
URKCreateActivity("Listing Volume URLs");

NSArray<URKFileInfo*> *listFileInfo = [self listFileInfo:error];

if (listFileInfo == nil) {
return nil;
}

NSMutableSet<NSURL*> *volumeURLs = [[NSMutableSet alloc] init];

for (URKFileInfo* info in listFileInfo) {
NSURL *archiveURL = [NSURL fileURLWithPath:info.archiveName];

if (archiveURL) {
[volumeURLs addObject:archiveURL];
}
}

SEL sortBySelector = @selector(path);
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:NSStringFromSelector(sortBySelector) ascending:YES];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

finally you used the sort descriptor 😄

NSArray<NSURL*> *sortedVolumes = [volumeURLs sortedArrayUsingDescriptors:@[sortDescriptor]];

return sortedVolumes;
}

- (BOOL)extractFilesTo:(NSString *)filePath
overwrite:(BOOL)overwrite
error:(NSError **)error
Expand Down Expand Up @@ -1262,6 +1310,8 @@ - (BOOL)headerContainsErrors:(NSError **)error

- (NSProgress *)beginProgressOperation:(NSUInteger)totalUnitCount
{
URKCreateActivity("-beginProgressOperation:");

NSProgress *progress;
progress = self.progress;
if (!progress) {
Expand All @@ -1279,4 +1329,79 @@ - (NSProgress *)beginProgressOperation:(NSUInteger)totalUnitCount
return progress;
}

+ (NSURL *)firstVolumeURL:(NSURL *)volumeURL {
URKCreateActivity("+firstVolumeURL:");

URKLogDebug("Checking if the file is part of a multi-volume archive...");

if (!volumeURL) {
URKLogError("+firstVolumeURL: nil volumeURL passed")
}

NSString *volumePath = volumeURL.path;

NSError *regexError = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"(.part)([0-9]+)(.rar)$"
options:NSRegularExpressionCaseInsensitive
error:&regexError];
if (!regex) {
URKLogError("Error constructing filename regex")
return nil;
}

NSString *firstVolumePath = nil;

// Check if it's following the current convention, like "Archive.part03.rar"
NSTextCheckingResult *match = [regex firstMatchInString:volumePath options:0 range:NSMakeRange(0, volumePath.length)];
if (match) {
URKLogDebug("The file is part of a multi-volume archive");

NSRange numberRange = [match rangeAtIndex:2];
NSString * partOne = [[@"" stringByPaddingToLength:numberRange.length - 1
withString:@"0"
startingAtIndex:0]
stringByAppendingString:@"1"];

NSString * regexTemplate = [NSString stringWithFormat:@"$1%@$3", partOne];
firstVolumePath = [regex stringByReplacingMatchesInString:volumePath
options:0
range:NSMakeRange(0, volumePath.length)
withTemplate:regexTemplate];
}

// It still might be a multivolume archive. Check for the legacy naming convention, like "Archive.r03"
else {
// After rXX, rar uses r-z and symbols like {}|~... so accepting anything but a number
NSError *legacyRegexError = nil;
regex = [NSRegularExpression regularExpressionWithPattern:@"(\\.[^0-9])([0-9]+)$"
options:NSRegularExpressionCaseInsensitive
error:&legacyRegexError];

if (!regex) {
URKLogError("Error constructing legacy filename regex")
return nil;
}

match = [regex firstMatchInString:volumePath options:0 range:NSMakeRange(0, volumePath.length)];
if (match) {
URKLogDebug("The archive is part of a legacy volume");
firstVolumePath = [[volumePath stringByDeletingPathExtension] stringByAppendingPathExtension:@"rar"];
}
}

// If it's a volume of either naming convention, use it
if (firstVolumePath) {
if ([[NSFileManager defaultManager] fileExistsAtPath:firstVolumePath]) {
URKLogDebug("First volume part %{public}@ found. Using as the main archive", firstVolumePath);
return [NSURL fileURLWithPath:firstVolumePath];
}
else {
URKLogInfo("First volume part not found: %{public}@. Skipping first volume selection", firstVolumePath);
return nil;
}
}

return volumeURL;
}

@end
109 changes: 109 additions & 0 deletions Tests/FirstVolumeTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// FirstVolumeTests.m
// UnrarKit
//
// Created by Dov Frankel on 2/9/17.
//
//

#import "URKArchiveTestCase.h"

@interface FirstVolumeTests : URKArchiveTestCase @end

@interface URKArchive (Tests)

// It's a private class method
+ (NSURL *)firstVolumeURL:(NSURL *)volumeURL;

@end

@implementation FirstVolumeTests

- (void)testSingleVolume {
NSURL *onlyVolumeArchiveURL = self.testFileURLs[@"Test Archive.rar"];
NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:onlyVolumeArchiveURL];

XCTAssertNotNil(returnedFirstVolumeURL, @"No URL returned");
XCTAssertEqualObjects(returnedFirstVolumeURL, onlyVolumeArchiveURL, @"URL changed even though it's a single volume archive");
}


#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseFirstVolume {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveWithName:@"FirstVolumeTests-testMultipleVolume_UseFirstVolume.rar"];
NSURL *firstVolumeURL = volumeURLs.firstObject;
NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:firstVolumeURL];

XCTAssertNotNil(returnedFirstVolumeURL, @"No URL returned");
XCTAssertEqualObjects(returnedFirstVolumeURL, firstVolumeURL, @"URL changed even though it was initialized with the first volume");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseMiddleVolume {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveWithName:@"ListVolumesTests-testMultipleVolume_UseFirstVolume.rar"];
NSURL *firstVolumeURL = volumeURLs.firstObject;
NSURL *thirdVolumeURL = volumeURLs[2];

NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:thirdVolumeURL];

XCTAssertNotNil(returnedFirstVolumeURL, @"No URL returned");
XCTAssertEqualObjects(returnedFirstVolumeURL.absoluteString, firstVolumeURL.absoluteString, @"Incorrect URL returned as first volume");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseMiddleVolume_OneHundredParts {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveWithName:@"ListVolumesTests-testMultipleVolume_UseFirstVolume.rar" fileSize:2500000];

NSURL *firstVolumeURL = volumeURLs.firstObject;
NSURL *hundredthVolumeURL = volumeURLs[100];

NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:hundredthVolumeURL];

XCTAssertNotNil(returnedFirstVolumeURL, @"No URL returned");
XCTAssertEqualObjects(returnedFirstVolumeURL.absoluteString, firstVolumeURL.absoluteString, @"Incorrect URL returned as first volume");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseFirstVolume_OldNamingScheme {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveOldSchemeWithName:@"FirstVolumeTests-testMultipleVolume_UseFirstVolume_OldNamingScheme.rar"];
NSURL *firstVolumeURL = volumeURLs.firstObject;
NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:firstVolumeURL];

XCTAssertNotNil(returnedFirstVolumeURL, @"No URL returned");
XCTAssertEqualObjects(returnedFirstVolumeURL, firstVolumeURL, @"URL changed even though it was initialized with the first volume");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseMiddleVolume_OldNamingScheme {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveOldSchemeWithName:@"FirstVolumeTests-testMultipleVolume_UseMiddleVolume_OldNamingScheme.rar"];
NSURL *firstVolumeURL = volumeURLs.firstObject;
NSURL *thirdVolumeURL = volumeURLs[2];

NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:thirdVolumeURL];

XCTAssertNotNil(returnedFirstVolumeURL, @"No URL returned");
XCTAssertEqualObjects(returnedFirstVolumeURL.absoluteString, firstVolumeURL.absoluteString, @"Incorrect URL returned as first volume");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_FirstVolumeMissing {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveWithName:@"ListVolumesTests-testMultipleVolume_FirstVolumeMissing.rar"];

NSError *deleteError = nil;
[[NSFileManager defaultManager] removeItemAtURL:volumeURLs.firstObject
error:&deleteError];
XCTAssertNil(deleteError, @"Error deleting first volume of archive");

NSURL *firstVolumeURL = volumeURLs.firstObject;
NSURL *returnedFirstVolumeURL = [URKArchive firstVolumeURL:firstVolumeURL];

XCTAssertNil(returnedFirstVolumeURL, @"First volume URL returned when it does not exist");
}
#endif

@end
73 changes: 73 additions & 0 deletions Tests/HasMultipleVolumesTests.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// HasMultipleVolumesTests.m
// UnrarKit
//
// Created by Dov Frankel on 2/9/17.
//
//

#import "URKArchiveTestCase.h"

@interface HasMultipleVolumesTests : URKArchiveTestCase

@end

@implementation HasMultipleVolumesTests

- (void)testSingleVolume {
NSURL *testArchiveURL = self.testFileURLs[@"Test Archive.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:testArchiveURL error:nil];

BOOL hasMultipleParts = archive.hasMultipleVolumes;

XCTAssertFalse(hasMultipleParts, @"Single-volume archive reported to have multiple parts");
}

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseFirstVolume {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveWithName:@"HasMultipleVolumesTests-testMultipleVolume_UseFirstVolume.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:volumeURLs.firstObject error:nil];

BOOL hasMultipleParts = archive.hasMultipleVolumes;
XCTAssertTrue(hasMultipleParts, @"Multi-volume archive's first part not reported to have multiple volumes");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseMiddleVolume {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveWithName:@"HasMultipleVolumesTests-testMultipleVolume_UseMiddleVolume.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:volumeURLs[2] error:nil];

BOOL hasMultipleParts = archive.hasMultipleVolumes;
XCTAssertTrue(hasMultipleParts, @"Multi-volume archive's middle part not reported to have multiple volumes");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseFirstVolume_OldNamingScheme {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveOldSchemeWithName:@"HasMultipleVolumesTests-testMultipleVolume_UseFirstVolume_OldNamingScheme.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:volumeURLs.firstObject error:nil];

BOOL hasMultipleParts = archive.hasMultipleVolumes;
XCTAssertTrue(hasMultipleParts, @"Multi-volume archive's first part not reported to have multiple volumes");
}
#endif

#if !TARGET_OS_IPHONE
- (void)testMultipleVolume_UseMiddleVolume_OldNamingScheme {
NSArray<NSURL*> *volumeURLs = [self multiPartArchiveOldSchemeWithName:@"HasMultipleVolumesTests-testMultipleVolume_UseMiddleVolume_OldNamingScheme.rar"];
URKArchive *archive = [[URKArchive alloc] initWithURL:volumeURLs[2] error:nil];

BOOL hasMultipleParts = archive.hasMultipleVolumes;
XCTAssertTrue(hasMultipleParts, @"Multi-volume archive's middle part not reported to have multiple volumes");
}
#endif

- (void)testInvalidArchive {
URKArchive *archive = [[URKArchive alloc] initWithURL:self.testFileURLs[@"Test File A.txt"] error:nil];

BOOL hasMultipleParts = archive.hasMultipleVolumes;
XCTAssertFalse(hasMultipleParts, @"Invalid archive reported to have multiple volumes");
}

@end
Loading