QuietUnrar/TDNUnarchiver.m

219 lines
8.1 KiB
Objective-C

//
// TDNUnarchiver.m
// QuietUnrar
//
// Created by Robert McGovern on 2021/05/31.
//
@import Foundation;
#import <wchar.h>
#import "TDNUnarchiver.h"
#import "QuietUnrarAppDelegate.h"
@import UnrarKit;
//#import <UnrarKit/UnrarKit.h>
#import "libunrar/dll.hpp"
#import "libunrar/rardefs.hpp"
@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];
return [self extractRarUsingUnrarKitWithFilename: 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) {
__block NSString *givenPassword = nil;
dispatch_sync(dispatch_get_main_queue(), ^{
givenPassword = [quietUnrar requestArchivePassword];
});
if (givenPassword != nil) {
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