Skip to content

Commit 08434ed

Browse files
committed
Move creating diffs into separate source file
Since a function for creating diffs is now exposed, this should make testing easier This is also consistent with applying diffs being in a separate source file from the delta tool as well
1 parent 3de3b11 commit 08434ed

4 files changed

Lines changed: 300 additions & 259 deletions

File tree

Sparkle.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
61F83F740DBFE141006FDD30 /* SUBasicUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */; settings = {ATTRIBUTES = (); }; };
169169
61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Sparkle.framework */; settings = {ATTRIBUTES = (Required, ); }; };
170170
7223E7631AD1AEFF008E3161 /* sais.c in Sources */ = {isa = PBXBuildFile; fileRef = 7223E7611AD1AEFF008E3161 /* sais.c */; };
171+
7268AC631AD634C200C3E0C1 /* SUBinaryDeltaCreate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */; };
171172
767B61AC1972D488004E0C3C /* SUGuidedPackageInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */; };
172173
767B61AD1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */; };
173174
767B61AE1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */; };
@@ -537,6 +538,8 @@
537538
61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBasicUpdateDriver.m; sourceTree = "<group>"; };
538539
7223E7611AD1AEFF008E3161 /* sais.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sais.c; sourceTree = "<group>"; };
539540
7223E7621AD1AEFF008E3161 /* sais.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sais.h; sourceTree = "<group>"; };
541+
7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBinaryDeltaCreate.m; sourceTree = "<group>"; };
542+
7268AC641AD634E400C3E0C1 /* SUBinaryDeltaCreate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBinaryDeltaCreate.h; sourceTree = "<group>"; };
540543
767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUGuidedPackageInstaller.h; sourceTree = "<group>"; };
541544
767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUGuidedPackageInstaller.m; sourceTree = "<group>"; };
542545
8DC2EF5A0486A6940098B216 /* Sparkle-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Sparkle-Info.plist"; sourceTree = "<group>"; };
@@ -787,6 +790,8 @@
787790
children = (
788791
5D06E8DF0FD68CC7005AE3F6 /* SUBinaryDeltaApply.h */,
789792
5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */,
793+
7268AC641AD634E400C3E0C1 /* SUBinaryDeltaCreate.h */,
794+
7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */,
790795
5D06E8E10FD68CC7005AE3F6 /* SUBinaryDeltaCommon.h */,
791796
5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */,
792797
5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */,
@@ -1400,6 +1405,7 @@
14001405
5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */,
14011406
14652F7E19A9728A00959E44 /* bspatch.c in Sources */,
14021407
14652F7D19A9726700959E44 /* SUBinaryDeltaApply.m in Sources */,
1408+
7268AC631AD634C200C3E0C1 /* SUBinaryDeltaCreate.m in Sources */,
14031409
7223E7631AD1AEFF008E3161 /* sais.c in Sources */,
14041410
14652F7C19A9725300959E44 /* SUBinaryDeltaCommon.m in Sources */,
14051411
5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */,

Sparkle/SUBinaryDeltaCreate.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// SUBinaryDeltaCreate.m
3+
// Sparkle
4+
//
5+
// Created by Mayur Pawashe on 4/9/15.
6+
// Copyright (c) 2015 Sparkle Project. All rights reserved.
7+
//
8+
9+
#ifndef SUBINARYDELTACREATE_H
10+
#define SUBINARYDELTACREATE_H
11+
12+
@class NSString;
13+
int createBinaryDelta(NSString *source, NSString *destination, NSString *patchFile);
14+
15+
#endif

