2009-11-10 16:48:42 +01:00
//
// QuietUnrarAppDelegate . m
// QuietUnrar
//
// Created by Robert McGovern on 2009 / 09 / 06.
// Copyright 2009 Tarasis . All rights reserved .
//
2021-05-26 18:18:00 +02:00
# import < wchar . h >
2009-11-10 16:48:42 +01:00
# import < Carbon / Carbon . h >
2021-05-26 14:49:40 +02:00
# import < UnrarKit / UnrarKit . h >
2009-11-10 16:48:42 +01:00
# import "QuietUnrarAppDelegate.h"
# import "libunrar/dll.hpp"
2021-05-26 14:49:40 +02:00
# import "libunrar/rardefs.hpp"
2009-11-10 16:48:42 +01:00
2009-11-16 15:00:11 +01:00
# 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
2009-11-15 18:50:12 +01:00
QuietUnrarAppDelegate * quietUnrar ;
2009-11-15 15:43:54 +01:00
2009-11-15 00:18:58 +01:00
int changeVolume ( char * volumeName , int mode ) ;
int callbackFunction ( UINT message , LPARAM userData , LPARAM parameterOne , LPARAM parameterTwo ) ;
2009-11-16 15:00:11 +01:00
// 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 .
2021-05-24 11:24:32 +02:00
//
2009-11-16 15:00:11 +01:00
// 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
2021-05-24 11:24:32 +02:00
// library that you have found it . You would need to block the copy , let the user find the
2009-11-16 15:00:11 +01:00
// volume , copy it to where the other volumes are and unblock to let the library
// continue processing
2009-11-15 00:18:58 +01:00
int changeVolume ( char * volumeName , int mode ) {
2009-11-15 15:43:54 +01:00
if ( mode = = RAR_VOL _ASK )
[ ( QuietUnrarAppDelegate * ) quietUnrar alertUserOfMissing : volumeName ] ;
2021-05-26 14:49:40 +02:00
return 0 ;
2009-11-15 00:18:58 +01:00
}
2009-11-16 15:00:11 +01:00
// 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 .
2009-11-15 00:18:58 +01:00
int callbackFunction ( UINT message , LPARAM userData , LPARAM parameterOne , LPARAM parameterTwo ) {
2021-05-26 14:49:40 +02:00
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 ) ;
* /
2009-11-15 00:18:58 +01:00
}
2009-11-16 15:00:11 +01:00
# pragma mark
2009-11-10 16:48:42 +01:00
@ implementation QuietUnrarAppDelegate
2009-11-15 18:50:12 +01:00
@ synthesize window , passwordView , passwordField ;
2009-11-10 16:48:42 +01:00
2009-11-14 23:12:16 +01:00
- ( void ) applicationWillFinishLaunching : ( NSNotification * ) notification {
2009-11-16 15:00:11 +01:00
// 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 .
2009-11-10 16:48:42 +01:00
KeyMap map ;
GetKeys ( map ) ;
2009-11-16 15:00:11 +01:00
if ( KEYMAP_GET ( map , kVKC_Shift ) || KEYMAP_GET ( map , kVKC_rShift ) )
2021-05-24 11:24:32 +02:00
NSLog ( @ "Shift or Right Shift" ) ;
2009-11-10 16:48:42 +01:00
}
2009-11-14 23:12:16 +01:00
- ( void ) applicationDidFinishLaunching : ( NSNotification * ) aNotification {
2009-11-15 13:54:07 +01:00
// Having extracted our file or not , quit . Though should not if error is displayed .
[ [ NSApplication sharedApplication ] terminate : self ] ;
2009-11-10 16:48:42 +01:00
}
2009-11-16 15:00:11 +01:00
// Call one at a time for each file selected when app is run
2021-05-26 18:15:39 +02:00
// This is seemingly never called , even when only selecting a single file .
2009-11-10 16:48:42 +01:00
- ( BOOL ) application : ( NSApplication * ) theApplication openFile : ( NSString * ) filename {
2009-11-15 18:50:12 +01:00
// NSLog ( @ "openFile: %@" , filename ) ;
2021-05-24 11:24:32 +02:00
2009-11-15 15:43:54 +01:00
[ self extractRarWith : filename ] ;
2021-05-24 11:24:32 +02:00
2009-11-15 15:43:54 +01:00
// 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 ;
2009-11-10 16:48:42 +01:00
}
2009-11-16 22:11:00 +01:00
- ( void ) application : ( NSApplication * ) theApplication openFiles : ( NSArray * ) arrayOfFilenames {
// NSLog ( @ "openFiles: %@" , arrayOfFilenames ) ;
2021-05-24 11:24:32 +02:00
2009-11-16 22:11:00 +01:00
for ( NSString * filename in arrayOfFilenames ) {
2009-11-17 00:04:22 +01:00
BOOL extracted = [ self extractRarWith : filename ] ;
if ( extracted ) {
2021-05-26 18:15:39 +02:00
// post notification based on user preference
2009-11-17 00:04:22 +01:00
}
2009-11-16 22:11:00 +01:00
}
}
2021-05-24 11:24:32 +02:00
# pragma mark "Main"
2009-11-14 23:12:16 +01:00
- ( BOOL ) extractRarWith : ( NSString * ) filename {
2021-05-24 17:37:15 +02:00
quietUnrar = ( __bridge QuietUnrarAppDelegate * ) ( ( __bridge void * ) self ) ;
2009-11-14 23:12:16 +01:00
char commentBuffer [ BUF_LEN ] ;
BOOL extractionSuccessful = YES ;
2009-11-16 22:11:00 +01:00
struct RARHeaderData headerData ;
NSString * lastExtractedFilename = @ "" ;
NSString * currentFilename ;
2021-05-24 11:24:32 +02:00
2009-11-14 23:12:16 +01:00
// Determine the folder we should extract the archive to . This by default
// is the < folderContainingTheArchive > / < archiveNameWithPathExtension >
2009-11-16 16:02:10 +01:00
NSString * folderToExtractTo = [ filename stringByDeletingPathExtension ] ;
2021-05-24 11:24:32 +02:00
2009-11-14 23:12:16 +01:00
// Open the Archive for extraction , we set the open result to 3 so we can see it has changed
2009-11-16 22:11:00 +01:00
char * filenameCString = ( char * ) [ filename cStringUsingEncoding : NSISOLatin1StringEncoding ] ;
2021-05-24 11:24:32 +02:00
struct RAROpenArchiveData arcData = { filenameCString , RAR_OM _EXTRACT , 3 , & commentBuffer [ 0 ] , BUF_LEN , 0 , 0 } ;
2009-11-16 22:11:00 +01:00
2009-11-14 23:12:16 +01:00
HANDLE archive = RAROpenArchive ( & arcData ) ;
2009-11-15 18:50:12 +01:00
// NSLog ( @ "Opening Archive %s with result %d" , filenameCString , arcData . OpenResult ) ;
2021-05-24 11:24:32 +02:00
2009-11-14 23:12:16 +01:00
// set call backs for if password needed or need to change volume
2009-11-15 15:43:54 +01:00
RARSetChangeVolProc ( archive , & changeVolume ) ;
2009-11-15 18:50:12 +01:00
RARSetCallback ( archive , & callbackFunction , ( LPARAM ) archive ) ;
2021-05-24 11:24:32 +02:00
2021-05-26 14:49:40 +02:00
while ( RARReadHeader ( archive , & headerData ) = = ERAR_SUCCESS ) {
2009-11-16 19:37:46 +01:00
// NSLog ( @ "Attempting to extract %s to %@" , headerData . FileName , folderToExtractTo ) ;
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
int processResult = 0 ;
BOOL extractFile = YES ;
2009-11-16 19:37:46 +01:00
BOOL isDir ;
2009-11-15 13:54:07 +01:00
currentFilename = [ NSString stringWithCString : ( const char * ) headerData . FileName encoding : NSISOLatin1StringEncoding ] ;
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
NSFileManager * fileManager = [ NSFileManager defaultManager ] ;
2021-05-24 11:24:32 +02:00
2009-11-16 19:37:46 +01:00
if ( [ fileManager fileExistsAtPath : [ NSString stringWithFormat : @ "%@/%s" , folderToExtractTo , headerData . FileName ] isDirectory : & isDir ] ) {
2009-11-15 13:54:07 +01:00
// 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 .
2021-05-24 11:24:32 +02:00
if ( [ lastExtractedFilename isEqualToString : currentFilename ] ||
2009-11-16 19:37:46 +01:00
isDir ||
! [ self shouldFileBeReplaced : currentFilename ] ) {
2009-11-15 13:54:07 +01:00
extractFile = NO ;
}
}
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
// NSLog ( @ "Last filename %@, currentFilename %@, equality %d" , lastExtractedFilename , currentFilename , [ lastExtractedFilename isEqualToString : currentFilename ] ) ;
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
if ( extractFile ) {
2009-11-15 18:50:12 +01:00
// NSLog ( @ "...Extracting" ) ;
2009-11-16 16:02:10 +01:00
processResult = RARProcessFile ( archive , RAR_EXTRACT , ( char * ) [ folderToExtractTo cStringUsingEncoding : NSISOLatin1StringEncoding ] , NULL ) ;
2009-11-15 13:54:07 +01:00
} else {
2009-11-15 18:50:12 +01:00
// NSLog ( @ "...Skipping as already exists" ) ;
2009-11-15 13:54:07 +01:00
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 .
}
2021-05-24 11:24:32 +02:00
2009-11-15 00:18:58 +01:00
if ( processResult ! = 0 ) {
2009-11-15 13:54:07 +01:00
NSLog ( @ "Error: Process Result was %d" , processResult ) ;
2009-11-14 23:12:16 +01:00
extractionSuccessful = NO ;
2009-11-15 13:54:07 +01:00
break ;
2009-11-14 23:12:16 +01:00
// DISPLAY ERROR DIALOG , ALERT THE USER
}
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
lastExtractedFilename = currentFilename ;
2009-11-14 23:12:16 +01:00
}
2021-05-24 11:24:32 +02:00
2009-11-16 19:37:46 +01:00
RARCloseArchive ( archive ) ;
2009-11-15 18:50:12 +01:00
// NSLog ( @ "Closing Archive %s with result %d" , filenameCString , closeResult ) ;
2009-11-15 00:18:58 +01:00
2009-11-14 23:12:16 +01:00
return extractionSuccessful ;
}
2009-11-16 15:00:11 +01:00
// Presents a dialog to the user allowing them to Skip a file or overwrite an existing version
// returns YES or NO
2009-11-15 13:54:07 +01:00
- ( BOOL ) shouldFileBeReplaced : ( NSString * ) filename {
BOOL result = NO ;
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
NSAlert * alert = [ [ NSAlert alloc ] init ] ;
[ alert addButtonWithTitle : @ "Overwrite" ] ;
2009-11-15 15:54:55 +01:00
NSButton * skipButton = [ alert addButtonWithTitle : @ "Skip" ] ;
[ skipButton setKeyEquivalent : @ "\e" ] ;
2009-11-15 13:54:07 +01:00
[ alert setMessageText : [ NSString stringWithFormat : @ "Overwrite %@?" , filename ] ] ;
2021-05-24 11:24:32 +02:00
[ alert setInformativeText : [ NSString stringWithFormat : @ "The file %@ already exists. Do you wish to extract it again, overwriting the original file?" , filename ] ] ;
[ alert setAlertStyle : NSAlertStyleWarning ] ;
2009-11-15 13:54:07 +01:00
if ( [ alert runModal ] = = NSAlertFirstButtonReturn ) {
result = YES ;
}
2021-05-24 11:24:32 +02:00
2009-11-15 13:54:07 +01:00
return result ;
}
2009-11-16 15:00:11 +01:00
// Indicate to the user that part of the RAR volume is missing .
2009-11-15 15:43:54 +01:00
- ( void ) alertUserOfMissing : ( const char * ) volume {
NSAlert * alert = [ [ NSAlert alloc ] init ] ;
[ alert addButtonWithTitle : @ "OK" ] ;
[ alert setMessageText : [ NSString stringWithFormat : @ "Archive part %s is missing." , volume ] ] ;
[ alert setInformativeText : @ "Unable to extract all files from RAR archive as part of it is missing" ] ;
2021-05-24 11:24:32 +02:00
[ alert setAlertStyle : NSAlertStyleCritical ] ;
2009-11-15 15:43:54 +01:00
[ alert runModal ] ;
}
2009-11-16 15:00:11 +01:00
// Creates a dialog with a custom view with a NSSecureTextField which is displayed
// to the user so they can provide a password . Returns the entered password or nil
- ( NSString * ) requestArchivePassword {
2009-11-15 18:50:12 +01:00
if ( ! passwordView ) {
2021-05-24 11:24:32 +02:00
NSBundle * bundle = [ NSBundle bundleForClass : [ self class ] ] ;
[ bundle loadNibNamed : @ "PasswordView" owner : self topLevelObjects : nil ] ;
2021-05-24 17:37:15 +02:00
} else {
[ passwordField setStringValue : @ "" ] ;
}
2021-05-24 11:24:32 +02:00
2009-11-16 15:00:11 +01:00
NSString * password = nil ;
2021-05-24 11:24:32 +02:00
2009-11-15 18:50:12 +01:00
NSAlert * alert = [ [ NSAlert alloc ] init ] ;
[ alert addButtonWithTitle : @ "OK" ] ;
[ alert addButtonWithTitle : @ "Cancel" ] ;
[ alert setMessageText : @ "Archive Requires a password" ] ;
[ alert setInformativeText : @ "To extract the contents of this archive a password is required." ] ;
[ alert setAccessoryView : passwordView ] ;
2021-05-24 11:24:32 +02:00
[ alert setAlertStyle : NSAlertStyleWarning ] ;
2009-11-15 18:50:12 +01:00
if ( [ alert runModal ] = = NSAlertFirstButtonReturn ) {
2009-11-16 15:00:11 +01:00
password = [ passwordField stringValue ] ;
2009-11-15 18:50:12 +01:00
}
2021-05-24 11:24:32 +02:00
2009-11-16 15:00:11 +01:00
return password ;
2009-11-15 18:50:12 +01:00
}
2009-11-10 16:48:42 +01:00
@ end