Code refactoring and other changes

Moved extraction code out of the App delegate into its own class.
Added window for setting preferences (doesn't do anything atm except hide dock icon, experiment)
Added Readme
This commit is contained in:
Robert McGovern 2021-05-31 13:07:27 +01:00
parent 929688681b
commit 19766c83d9
11 changed files with 441 additions and 162 deletions

View File

@ -23,6 +23,10 @@
E2A3B843265F199A00A6C0A3 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = E2A3B842265F199A00A6C0A3 /* Cartfile */; };
E2A3B845265F1AA900A6C0A3 /* DockProgress.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2A3B844265F1AA800A6C0A3 /* DockProgress.framework */; };
E2A3B846265F1AA900A6C0A3 /* DockProgress.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = E2A3B844265F1AA800A6C0A3 /* DockProgress.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E2A3B848265F267900A6C0A3 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E2A3B847265F267900A6C0A3 /* UserNotifications.framework */; };
E2A3B8862663C1FB00A6C0A3 /* PreferencesWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = E2A3B8852663C1FB00A6C0A3 /* PreferencesWindow.xib */; };
E2A3B8892663C60200A6C0A3 /* TDNPreferencesWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A3B8882663C60200A6C0A3 /* TDNPreferencesWindowController.m */; };
E2A3B8902664DE8900A6C0A3 /* TDNUnarchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = E2A3B88F2664DE8900A6C0A3 /* TDNUnarchiver.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -177,6 +181,13 @@
E2A3B83C265EA8B800A6C0A3 /* UnzipKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UnzipKit.framework; path = Carthage/Build/Mac/UnzipKit.framework; sourceTree = "<group>"; };
E2A3B842265F199A00A6C0A3 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = "<group>"; };
E2A3B844265F1AA800A6C0A3 /* DockProgress.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DockProgress.framework; path = Carthage/Build/Mac/DockProgress.framework; sourceTree = "<group>"; };
E2A3B847265F267900A6C0A3 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };
E2A3B849266009B000A6C0A3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E2A3B8852663C1FB00A6C0A3 /* PreferencesWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = PreferencesWindow.xib; path = Resources/PreferencesWindow.xib; sourceTree = "<group>"; };
E2A3B8872663C60200A6C0A3 /* TDNPreferencesWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TDNPreferencesWindowController.h; sourceTree = "<group>"; };
E2A3B8882663C60200A6C0A3 /* TDNPreferencesWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TDNPreferencesWindowController.m; sourceTree = "<group>"; };
E2A3B88F2664DE8900A6C0A3 /* TDNUnarchiver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TDNUnarchiver.m; sourceTree = "<group>"; };
E2A3B8912664DEE300A6C0A3 /* TDNUnarchiver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TDNUnarchiver.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -190,6 +201,7 @@
E2A3B83D265EA8B900A6C0A3 /* UnrarKit.framework in Frameworks */,
D4A96E2110545E9A0091ECB4 /* Carbon.framework in Frameworks */,
E2A3B83F265EA8B900A6C0A3 /* UnzipKit.framework in Frameworks */,
E2A3B848265F267900A6C0A3 /* UserNotifications.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -201,6 +213,10 @@
children = (
256AC3D80F4B6AC300CF3369 /* QuietUnrarAppDelegate.h */,
256AC3D90F4B6AC300CF3369 /* QuietUnrarAppDelegate.m */,
E2A3B8872663C60200A6C0A3 /* TDNPreferencesWindowController.h */,
E2A3B8882663C60200A6C0A3 /* TDNPreferencesWindowController.m */,
E2A3B88F2664DE8900A6C0A3 /* TDNUnarchiver.m */,
E2A3B8912664DEE300A6C0A3 /* TDNUnarchiver.h */,
);
name = Classes;
sourceTree = "<group>";
@ -262,6 +278,8 @@
8D1107310486CEB800E47090 /* QuietUnrar-Info.plist */,
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
1DDD58140DA1D0A300B32029 /* MainMenu.xib */,
E2A3B849266009B000A6C0A3 /* README.md */,
E2A3B8852663C1FB00A6C0A3 /* PreferencesWindow.xib */,
);
name = Resources;
sourceTree = "<group>";
@ -269,6 +287,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
E2A3B847265F267900A6C0A3 /* UserNotifications.framework */,
E2A3B844265F1AA800A6C0A3 /* DockProgress.framework */,
E2A3B83B265EA8B800A6C0A3 /* UnrarKit.framework */,
E2A3B83C265EA8B800A6C0A3 /* UnzipKit.framework */,
@ -466,6 +485,7 @@
buildActionMask = 2147483647;
files = (
D4A49692105435C100BE38AE /* MainMenu.xib in Resources */,
E2A3B8862663C1FB00A6C0A3 /* PreferencesWindow.xib in Resources */,
D4A49691105435BE00BE38AE /* InfoPlist.strings in Resources */,
E2A3B843265F199A00A6C0A3 /* Cartfile in Resources */,
D488BE5510B05F3800B3451C /* PasswordView.xib in Resources */,
@ -515,6 +535,8 @@
buildActionMask = 2147483647;
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
E2A3B8892663C60200A6C0A3 /* TDNPreferencesWindowController.m in Sources */,
E2A3B8902664DE8900A6C0A3 /* TDNUnarchiver.m in Sources */,
256AC3DA0F4B6AC300CF3369 /* QuietUnrarAppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -7,6 +7,7 @@
//
#import <Cocoa/Cocoa.h>
#import "TDNPreferencesWindowController.h"
enum
{
@ -33,8 +34,9 @@ enum
@property (weak) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSView *passwordView;
@property (weak) IBOutlet NSSecureTextField * passwordField;
@property TDNPreferencesWindowController * preferencesWindowController;
- (BOOL) extractRarWith:(NSString *) filename;
// UI Based methods
- (BOOL) shouldFileBeReplaced:(NSString *) filename;
- (void) alertUserOfMissing:(const char *) volume;
- (NSString *) requestArchivePassword;

View File

@ -6,97 +6,26 @@
// Copyright 2009 Tarasis. All rights reserved.
//
#import <wchar.h>
#import <Carbon/Carbon.h>
#import <UnrarKit/UnrarKit.h>
#import <Cocoa/Cocoa.h>
#import <UserNotifications/UserNotifications.h>
#import "QuietUnrarAppDelegate.h"
#import "libunrar/dll.hpp"
#import "libunrar/rardefs.hpp"
#import "TDNUnarchiver.h"
#pragma mark Callbacks
// Declartions that are not to be part of the public interface.
// The two methods are for callbacks passed to the RAR library
QuietUnrarAppDelegate * quietUnrar;
@interface QuietUnrarAppDelegate ()
int changeVolume(char * volumeName, int mode);
int callbackFunction(UINT message, LPARAM userData, LPARAM parameterOne, LPARAM parameterTwo);
@property TDNUnarchiver * unarchiver;
// Called everytime a new volume (part) of the RAR is needed.
// mode will either be
// RAR_VOL_NOTIFY that just notifies us that the volume has changed
// RAR_VOL_ASK indicates that a volume is needed and the library is asking for it.
//
// in both case volumeName is that name of the volume (for instance .r00)
//
// Note in the event of a volume being missing, there is no way to indicate to the
// library that you have found it. You would need to block the copy, let the user find the
// volume, copy it to where the other volumes are and unblock to let the library
// continue processing
int changeVolume(char * volumeName, int mode) {
if (mode == RAR_VOL_ASK)
[(QuietUnrarAppDelegate *) quietUnrar alertUserOfMissing:volumeName];
return 0;
}
// Multipurpose callback function that is called un changing a volume, when data is being processed
// and when a password is required. This is indicated by the message parameter
//
// UCM_CHANGEVOLUME sent when changing volumes
// UCM_PROCESSDATA sent as each file in the archive is being extracted in chunks, useful for progress bars
// UCM_NEEDPASSWORD sent when the library discovers a password is needed.
//
// The userData param is a pointer to something we supplied when the callback was registered. In my
// case I am passing in the pointer to the archive data so that the requestArchivePassword method
// can supply the password to the RAR library via RARSetPassword
//
// parameterOne & parameterTwo have different meanings depending on what message is passed.
int callbackFunction(UINT message, LPARAM userData, LPARAM parameterOne, LPARAM parameterTwo) {
if (message == UCM_NEEDPASSWORDW) {
NSString * password = [(QuietUnrarAppDelegate *) quietUnrar requestArchivePassword];
if (password) {
wchar_t const *passwordAsWChar = (const wchar_t *)[password cStringUsingEncoding:NSUTF32LittleEndianStringEncoding];
wcscpy((wchar_t *) parameterOne, passwordAsWChar);
return 1;
} else {
return -1;
}
}
return 0;
/*
You need to copy the password string to buffer with P1 address
and P2 size.
This password string must use little endian Unicode encoding in case
UCM_NEEDPASSWORDW message. Namely, it must be wchar_t and not UTF-8.
case UCM_NEEDPASSWORDW:
{
wchar_t *eol;
printf("\nPassword required: ");
// fgetws may fail to read non-English characters from stdin
// in some compilers. In this case use something more appropriate
// for Unicode input.
fgetws((wchar_t *)P1,(int)P2,stdin);
eol=wcspbrk((wchar_t *)P1,L"\r\n");
if (eol!=NULL)
*eol=0;
}
return(1);
*/
}
@end
#pragma mark
@implementation QuietUnrarAppDelegate
@synthesize window, passwordView, passwordField;
@synthesize window, passwordView, passwordField, preferencesWindowController, unarchiver;
- (void) applicationWillFinishLaunching:(NSNotification *)notification {
NSLog(@"applicationWillFinishLaunching");
// The following is used to determine is the left or right shift keys were depressed
// as the application was launched. Could be used to display a gui on Application start.
KeyMap map;
@ -106,8 +35,15 @@ int callbackFunction(UINT message, LPARAM userData, LPARAM parameterOne, LPARAM
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSLog(@"applicationDidFinishLaunching");
// Having extracted our file or not, quit. Though should not if error is displayed.
[[NSApplication sharedApplication] terminate:self];
//[[NSApplication sharedApplication] terminate:self];
[self requestUserPermissionForNotifications];
preferencesWindowController = [[TDNPreferencesWindowController alloc] init];
[preferencesWindowController showWindow:nil];
}
// Call one at a time for each file selected when app is run
@ -115,98 +51,39 @@ int callbackFunction(UINT message, LPARAM userData, LPARAM parameterOne, LPARAM
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
//NSLog(@"openFile: %@", filename);
[self extractRarWith:filename];
//[self extractRarWith:filename];
// [self extractRarUsingUnrarKitWithFilename:filename];
// Always return YES even if there is an error to avoid dialog indicating unable to
// handle files of type RAR if the archive is corrupt or part of it is missing
return YES;
}
- (void)application:(NSApplication *)theApplication openFiles:(NSArray *) arrayOfFilenames {
// NSLog(@"openFiles: %@", arrayOfFilenames);
NSLog(@"openFiles: %@", arrayOfFilenames);
unarchiver = [[TDNUnarchiver alloc] init];
unarchiver.quietUnrar = self;
[self requestUserPermissionForNotifications];
for (NSString * filename in arrayOfFilenames) {
BOOL extracted = [self extractRarWith:filename];
BOOL extracted = [unarchiver extractArchiveWithFilename:filename];
if (extracted) {
// post notification based on user preference
if (true && true) { // if show notification + permission granted ...
[self postNotificationUncompressedFile:filename];
}
}
}
[[NSApplication sharedApplication] terminate:self];
}
#pragma mark "Main"
- (BOOL) extractRarWith:(NSString *) filename {
quietUnrar = (__bridge QuietUnrarAppDelegate *)((__bridge void *) self);
char commentBuffer[BUF_LEN];
BOOL extractionSuccessful = YES;
struct RARHeaderData headerData;
NSString * lastExtractedFilename = @"";
NSString * currentFilename;
//Determine the folder we should extract the archive to. This by default
//is the <folderContainingTheArchive>/<archiveNameWithPathExtension>
NSString * folderToExtractTo = [filename stringByDeletingPathExtension];
// Open the Archive for extraction, we set the open result to 3 so we can see it has changed
char * filenameCString = (char *)[filename cStringUsingEncoding:NSISOLatin1StringEncoding];
struct RAROpenArchiveData arcData = { filenameCString, RAR_OM_EXTRACT, 3, &commentBuffer[0], BUF_LEN, 0, 0};
HANDLE archive = RAROpenArchive(&arcData);
//NSLog(@"Opening Archive %s with result %d", filenameCString, arcData.OpenResult);
// set call backs for if password needed or need to change volume
RARSetChangeVolProc(archive, &changeVolume);
RARSetCallback(archive, &callbackFunction, (LPARAM)archive);
while (RARReadHeader(archive, &headerData) == ERAR_SUCCESS) {
//NSLog(@"Attempting to extract %s to %@", headerData.FileName, folderToExtractTo);
int processResult = 0;
BOOL extractFile = YES;
BOOL isDir;
currentFilename = [NSString stringWithCString:(const char *) headerData.FileName encoding:NSISOLatin1StringEncoding];
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[NSString stringWithFormat:@"%@/%s", folderToExtractTo, headerData.FileName] isDirectory:&isDir] ) {
// If we have already processed the file once and the user has told us to skip
// don't ask them again, even though we've changed volumes. Otherwise
// ask the user what to do.
if ([lastExtractedFilename isEqualToString:currentFilename] ||
isDir ||
![self shouldFileBeReplaced:currentFilename]) {
extractFile = NO;
}
}
// NSLog(@"Last filename %@, currentFilename %@, equality %d", lastExtractedFilename, currentFilename, [lastExtractedFilename isEqualToString:currentFilename]);
if (extractFile) {
//NSLog(@"...Extracting");
processResult = RARProcessFile(archive, RAR_EXTRACT, (char *) [folderToExtractTo cStringUsingEncoding:NSISOLatin1StringEncoding], NULL);
} else {
//NSLog(@"...Skipping as already exists");
processResult = RARProcessFile(archive, RAR_SKIP, NULL, NULL);
// Curious behavior by the lib, you have SKIP a file number of times (4 in my test example) before
// it is skipped. However if you extract it is only processed once.
}
if (processResult != 0) {
NSLog(@"Error: Process Result was %d", processResult);
extractionSuccessful = NO;
break;
// DISPLAY ERROR DIALOG, ALERT THE USER
}
lastExtractedFilename = currentFilename;
}
RARCloseArchive(archive);
//NSLog(@"Closing Archive %s with result %d", filenameCString, closeResult);
return extractionSuccessful;
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
#pragma mark UI Methods
// Presents a dialog to the user allowing them to Skip a file or overwrite an existing version
// returns YES or NO
- (BOOL) shouldFileBeReplaced:(NSString *) filename {
@ -266,8 +143,25 @@ int callbackFunction(UINT message, LPARAM userData, LPARAM parameterOne, LPARAM
password = [passwordField stringValue];
}
return password;
}
#pragma mark "Notifications"
- (void) requestUserPermissionForNotifications {
UNUserNotificationCenter * center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert + UNAuthorizationOptionSound)
completionHandler:^(BOOL granted, NSError * _Nullable error) {
// Enable or disable features based on authorization.
if (granted) {
// set some flag, that would be used to see if notifications should be posted
NSLog(@"Notification Permission Granted");
}
}];
}
- (void) postNotificationUncompressedFile:(NSString *) filename {
// add details of notification
}
@end

