Skip to content

Instantly share code, notes, and snippets.

@unixpickle
Created November 13, 2011 20:37
Show Gist options
  • Select an option

  • Save unixpickle/1362646 to your computer and use it in GitHub Desktop.

Select an option

Save unixpickle/1362646 to your computer and use it in GitHub Desktop.
MD5FileHasher
#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonDigest.h>
#define kDefaultHashBufferSize 0x1000
@class FileHasher;
typedef void (^FileHasherCallback) (FileHasher * hasher,
BOOL finished,
NSUInteger bytesCompleted);
@interface FileHasher : NSObject {
NSData * hashData;
NSLock * hashDataLock;
NSString * filePath;
NSUInteger partialSize;
NSUInteger hashSize;
NSLock * hashSizeLock;
FileHasherCallback callback;
NSThread * backgroundThread;
NSLock * backgroundThreadLock;
}
+ (void)hashFile:(NSString *)file partialSize:(NSUInteger)size callback:(FileHasherCallback)callback;
- (id)initWithFilePath:(NSString *)filePath callback:(FileHasherCallback)callback;
- (NSUInteger)totalSize;
- (void)calculateHashInBackground;
- (void)calculatePartialHashInBackground:(NSUInteger)partialSize;
- (void)cancelHash;
- (NSString *)hashHexString;
- (NSData *)rawHashData;
@end
NSUInteger SizeOfFileAtPath (NSString * filePath);
NSData * PartialFileHash (NSString * filePath,
NSUInteger partialSize,
BOOL (^progressCallback)(NSUInteger bytesDone));
#import "FileHasher.h"
@interface FileHasher (AtomicProperties)
- (void)setBackgroundThread:(NSThread *)thread;
- (NSThread *)backgroundThread;
- (void)setHashData:(NSData *)someData;
- (NSData *)hashData;
- (void)setHashSize:(NSUInteger)aSize;
- (NSUInteger)hashSize;
@end
@interface FileHasher (Threading)
- (void)backgroundThread:(NSThread *)callbackThread;
- (void)callbackProgress:(NSNumber *)bytesDone;
- (void)callbackDone:(NSNumber *)bytesDone;
@end
@implementation FileHasher
#pragma mark Convenience
+ (void)hashFile:(NSString *)file partialSize:(NSUInteger)size callback:(FileHasherCallback)callback {
FileHasher * hasher = [[FileHasher alloc] initWithFilePath:file callback:callback];
[hasher calculatePartialHashInBackground:size];
}
#pragma mark Initialization
- (id)initWithFilePath:(NSString *)_filePath callback:(FileHasherCallback)_callback {
if ((self = [super init])) {
callback = _callback;
filePath = _filePath;
backgroundThreadLock = [[NSLock alloc] init];
hashDataLock = [[NSLock alloc] init];
hashSizeLock = [[NSLock alloc] init];
}
return self;
}
#pragma mark Start Calculation
- (NSUInteger)totalSize {
return self.hashSize;
}
- (void)calculateHashInBackground {
[self calculatePartialHashInBackground:0];
}
- (void)calculatePartialHashInBackground:(NSUInteger)_partialSize {
if (self.backgroundThread) return;
partialSize = _partialSize;
self.hashSize = 0;
self.backgroundThread = [[NSThread alloc] initWithTarget:self
selector:@selector(backgroundThread:)
object:[NSThread currentThread]];
[self.backgroundThread start];
}
- (void)cancelHash {
[self.backgroundThread cancel];
self.backgroundThread = nil;
}
#pragma mark Retrieving Hashes
- (NSString *)hashHexString {
NSData * _hashData = self.hashData;
NSMutableString * string = [NSMutableString string];
const unsigned char * bytes = (const unsigned char *)[_hashData bytes];
for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[string appendFormat:@"%02x", bytes[i]];
}
return string;
}
- (NSData *)rawHashData {
return [self hashData];
}
#pragma mark - Threading -
#pragma mark Background Thread
- (void)backgroundThread:(NSThread *)callbackThread {
@autoreleasepool {
NSUInteger size = partialSize;
if (size == 0) {
size = SizeOfFileAtPath(filePath);
}
self.hashSize = size;
__block BOOL hasInformedDone = NO;
NSData * hash = PartialFileHash(filePath, size, ^ BOOL (NSUInteger bytesDone) {
if ([[NSThread currentThread] isCancelled]) {
return NO;
}
if (bytesDone == size && !hasInformedDone) {
hasInformedDone = YES;
[self performSelectorOnMainThread:@selector(callbackDone:)
withObject:[NSNumber numberWithUnsignedInteger:size]
waitUntilDone:NO];
} else {
[self performSelectorOnMainThread:@selector(callbackProgress:)
withObject:[NSNumber numberWithUnsignedInteger:bytesDone]
waitUntilDone:NO];
}
return YES;
});
if (![[NSThread currentThread] isCancelled]) {
self.hashData = hash;
self.backgroundThread = nil;
if (!hasInformedDone) {
[self performSelectorOnMainThread:@selector(callbackDone:)
withObject:[NSNumber numberWithUnsignedInteger:size]
waitUntilDone:NO];
}
}
}
}
#pragma mark Main Thread
- (void)callbackProgress:(NSNumber *)bytesDone {
if (callback) {
callback(self, NO, [bytesDone unsignedIntegerValue]);
}
}
- (void)callbackDone:(NSNumber *)bytesDone {
if (callback) {
callback(self, YES, [bytesDone unsignedIntegerValue]);
}
}
@end
@implementation FileHasher (AtomicProperties)
- (void)setBackgroundThread:(NSThread *)thread {
[backgroundThreadLock lock];
backgroundThread = thread;
[backgroundThreadLock unlock];
}
- (NSThread *)backgroundThread {
NSThread * thread = nil;
[backgroundThreadLock lock];
thread = backgroundThread;
[backgroundThreadLock unlock];
return thread;
}
- (void)setHashData:(NSData *)someData {
[hashDataLock lock];
hashData = [someData copy];
[hashDataLock unlock];
}
- (NSData *)hashData {
NSData * theData = nil;
[hashDataLock lock];
theData = [hashData copy];
[hashDataLock unlock];
return theData;
}
- (void)setHashSize:(NSUInteger)aSize {
[hashSizeLock lock];
hashSize = aSize;
[hashSizeLock unlock];
}
- (NSUInteger)hashSize {
NSUInteger retVal;
[hashSizeLock lock];
retVal = hashSize;
[hashSizeLock unlock];
return retVal;
}
@end
#pragma mark - C Implementation -
NSUInteger SizeOfFileAtPath (NSString * filePath) {
FILE * fp = fopen([filePath UTF8String], "r");
if (!fp) return 0;
fseek(fp, 0, SEEK_END);
NSUInteger size = (NSUInteger)ftell(fp);
fclose(fp);
return size;
}
NSData * PartialFileHash (NSString * filePath,
NSUInteger partialSize,
BOOL (^progressCallback)(NSUInteger bytesDone)) {
if (!filePath) return nil;
NSUInteger bufferSize = kDefaultHashBufferSize;
NSUInteger bytesRemaining = partialSize;
FILE * fp;
CC_MD5_CTX hashObject;
fp = fopen([filePath UTF8String], "r");
if (!fp) return nil;
CC_MD5_Init(&hashObject);
char * buffer = (char *)malloc(bufferSize);
while (bytesRemaining > 0) {
NSUInteger blockSize = bytesRemaining < bufferSize ? bytesRemaining : bufferSize;
size_t readSize = fread(buffer, 1, blockSize, fp);
if (readSize < blockSize) {
free(buffer);
fclose(fp);
return nil;
}
CC_MD5_Update(&hashObject, buffer, (CC_LONG)readSize);
bytesRemaining -= readSize;
if (progressCallback) {
if (!progressCallback(partialSize - bytesRemaining)) {
free(buffer);
fclose(fp);
return nil;
}
}
}
buffer = realloc(buffer, CC_MD5_DIGEST_LENGTH);
// use our buffer for the MD5 buffer.
CC_MD5_Final((unsigned char *)buffer, &hashObject);
NSData * data = [NSData dataWithBytes:buffer length:CC_MD5_DIGEST_LENGTH];
free(buffer);
return data;
}
#import <Foundation/Foundation.h>
#import "FileHasher.h"
int main (int argc, const char * argv[]) {
@autoreleasepool {
char fileBuff[512];
printf("Gimme a file: ");
fgets(fileBuff, 512, stdin);
NSString * file = [NSString stringWithUTF8String:fileBuff];
if ([file hasSuffix:@"\n"])
file = [file substringWithRange:NSMakeRange(0, [file length] - 1)];
__block int lastPct = 0;
[FileHasher hashFile:file
partialSize:0
callback:^(FileHasher * hasher, BOOL finished, NSUInteger bytesCompleted) {
if (finished) {
printf("Final hash: %s\n", [[hasher hashHexString] UTF8String]);
exit(-1);
} else {
double percentage = (double)bytesCompleted / (double)hasher.totalSize;
int pct = (int)round(percentage * 100.0);
if (pct != lastPct) {
lastPct = pct;
printf("%d%%\n", pct);
}
}
}];
[[NSRunLoop currentRunLoop] run];
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment