Created
November 13, 2011 20:37
-
-
Save unixpickle/1362646 to your computer and use it in GitHub Desktop.
MD5FileHasher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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