View File

@ -5,3 +5,5 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif
#import "QuietUnrarAppDelegate.h"

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# QuietUnrar (or QuietUnarchiver or QuietDecompressor)
Small app for quietly unarchiving rar, zip and lzma files. No windows on the screen unless there is an issue (bad CRC, requires password, missing volume)
Optionally show progress on Dock Icon or Status Bar (for larger files)
Optionally show a notification on completion (with action button to open finder in that folder)
Original was written in 2009 as a little thing for me, and now its getting some TLC and updates. Mostly to play with Objective-C again.
## TO DO
* Store preferences in User Defaults (or mac equvalent)
* ✅ Move code handling un archiving into seperate class
* add model code for preferences
* add support for 7zip https://github.com/OlehKulykov/PLzmaSDK
* Investigate metal warning, something to ignore?
### Metal Warning
2021-05-30 15:17:27.995689+0100 QuietUnrar[91513:2457432] Metal API Validation Enabled
2021-05-30 15:17:28.105839+0100 QuietUnrar[91513:2457432] MTLIOAccelDevice bad MetalPluginClassName property (null)
2021-05-30 15:17:28.124992+0100 QuietUnrar[91513:2457432] +[MTLIOAccelDevice registerDevices]: Zero Metal services found
A new mac project doesn't report these warnings.

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="TDNPreferencesWindowController">
<connections>
<outlet property="window" destination="QvC-M9-y7g" id="kY2-3a-gXb"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1366" height="743"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="V7e-wf-zha">
<rect key="frame" x="154" y="118" width="172" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Hide / Show Dock Icon" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="us0-hR-9qi">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="showHideButtonPressed:" target="-2" id="Rrk-Jt-cra"/>
</connections>
</button>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="-2" id="Sl0-lc-NGz"/>
</connections>
<point key="canvasLocation" x="112" y="110"/>
</window>
</objects>
</document>

