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); }