Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

270 lines (219 sloc) 8.906 kb
//
// SUBinaryDeltaTool.m
// Sparkle
//
// Created by Mark Rowe on 2009-06-01.
// Copyright 2009 Mark Rowe. All rights reserved.
//
#define _DARWIN_NO_64_BIT_INODE 1
#include "SUBinaryDeltaCommon.h"
#include "SUBinaryDeltaApply.h"
#include <CommonCrypto/CommonDigest.h>
#include <Foundation/Foundation.h>
#include <fcntl.h>
#include <fts.h>
#include <libgen.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>
#include <xar/xar.h>
extern int bsdiff(int argc, const char **argv);
@interface CreateBinaryDeltaOperation : NSOperation
{
NSString *_relativePath;
NSString *_fromPath;
NSString *_toPath;
NSString *_resultPath;
}
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree;
- (NSString *)relativePath;
- (NSString *)resultPath;
@end
@implementation CreateBinaryDeltaOperation
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree
{
if ((self = [super init])) {
_relativePath = [relativePath copy];
_fromPath = [[oldTree stringByAppendingPathComponent:relativePath] retain];
_toPath = [[newTree stringByAppendingPathComponent:relativePath] retain];
}
return self;
}
- (NSString *)relativePath
{
return [[_relativePath retain] autorelease];
}
- (NSString *)resultPath
{
return [[_resultPath retain] autorelease];
}
- (void)main
{
NSString *temporaryFile = temporaryFilename(@"BinaryDelta");
const char *argv[] = {"/usr/bin/bsdiff", [_fromPath fileSystemRepresentation], [_toPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]};
int result = bsdiff(4, argv);
if (!result)
_resultPath = [temporaryFile retain];
}
@end
static NSDictionary *infoForFile(FTSENT *ent)
{
NSData *hash = hashOfFile(ent);
NSNumber *size = nil;
if (ent->fts_info != FTS_D)
size = [NSNumber numberWithUnsignedLongLong:ent->fts_statp->st_size];
return [NSDictionary dictionaryWithObjectsAndKeys:hash, @"hash", [NSNumber numberWithUnsignedShort:ent->fts_info], @"type", size, @"size", nil];
}
static NSString *absolutePath(NSString *path)
{
NSURL *url = [[[NSURL alloc] initFileURLWithPath:path] autorelease];
return [[url absoluteURL] path];
}
static NSString *temporaryPatchFile(NSString *patchFile)
{
NSString *path = absolutePath(patchFile);
NSString *directory = [path stringByDeletingLastPathComponent];
NSString *file = [path lastPathComponent];
return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file];
}
static BOOL shouldSkipDeltaCompression(NSString *key, NSDictionary* originalInfo, NSDictionary *newInfo)
{
unsigned long long fileSize = [[newInfo objectForKey:@"size"] unsignedLongLongValue];
if (fileSize < 4096)
return YES;
if (!originalInfo)
return YES;
if ([[originalInfo objectForKey:@"type"] unsignedShortValue] != [[newInfo objectForKey:@"type"] unsignedShortValue])
return YES;
return NO;
}
static BOOL shouldDeleteThenExtract(NSString *key, NSDictionary* originalInfo, NSDictionary *newInfo)
{
if (!originalInfo)
return NO;
if ([[originalInfo objectForKey:@"type"] unsignedShortValue] != [[newInfo objectForKey:@"type"] unsignedShortValue])
return YES;
return NO;
}
int main(int argc, char **argv)
{
if (argc != 5) {
usage:
fprintf(stderr, "Usage: BinaryDelta [create | apply] before-tree after-tree patch-file\n");
exit(1);
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *command = [NSString stringWithUTF8String:argv[1]];
NSString *oldPath = [NSString stringWithUTF8String:argv[2]];
NSString *newPath = [NSString stringWithUTF8String:argv[3]];
NSString *patchFile = [NSString stringWithUTF8String:argv[4]];
if ([command isEqualToString:@"apply"]) {
int result = applyBinaryDelta(oldPath, newPath, patchFile);
[pool drain];
return result;
}
if (![command isEqualToString:@"create"]) {
[pool drain];
goto usage;
}
NSMutableDictionary *originalTreeState = [NSMutableDictionary dictionary];
const char *sourcePaths[] = {[oldPath fileSystemRepresentation], 0};
FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
if (!fts) {
[pool drain];
perror("fts_open");
return 1;
}
fprintf(stderr, "Processing %s...", [oldPath UTF8String]);
FTSENT *ent = 0;
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D)
continue;
NSString *key = pathRelativeToDirectory(oldPath, [NSString stringWithUTF8String:ent->fts_path]);
if (![key length])
continue;
NSDictionary *info = infoForFile(ent);
[originalTreeState setObject:info forKey:key];
}
fts_close(fts);
NSString *beforeHash = hashOfTree(oldPath);
NSMutableDictionary *newTreeState = [NSMutableDictionary dictionary];
for (NSString *key in originalTreeState)
{
[newTreeState setObject:[NSNull null] forKey:key];
}
fprintf(stderr, "\nProcessing %s... ", [newPath UTF8String]);
sourcePaths[0] = [newPath fileSystemRepresentation];
fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
if (!fts) {
[pool drain];
perror("fts_open");
return 1;
}
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D)
continue;
NSString *key = pathRelativeToDirectory(newPath, [NSString stringWithUTF8String:ent->fts_path]);
if (![key length])
continue;
NSDictionary *info = infoForFile(ent);
NSDictionary *oldInfo = [originalTreeState objectForKey:key];
if ([info isEqual:oldInfo])
[newTreeState removeObjectForKey:key];
else
[newTreeState setObject:info forKey:key];
}
fts_close(fts);
NSString *afterHash = hashOfTree(newPath);
fprintf(stderr, "\nGenerating delta... ");
NSString *temporaryFile = temporaryPatchFile(patchFile);
xar_t x = xar_open([temporaryFile fileSystemRepresentation], WRITE);
xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2");
xar_subdoc_t attributes = xar_subdoc_new(x, "binary-delta-attributes");
xar_subdoc_prop_set(attributes, "before-sha1", [beforeHash UTF8String]);
xar_subdoc_prop_set(attributes, "after-sha1", [afterHash UTF8String]);
NSOperationQueue *deltaQueue = [[NSOperationQueue alloc] init];
NSMutableArray *deltaOperations = [NSMutableArray array];
NSArray *keys = [[newTreeState allKeys] sortedArrayUsingSelector:@selector(compare:)];
for (NSString* key in keys) {
id value = [newTreeState valueForKey:key];
if ([value isEqual:[NSNull null]]) {
xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
assert(newFile);
xar_prop_set(newFile, "delete", "true");
continue;
}
NSDictionary *originalInfo = [originalTreeState objectForKey:key];
NSDictionary *newInfo = [newTreeState objectForKey:key];
if (shouldSkipDeltaCompression(key, originalInfo, newInfo)) {
NSString *path = [newPath stringByAppendingPathComponent:key];
xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]);
assert(newFile);
if (shouldDeleteThenExtract(key, originalInfo, newInfo))
xar_prop_set(newFile, "delete-then-extract", "true");
} else {
CreateBinaryDeltaOperation *operation = [[CreateBinaryDeltaOperation alloc] initWithRelativePath:key oldTree:oldPath newTree:newPath];
[deltaQueue addOperation:operation];
[deltaOperations addObject:operation];
[operation release];
}
}
[deltaQueue waitUntilAllOperationsAreFinished];
[deltaQueue release];
for (CreateBinaryDeltaOperation *operation in deltaOperations) {
NSString *resultPath = [operation resultPath];
xar_file_t newFile = xar_add_frompath(x, 0, [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
assert(newFile);
xar_prop_set(newFile, "binary-delta", "true");
unlink([resultPath fileSystemRepresentation]);
}
xar_close(x);
unlink([patchFile fileSystemRepresentation]);
link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]);
unlink([temporaryFile fileSystemRepresentation]);
fprintf(stderr, "Done!\n");
[pool drain];
return 0;
}
Jump to Line
Something went wrong with that request. Please try again.