View File

@ -0,0 +1,16 @@
//
// TDNPreferencesWindowController.h
// QuietUnrar
//
// Created by Robert McGovern on 2021/05/30.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface TDNPreferencesWindowController : NSWindowController
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,42 @@
//
// TDNPreferencesWindowController.m
// QuietUnrar
//
// Created by Robert McGovern on 2021/05/30.
//
#import "TDNPreferencesWindowController.h"
@interface TDNPreferencesWindowController ()
@end
@implementation TDNPreferencesWindowController
- (id) init {
return [super initWithWindowNibName:@"PreferencesWindow"];
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
BOOL showingDock = TRUE;
- (IBAction)showHideButtonPressed:(id)sender {
if (showingDock) {
showingDock = FALSE;
NSLog(@"Setting Policy to Accesosry");
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
NSLog(@"%@", [[[NSApplication sharedApplication]delegate] description]);
} else {
showingDock = TRUE;
NSLog(@"Setting Policy to Regular");
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}
}
@end

20
TDNUnarchiver.h Normal file
View File

@ -0,0 +1,20 @@
//
// TDNUnarchiver.h
// QuietUnrar
//
// Created by Robert McGovern on 2021/05/31.
//
#ifndef Unarchiver_h
#define Unarchiver_h
@interface TDNUnarchiver : NSObject
@property QuietUnrarAppDelegate * quietUnrar;
- (BOOL) extractArchiveWithFilename:(NSString *) filename;
@end
#endif /* Unarchiver_h */

212
TDNUnarchiver.m Normal file
View File

@ -0,0 +1,212 @@
//
// TDNUnarchiver.m
// QuietUnrar
//
// Created by Robert McGovern on 2021/05/31.
//
#import <Foundation/Foundation.h>
#import "TDNUnarchiver.h"
#import "QuietUnrarAppDelegate.h"
#import <UnrarKit/UnrarKit.h>
#import "libunrar/dll.hpp"
#import "libunrar/rardefs.hpp"
#import <wchar.h>
//@interface TDNUnarchiver ()
//
//@end
@implementation TDNUnarchiver
@synthesize quietUnrar;
static QuietUnrarAppDelegate * quietUnrarClassPtr;
#pragma mark - Callback Functions
// Called everytime a new volume (part) of the RAR is needed.
// mode will either be
// RAR_VOL_NOTIFY that just notifies us that the volume has changed
// RAR_VOL_ASK indicates that a volume is needed and the library is asking for it.
//
// in both case volumeName is that name of the volume (for instance .r00)
//
// Note in the event of a volume being missing, there is no way to indicate to the
// library that you have found it. You would need to block the copy, let the user find the
// volume, copy it to where the other volumes are and unblock to let the library
// continue processing
int CALLBACK changeVolume(char * volumeName, int mode) {
if (mode == RAR_VOL_ASK)
[quietUnrarClassPtr alertUserOfMissing:volumeName];
return 0;
}
// Multipurpose callback function that is called un changing a volume, when data is being processed
// and when a password is required. This is indicated by the message parameter
//
// UCM_CHANGEVOLUME sent when changing volumes
// UCM_PROCESSDATA sent as each file in the archive is being extracted in chunks, useful for progress bars
// UCM_NEEDPASSWORD sent when the library discovers a password is needed.
//
// The userData param is a pointer to something we supplied when the callback was registered. In my
// case I am passing in the pointer to the archive data so that the requestArchivePassword method
// can supply the password to the RAR library via RARSetPassword
//
// parameterOne & parameterTwo have different meanings depending on what message is passed.
int CALLBACK generalCallbackFunction(UINT message, LPARAM userData, LPARAM parameterOne, LPARAM parameterTwo) {
NSLog(@"into generalCallbackFunction");
if (message == UCM_NEEDPASSWORDW) {
NSLog(@"file requires a password ...");
NSString * password = [quietUnrarClassPtr requestArchivePassword];
if (password) {
wchar_t const *passwordAsWChar = (const wchar_t *)[password cStringUsingEncoding:NSUTF32LittleEndianStringEncoding];
wcscpy((wchar_t *) parameterOne, passwordAsWChar);
return 1;
} else {
return -1;
}
}
return 0;
/*
You need to copy the password string to buffer with P1 address
and P2 size.
This password string must use little endian Unicode encoding in case
UCM_NEEDPASSWORDW message. Namely, it must be wchar_t and not UTF-8.
case UCM_NEEDPASSWORDW:
{
wchar_t *eol;
printf("\nPassword required: ");
// fgetws may fail to read non-English characters from stdin
// in some compilers. In this case use something more appropriate
// for Unicode input.
fgetws((wchar_t *)P1,(int)P2,stdin);
eol=wcspbrk((wchar_t *)P1,L"\r\n");
if (eol!=NULL)
*eol=0;
}
return(1);
*/
}
#pragma mark Public Methods
- (BOOL) extractArchiveWithFilename:(NSString *) filename {
// Add code to distinguish filetypes and pass of to relevant unarchiver
return [self extractRARArchiveWithFilename:filename];
}
#pragma mark Extraction Methods
- (BOOL) extractRARArchiveWithFilename:(NSString *) filename {
quietUnrarClassPtr = quietUnrar;
char commentBuffer[BUF_LEN];
BOOL extractionSuccessful = YES;
struct RARHeaderData headerData;
NSString * lastExtractedFilename = @"";
NSString * currentFilename;
//Determine the folder we should extract the archive to. This by default
//is the <folderContainingTheArchive>/<archiveNameWithPathExtension>
NSString * folderToExtractTo = [filename stringByDeletingPathExtension];
// Open the Archive for extraction, we set the open result to 3 so we can see it has changed
char * filenameCString = (char *)[filename cStringUsingEncoding:NSISOLatin1StringEncoding];
struct RAROpenArchiveData arcData = { filenameCString, RAR_OM_EXTRACT, 3, &commentBuffer[0], BUF_LEN, 0, 0};
HANDLE archive = RAROpenArchive(&arcData);
//NSLog(@"Opening Archive %s with result %d", filenameCString, arcData.OpenResult);
// set call backs for if password needed or need to change volume
RARSetChangeVolProc(archive, &changeVolume);
RARSetCallback(archive, &generalCallbackFunction, (LPARAM)archive);
while (RARReadHeader(archive, &headerData) == ERAR_SUCCESS) {
//NSLog(@"Attempting to extract %s to %@", headerData.FileName, folderToExtractTo);
int processResult = 0;
BOOL extractFile = YES;
BOOL isDir;
currentFilename = [NSString stringWithCString:(const char *) headerData.FileName encoding:NSISOLatin1StringEncoding];
NSFileManager * fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:[NSString stringWithFormat:@"%@/%s", folderToExtractTo, headerData.FileName] isDirectory:&isDir] ) {
// If we have already processed the file once and the user has told us to skip
// don't ask them again, even though we've changed volumes. Otherwise
// ask the user what to do.
if ([lastExtractedFilename isEqualToString:currentFilename] ||
isDir ||
![quietUnrar shouldFileBeReplaced:currentFilename]) {
extractFile = NO;
}
}
// NSLog(@"Last filename %@, currentFilename %@, equality %d", lastExtractedFilename, currentFilename, [lastExtractedFilename isEqualToString:currentFilename]);
if (extractFile) {
//NSLog(@"...Extracting");
processResult = RARProcessFile(archive, RAR_EXTRACT, (char *) [folderToExtractTo cStringUsingEncoding:NSISOLatin1StringEncoding], NULL);
} else {
//NSLog(@"...Skipping as already exists");
processResult = RARProcessFile(archive, RAR_SKIP, NULL, NULL);
// Curious behavior by the lib, you have SKIP a file number of times (4 in my test example) before
// it is skipped. However if you extract it is only processed once.
}
if (processResult != 0) {
NSLog(@"Error: Process Result was %d", processResult);
extractionSuccessful = NO;
break;
// DISPLAY ERROR DIALOG, ALERT THE USER
}
lastExtractedFilename = currentFilename;
}
RARCloseArchive(archive);
//NSLog(@"Closing Archive %s with result %d", filenameCString, closeResult);
return extractionSuccessful;
}
- (BOOL) extractRarUsingUnrarKitWithFilename:(NSString *) filename {
NSError *archiveError = nil;
NSError *error = nil;
URKArchive *archive = [[URKArchive alloc] initWithPath:filename error:&archiveError];
if (archiveError != nil) {
NSLog(@"Error with archive: %@", archiveError.localizedDescription);
return false;
}
//NSArray<URKFileInfo*> *fileInfosInArchive = [archive listFileInfo:&error];
if (archive.isPasswordProtected) {
NSString *givenPassword = [quietUnrar requestArchivePassword];
archive.password = givenPassword;
}
NSString * folderToExtractTo = [filename stringByDeletingPathExtension];
BOOL extractFilesSuccessful = [archive extractFilesTo: folderToExtractTo
overwrite:NO
error:&error];
if (error != nil) {
NSLog(@"Error extracting archive: %@", error.localizedDescription);
}
return extractFilesSuccessful;
}
@end

8
main.m
View File

@ -8,7 +8,9 @@
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}