Sparkle/SUBinaryDeltaCreate.m

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
//
2+
// SUBinaryDeltaCreate.m
3+
// Sparkle
4+
//
5+
// Created by Mayur Pawashe on 4/9/15.
6+
// Copyright (c) 2015 Sparkle Project. All rights reserved.
7+
//
8+
9+
#define _DARWIN_NO_64_BIT_INODE 1
10+
11+
#import "SUBinaryDeltaCreate.h"
12+
#import <Foundation/Foundation.h>
13+
#include "SUBinaryDeltaCommon.h"
14+
#import <CommonCrypto/CommonDigest.h>
15+
#include <fcntl.h>
16+
#include <fts.h>
17+
#include <libgen.h>
18+
#include <stdio.h>
19+
#include <sys/mman.h>
20+
#include <sys/param.h>
21+
#include <sys/stat.h>
22+
#include <unistd.h>
23+
#include <xar/xar.h>
24+
25+
extern int bsdiff(int argc, const char **argv);
26+
27+
@interface CreateBinaryDeltaOperation : NSOperation
28+
@property (copy) NSString *relativePath;
29+
@property (strong) NSString *resultPath;
30+
@property (strong) NSString *_fromPath;
31+
@property (strong) NSString *_toPath;
32+
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree;
33+
@end
34+
35+
@implementation CreateBinaryDeltaOperation
36+
@synthesize relativePath = _relativePath;
37+
@synthesize resultPath = _resultPath;
38+
@synthesize _fromPath = _fromPath;
39+
@synthesize _toPath = _toPath;
40+
41+
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree
42+
{
43+
if ((self = [super init])) {
44+
self.relativePath = relativePath;
45+
self._fromPath = [oldTree stringByAppendingPathComponent:relativePath];
46+
self._toPath = [newTree stringByAppendingPathComponent:relativePath];
47+
}
48+
return self;
49+
}
50+
51+
- (void)main
52+
{
53+
NSString *temporaryFile = temporaryFilename(@"BinaryDelta");
54+
const char *argv[] = {"/usr/bin/bsdiff", [self._fromPath fileSystemRepresentation], [self._toPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]};
55+
int result = bsdiff(4, argv);
56+
if (!result)
57+
self.resultPath = temporaryFile;
58+
}
59+
60+
@end
61+
62+
static NSDictionary *infoForFile(FTSENT *ent)
63+
{
64+
NSData *hash = hashOfFile(ent);
65+
NSNumber *size = @0;
66+
if (ent->fts_info != FTS_D) {
67+
size = @(ent->fts_statp->st_size);
68+
}
69+
return @{@"hash": hash, @"type": @(ent->fts_info), @"size": size};
70+
}
71+
72+
static NSString *absolutePath(NSString *path)
73+
{
74+
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
75+
return [[url absoluteURL] path];
76+
}
77+
78+
static NSString *temporaryPatchFile(NSString *patchFile)
79+
{
80+
NSString *path = absolutePath(patchFile);
81+
NSString *directory = [path stringByDeletingLastPathComponent];
82+
NSString *file = [path lastPathComponent];
83+
return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file];
84+
}
85+
86+
static BOOL shouldSkipDeltaCompression(NSString * __unused key, NSDictionary* originalInfo, NSDictionary *newInfo)
87+
{
88+
unsigned long long fileSize = [newInfo[@"size"] unsignedLongLongValue];
89+
if (fileSize < 4096) {
90+
return YES;
91+
}
92+
93+
if (!originalInfo) {
94+
return YES;
95+
}
96+
97+
if ([originalInfo[@"type"] unsignedShortValue] != [newInfo[@"type"] unsignedShortValue]) {
98+
return YES;
99+
}
100+
101+
return NO;
102+
}
103+
104+
static BOOL shouldDeleteThenExtract(NSString * __unused key, NSDictionary* originalInfo, NSDictionary *newInfo)
105+
{
106+
if (!originalInfo) {
107+
return NO;
108+
}
109+
110+
if ([originalInfo[@"type"] unsignedShortValue] != [newInfo[@"type"] unsignedShortValue]) {
111+
return YES;
112+
}
113+
114+
return NO;
115+
}
116+
117+
int createBinaryDelta(NSString *source, NSString *destination, NSString *patchFile)
118+
{
119+
NSMutableDictionary *originalTreeState = [NSMutableDictionary dictionary];
120+
121+
const char *sourcePaths[] = {[source fileSystemRepresentation], 0};
122+
FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
123+
if (!fts) {
124+
perror("fts_open");
125+
return 1;
126+
}
127+
128+
fprintf(stdout, "Processing %s...", [source UTF8String]);
129+
FTSENT *ent = 0;
130+
while ((ent = fts_read(fts))) {
131+
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
132+
continue;
133+
}
134+
135+
NSString *key = pathRelativeToDirectory(source, stringWithFileSystemRepresentation(ent->fts_path));
136+
if (![key length]) {
137+
continue;
138+
}
139+
140+
NSDictionary *info = infoForFile(ent);
141+
originalTreeState[key] = info;
142+
}
143+
fts_close(fts);
144+
145+
NSString *beforeHash = hashOfTree(source);
146+
147+
NSMutableDictionary *newTreeState = [NSMutableDictionary dictionary];
148+
for (NSString *key in originalTreeState)
149+
{
150+
newTreeState[key] = [NSNull null];
151+
}
152+
153+
fprintf(stdout, "\nProcessing %s... ", [destination UTF8String]);
154+
sourcePaths[0] = [destination fileSystemRepresentation];
155+
fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
156+
if (!fts) {
157+
perror("fts_open");
158+
return 1;
159+
}
160+
161+
162+
while ((ent = fts_read(fts))) {
163+
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
164+
continue;
165+
}
166+
167+
NSString *key = pathRelativeToDirectory(destination, stringWithFileSystemRepresentation(ent->fts_path));
168+
if (![key length]) {
169+
continue;
170+
}
171+
172+
NSDictionary *info = infoForFile(ent);
173+
NSDictionary *oldInfo = originalTreeState[key];
174+
175+
if ([info isEqual:oldInfo]) {
176+
[newTreeState removeObjectForKey:key];
177+
} else {
178+
newTreeState[key] = info;
179+
180+
if (oldInfo && [oldInfo[@"type"] unsignedShortValue] == FTS_D && [info[@"type"] unsignedShortValue] != FTS_D) {
181+
NSArray *parentPathComponents = key.pathComponents;
182+
183+
for (NSString *childPath in originalTreeState) {
184+
NSArray *childPathComponents = childPath.pathComponents;
185+
if (childPathComponents.count > parentPathComponents.count &&
186+
[parentPathComponents isEqualToArray:[childPathComponents subarrayWithRange:NSMakeRange(0, parentPathComponents.count)]]) {
187+
[newTreeState removeObjectForKey:childPath];
188+
}
189+
}
190+
}
191+
}
192+
}
193+
fts_close(fts);
194+
195+
NSString *afterHash = hashOfTree(destination);
196+
197+
fprintf(stdout, "\nGenerating delta... ");
198+
199+
NSString *temporaryFile = temporaryPatchFile(patchFile);
200+
xar_t x = xar_open([temporaryFile fileSystemRepresentation], WRITE);
201+
xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2");
202+
xar_subdoc_t attributes = xar_subdoc_new(x, "binary-delta-attributes");
203+
xar_subdoc_prop_set(attributes, "before-sha1", [beforeHash UTF8String]);
204+
xar_subdoc_prop_set(attributes, "after-sha1", [afterHash UTF8String]);
205+
206+
NSOperationQueue *deltaQueue = [[NSOperationQueue alloc] init];
207+
NSMutableArray *deltaOperations = [NSMutableArray array];
208+
209+
// Sort the keys by preferring the ones from the original tree to appear first
210+
// We want to enforce deleting before extracting in the case paths differ only by case
211+
NSArray *keys = [[newTreeState allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *key1, NSString *key2) {
212+
NSComparisonResult insensitiveCompareResult = [key1 caseInsensitiveCompare:key2];
213+
if (insensitiveCompareResult != NSOrderedSame) {
214+
return insensitiveCompareResult;
215+
}
216+
217+
return originalTreeState[key1] ? NSOrderedAscending : NSOrderedDescending;
218+
}];
219+
for (NSString* key in keys) {
220+
id value = [newTreeState valueForKey:key];
221+
222+
if ([value isEqual:[NSNull null]]) {
223+
xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
224+
assert(newFile);
225+
xar_prop_set(newFile, "delete", "true");
226+
continue;
227+
}
228+
229+
NSDictionary *originalInfo = originalTreeState[key];
230+
NSDictionary *newInfo = newTreeState[key];
231+
if (shouldSkipDeltaCompression(key, originalInfo, newInfo)) {
232+
NSString *path = [destination stringByAppendingPathComponent:key];
233+
xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]);
234+
assert(newFile);
235+
if (shouldDeleteThenExtract(key, originalInfo, newInfo)) {
236+
xar_prop_set(newFile, "delete-then-extract", "true");
237+
}
238+
} else {
239+
CreateBinaryDeltaOperation *operation = [[CreateBinaryDeltaOperation alloc] initWithRelativePath:key oldTree:source newTree:destination];
240+
[deltaQueue addOperation:operation];
241+
[deltaOperations addObject:operation];
242+
}
243+
}
244+
245+
[deltaQueue waitUntilAllOperationsAreFinished];
246+
247+
for (CreateBinaryDeltaOperation *operation in deltaOperations) {
248+
NSString *resultPath = [operation resultPath];
249+
xar_file_t newFile = xar_add_frompath(x, 0, [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
250+
assert(newFile);
251+
xar_prop_set(newFile, "binary-delta", "true");
252+
unlink([resultPath fileSystemRepresentation]);
253+
}
254+
255+
xar_close(x);
256+
257+
unlink([patchFile fileSystemRepresentation]);
258+
link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]);
259+
unlink([temporaryFile fileSystemRepresentation]);
260+
fprintf(stdout, "Done!\n");
261+
262+
return 0;
263+
}

0 commit comments

Comments
 (0)