From 19766c83d9931186e1ef4bf30a7a2574e247eaba Mon Sep 17 00:00:00 2001 From: Robert McGovern Date: Mon, 31 May 2021 13:07:27 +0100 Subject: [PATCH] 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 --- QuietUnrar.xcodeproj/project.pbxproj | 22 +++ QuietUnrarAppDelegate.h | 4 +- QuietUnrarAppDelegate.m | 210 +++++++------------------- QuietUnrar_Prefix.pch | 2 + README.md | 23 +++ Resources/PreferencesWindow.xib | 44 ++++++ TDNPreferencesWindowController.h | 16 ++ TDNPreferencesWindowController.m | 42 ++++++ TDNUnarchiver.h | 20 +++ TDNUnarchiver.m | 212 +++++++++++++++++++++++++++ main.m | 8 +- 11 files changed, 441 insertions(+), 162 deletions(-) create mode 100644 README.md create mode 100644 Resources/PreferencesWindow.xib create mode 100644 TDNPreferencesWindowController.h create mode 100644 TDNPreferencesWindowController.m create mode 100644 TDNUnarchiver.h create mode 100644 TDNUnarchiver.m diff --git a/QuietUnrar.xcodeproj/project.pbxproj b/QuietUnrar.xcodeproj/project.pbxproj index daec819..21900ad 100644 --- a/QuietUnrar.xcodeproj/project.pbxproj +++ b/QuietUnrar.xcodeproj/project.pbxproj @@ -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 = ""; }; E2A3B842265F199A00A6C0A3 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = ""; }; E2A3B844265F1AA800A6C0A3 /* DockProgress.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DockProgress.framework; path = Carthage/Build/Mac/DockProgress.framework; sourceTree = ""; }; + 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 = ""; }; + E2A3B8852663C1FB00A6C0A3 /* PreferencesWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = PreferencesWindow.xib; path = Resources/PreferencesWindow.xib; sourceTree = ""; }; + E2A3B8872663C60200A6C0A3 /* TDNPreferencesWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TDNPreferencesWindowController.h; sourceTree = ""; }; + E2A3B8882663C60200A6C0A3 /* TDNPreferencesWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TDNPreferencesWindowController.m; sourceTree = ""; }; + E2A3B88F2664DE8900A6C0A3 /* TDNUnarchiver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TDNUnarchiver.m; sourceTree = ""; }; + E2A3B8912664DEE300A6C0A3 /* TDNUnarchiver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TDNUnarchiver.h; sourceTree = ""; }; /* 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 = ""; @@ -262,6 +278,8 @@ 8D1107310486CEB800E47090 /* QuietUnrar-Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, + E2A3B849266009B000A6C0A3 /* README.md */, + E2A3B8852663C1FB00A6C0A3 /* PreferencesWindow.xib */, ); name = Resources; sourceTree = ""; @@ -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; diff --git a/QuietUnrarAppDelegate.h b/QuietUnrarAppDelegate.h index a931470..2a9f9f1 100644 --- a/QuietUnrarAppDelegate.h +++ b/QuietUnrarAppDelegate.h @@ -7,6 +7,7 @@ // #import +#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; diff --git a/QuietUnrarAppDelegate.m b/QuietUnrarAppDelegate.m index 573d918..6303547 100644 --- a/QuietUnrarAppDelegate.m +++ b/QuietUnrarAppDelegate.m @@ -6,97 +6,26 @@ // Copyright 2009 Tarasis. All rights reserved. // -#import #import -#import +#import +#import + #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 / - 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 diff --git a/QuietUnrar_Prefix.pch b/QuietUnrar_Prefix.pch index d212878..22d9846 100644 --- a/QuietUnrar_Prefix.pch +++ b/QuietUnrar_Prefix.pch @@ -5,3 +5,5 @@ #ifdef __OBJC__ #import #endif + +#import "QuietUnrarAppDelegate.h" diff --git a/README.md b/README.md new file mode 100644 index 0000000..df1d147 --- /dev/null +++ b/README.md @@ -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. diff --git a/Resources/PreferencesWindow.xib b/Resources/PreferencesWindow.xib new file mode 100644 index 0000000..0c4ffe5 --- /dev/null +++ b/Resources/PreferencesWindow.xib @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TDNPreferencesWindowController.h b/TDNPreferencesWindowController.h new file mode 100644 index 0000000..cc2241a --- /dev/null +++ b/TDNPreferencesWindowController.h @@ -0,0 +1,16 @@ +// +// TDNPreferencesWindowController.h +// QuietUnrar +// +// Created by Robert McGovern on 2021/05/30. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface TDNPreferencesWindowController : NSWindowController + +@end + +NS_ASSUME_NONNULL_END diff --git a/TDNPreferencesWindowController.m b/TDNPreferencesWindowController.m new file mode 100644 index 0000000..428287b --- /dev/null +++ b/TDNPreferencesWindowController.m @@ -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 diff --git a/TDNUnarchiver.h b/TDNUnarchiver.h new file mode 100644 index 0000000..ffbe58a --- /dev/null +++ b/TDNUnarchiver.h @@ -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 */ diff --git a/TDNUnarchiver.m b/TDNUnarchiver.m new file mode 100644 index 0000000..f08adec --- /dev/null +++ b/TDNUnarchiver.m @@ -0,0 +1,212 @@ +// +// TDNUnarchiver.m +// QuietUnrar +// +// Created by Robert McGovern on 2021/05/31. +// + +#import +#import "TDNUnarchiver.h" +#import "QuietUnrarAppDelegate.h" + +#import +#import "libunrar/dll.hpp" +#import "libunrar/rardefs.hpp" + +#import + +//@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 / + 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 *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 diff --git a/main.m b/main.m index 938eb8b..68db4a3 100644 --- a/main.m +++ b/main.m @@ -8,7 +8,9 @@ #import -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); }