Compare commits
38
Commits
backport-rac
...
rac-3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33137bcae6 | ||
|
|
532c565b2a | ||
|
|
9f5a0761ae | ||
|
|
572ef9aa9b | ||
|
|
b8aef91738 | ||
|
|
edb4aafa31 | ||
|
|
de276d73a6 | ||
|
|
7655bd0a90 | ||
|
|
e9a5874339 | ||
|
|
1ac9c4ddc1 | ||
|
|
9907d44f40 | ||
|
|
b5bbdfbed0 | ||
|
|
28079454dc | ||
|
|
210e09e079 | ||
|
|
f0161a51d9 | ||
|
|
d121f4cf7f | ||
|
|
02bb683cf7 | ||
|
|
d64a09bb2b | ||
|
|
5f201f833e | ||
|
|
6e1b3c9ded | ||
|
|
656decb128 | ||
|
|
4ef110e84b | ||
|
|
a22ebbfdb9 | ||
|
|
8cdf3d0666 | ||
|
|
ff6a2e1cb7 | ||
|
|
c3a41e9032 | ||
|
|
27eb479de4 | ||
|
|
aab4ca3efb | ||
|
|
6bc3b0d466 | ||
|
|
ef83ed3175 | ||
|
|
c382fbce40 | ||
|
|
d0498c528e | ||
|
|
cb2502510b | ||
|
|
49a8f3b624 | ||
|
|
36e3b17abb | ||
|
|
eb49de653b | ||
|
|
35e5a03130 | ||
|
|
b4387c312a |
Vendored
+1
-1
Submodule External/ReactiveCocoa updated: aa8ba8c868...09ed195916
@@ -20,6 +20,20 @@
|
||||
ReferencedContainer = "container:Squirrel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "D0C22BEE179CC00E00158214"
|
||||
BuildableName = "SquirrelTests.octest"
|
||||
BlueprintName = "SquirrelTests"
|
||||
ReferencedContainer = "container:Squirrel.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
|
||||
@@ -97,15 +97,12 @@ static RACDisposable *SQRLCreateTransaction(NSString *name, NSString *descriptio
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
|
||||
RACDisposable *transactionDisposable = SQRLCreateTransaction(name, description);
|
||||
RACDisposable *subscriptionDisposable = [self subscribe:subscriber];
|
||||
|
||||
return [RACDisposable disposableWithBlock:^{
|
||||
[subscriptionDisposable dispose];
|
||||
[transactionDisposable dispose];
|
||||
}];
|
||||
}] setNameWithFormat:@"[%@] -sqrl_addTransactionWithName: %@ description: %@", self.name, name, description];
|
||||
return [[RACSignal
|
||||
create:^(id<RACSubscriber> subscriber) {
|
||||
[subscriber.disposable addDisposable:SQRLCreateTransaction(name, description)];
|
||||
[self subscribe:subscriber];
|
||||
}]
|
||||
setNameWithFormat:@"[%@] -sqrl_addTransactionWithName: %@ description: %@", self.name, name, description];
|
||||
}
|
||||
|
||||
- (RACSignal *)sqrl_addSubscriptionTransactionWithName:(NSString *)name description:(NSString *)descriptionFormat, ... {
|
||||
@@ -117,13 +114,13 @@ static RACDisposable *SQRLCreateTransaction(NSString *name, NSString *descriptio
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
|
||||
RACDisposable *transactionDisposable = SQRLCreateTransaction(name, description);
|
||||
RACDisposable *subscriptionDisposable = [self subscribe:subscriber];
|
||||
[transactionDisposable dispose];
|
||||
|
||||
return subscriptionDisposable;
|
||||
}] setNameWithFormat:@"[%@] -sqrl_addSubscriptionTransactionWithName: %@ description: %@", self.name, name, description];
|
||||
return [[RACSignal
|
||||
create:^(id<RACSubscriber> subscriber) {
|
||||
RACDisposable *transactionDisposable = SQRLCreateTransaction(name, description);
|
||||
[self subscribe:subscriber];
|
||||
[transactionDisposable dispose];
|
||||
}]
|
||||
setNameWithFormat:@"[%@] -sqrl_addSubscriptionTransactionWithName: %@ description: %@", self.name, name, description];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -83,7 +83,7 @@ const NSInteger SQRLCodeSignatureErrorCouldNotCreateStaticCode = -2;
|
||||
- (RACSignal *)verifyBundleAtURL:(NSURL *)bundleURL {
|
||||
NSParameterAssert(bundleURL != nil);
|
||||
|
||||
return [[RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
|
||||
return [[RACSignal create:^(id<RACSubscriber> subscriber) {
|
||||
SecStaticCodeRef staticCode = NULL;
|
||||
|
||||
OSStatus result = SecStaticCodeCreateWithPath((__bridge CFURLRef)bundleURL, kSecCSDefaultFlags, &staticCode);
|
||||
@@ -100,8 +100,10 @@ const NSInteger SQRLCodeSignatureErrorCouldNotCreateStaticCode = -2;
|
||||
if (failureReason != nil) userInfo[NSLocalizedFailureReasonErrorKey] = failureReason;
|
||||
|
||||
[subscriber sendError:[NSError errorWithDomain:SQRLCodeSignatureErrorDomain code:SQRLCodeSignatureErrorCouldNotCreateStaticCode userInfo:userInfo]];
|
||||
return nil;
|
||||
return;
|
||||
}
|
||||
|
||||
if (subscriber.disposable.disposed) return;
|
||||
|
||||
CFErrorRef validityError = NULL;
|
||||
result = SecStaticCodeCheckValidityWithErrors(staticCode, kSecCSCheckAllArchitectures, (__bridge SecRequirementRef)self.requirement, &validityError);
|
||||
@@ -119,11 +121,10 @@ const NSInteger SQRLCodeSignatureErrorCouldNotCreateStaticCode = -2;
|
||||
if (validityError != NULL) userInfo[NSUnderlyingErrorKey] = (__bridge NSError *)validityError;
|
||||
|
||||
[subscriber sendError:[NSError errorWithDomain:SQRLCodeSignatureErrorDomain code:SQRLCodeSignatureErrorDidNotPass userInfo:userInfo]];
|
||||
return nil;
|
||||
return;
|
||||
}
|
||||
|
||||
[subscriber sendCompleted];
|
||||
return nil;
|
||||
}] setNameWithFormat:@"-verifyCodeSignatureOfBundle: %@", bundleURL];
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -34,7 +34,9 @@ extern const NSInteger SQRLInstallerErrorInvalidState;
|
||||
// There was an error moving a bundle across volumes.
|
||||
extern const NSInteger SQRLInstallerErrorMovingAcrossVolumes;
|
||||
|
||||
@class RACCommand;
|
||||
@class RACSignal;
|
||||
@class RACAction;
|
||||
@class SQRLShipItState;
|
||||
@class SQRLDirectoryManager;
|
||||
|
||||
// Performs the installation of an update, using the `SQRLShipItState` on disk,
|
||||
@@ -44,21 +46,20 @@ extern const NSInteger SQRLInstallerErrorMovingAcrossVolumes;
|
||||
// terminated.
|
||||
@interface SQRLInstaller : NSObject
|
||||
|
||||
// When executed with a `SQRLShipItState`, attempts to install the update or
|
||||
// resume an in-progress installation.
|
||||
// Attempts to install the update or resume an in-progress installation.
|
||||
//
|
||||
// Each execution will complete or error on an unspecified scheduler when
|
||||
// Returns a signal which completes or errors on an unspecified scheduler when
|
||||
// installation has completed or failed.
|
||||
@property (nonatomic, strong, readonly) RACCommand *installUpdateCommand;
|
||||
- (RACSignal *)installUpdateWithState:(SQRLShipItState *)state;
|
||||
|
||||
// When executed with a `SQRLShipItState`, aborts an installation, and attempts
|
||||
// to restore the old version of the application if necessary.
|
||||
// Aborts an installation, and attempts to restore the old version of the
|
||||
// application if necessary.
|
||||
//
|
||||
// This must not be executed while `installUpdateCommand` is executing.
|
||||
// This must not be executed while `installUpdateWithState:` is executing.
|
||||
//
|
||||
// Each execution will complete or error on an unspecified scheduler once
|
||||
// Returns a signal while completes or errors on an unspecified scheduler once
|
||||
// aborting/recovery has finished.
|
||||
@property (nonatomic, strong, readonly) RACCommand *abortInstallationCommand;
|
||||
- (RACSignal *)abortInstallationWithState:(SQRLShipItState *)state;
|
||||
|
||||
// Initializes an installer using the given directory manager to read and write the
|
||||
// state of the installation.
|
||||
|
||||
+45
-64
@@ -145,44 +145,34 @@ typedef struct {
|
||||
if (self == nil) return nil;
|
||||
|
||||
_directoryManager = directoryManager;
|
||||
|
||||
@weakify(self);
|
||||
|
||||
RACSignal *aborting = [[[[RACObserve(self, abortInstallationCommand)
|
||||
ignore:nil]
|
||||
map:^(RACCommand *command) {
|
||||
return command.executing;
|
||||
}]
|
||||
switchToLatest]
|
||||
setNameWithFormat:@"aborting"];
|
||||
|
||||
_installUpdateCommand = [[RACCommand alloc] initWithEnabled:[aborting not] signalBlock:^(SQRLShipItState *state) {
|
||||
@strongify(self);
|
||||
NSParameterAssert(state != nil);
|
||||
|
||||
return [[self
|
||||
resumeInstallationFromState:state]
|
||||
sqrl_addTransactionWithName:NSLocalizedString(@"Updating", nil) description:NSLocalizedString(@"%@ is being updated, and interrupting the process could corrupt the application", nil), state.targetBundleURL.path];
|
||||
}];
|
||||
|
||||
_abortInstallationCommand = [[RACCommand alloc] initWithEnabled:[self.installUpdateCommand.executing not] signalBlock:^(SQRLShipItState *state) {
|
||||
@strongify(self);
|
||||
NSParameterAssert(state != nil);
|
||||
|
||||
return [[[RACSignal
|
||||
zip:@[
|
||||
[self getRequiredKey:@keypath(state.targetBundleURL) fromState:state],
|
||||
[self getRequiredKey:@keypath(state.codeSignature) fromState:state]
|
||||
] reduce:^(NSURL *targetBundleURL, SQRLCodeSignature *codeSignature) {
|
||||
return [self verifyBundleAtURL:targetBundleURL usingSignature:codeSignature recoveringUsingBackupAtURL:state.backupBundleURL];
|
||||
}]
|
||||
flatten]
|
||||
sqrl_addTransactionWithName:NSLocalizedString(@"Aborting update", nil) description:NSLocalizedString(@"An update to %@ is being rolled back, and interrupting the process could corrupt the application", nil), state.targetBundleURL.path];
|
||||
}];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Actions
|
||||
|
||||
- (RACSignal *)installUpdateWithState:(SQRLShipItState *)state {
|
||||
NSParameterAssert(state != nil);
|
||||
|
||||
return [[self
|
||||
resumeInstallationFromState:state]
|
||||
sqrl_addTransactionWithName:NSLocalizedString(@"Updating", nil) description:NSLocalizedString(@"%@ is being updated, and interrupting the process could corrupt the application", nil), state.targetBundleURL.path];
|
||||
}
|
||||
|
||||
- (RACSignal *)abortInstallationWithState:(SQRLShipItState *)state {
|
||||
NSParameterAssert(state != nil);
|
||||
|
||||
return [[[RACSignal
|
||||
zip:@[
|
||||
[self getRequiredKey:@keypath(state.targetBundleURL) fromState:state],
|
||||
[self getRequiredKey:@keypath(state.codeSignature) fromState:state]
|
||||
] reduce:^(NSURL *targetBundleURL, SQRLCodeSignature *codeSignature) {
|
||||
return [self verifyBundleAtURL:targetBundleURL usingSignature:codeSignature recoveringUsingBackupAtURL:state.backupBundleURL];
|
||||
}]
|
||||
flatten]
|
||||
sqrl_addTransactionWithName:NSLocalizedString(@"Aborting update", nil) description:NSLocalizedString(@"An update to %@ is being rolled back, and interrupting the process could corrupt the application", nil), state.targetBundleURL.path];
|
||||
}
|
||||
|
||||
#pragma mark Installer State
|
||||
|
||||
- (RACSignal *)getRequiredKey:(NSString *)key fromState:(SQRLShipItState *)state {
|
||||
@@ -262,13 +252,12 @@ typedef struct {
|
||||
nextState = dispatchTable[tableIndex + 1].installerState;
|
||||
}
|
||||
|
||||
return [[step
|
||||
return [[[step
|
||||
doCompleted:^{
|
||||
NSLog(@"Completed state %i", (int)installerState);
|
||||
}]
|
||||
then:^{
|
||||
return [RACSignal return:@(nextState)];
|
||||
}];
|
||||
ignoreValues]
|
||||
concat:[RACSignal return:@(nextState)]];
|
||||
}];
|
||||
|
||||
return [[self
|
||||
@@ -365,11 +354,9 @@ typedef struct {
|
||||
// failure. Try recovering it if it did.
|
||||
return [[self
|
||||
verifyBundleAtURL:targetBundleURL usingSignature:codeSignature recoveringUsingBackupAtURL:backupBundleURL]
|
||||
then:^{
|
||||
// Recovery succeeded, but we still want to pass
|
||||
// through the original error.
|
||||
return [RACSignal error:error];
|
||||
}];
|
||||
// Recovery succeeded, but we still want to pass
|
||||
// through the original error.
|
||||
concat:[RACSignal error:error]];
|
||||
}];
|
||||
}]
|
||||
flatten]
|
||||
@@ -387,11 +374,9 @@ typedef struct {
|
||||
] reduce:^(NSURL *targetBundleURL, NSURL *backupBundleURL, SQRLCodeSignature *codeSignature) {
|
||||
return [[self
|
||||
verifyBundleAtURL:targetBundleURL usingSignature:codeSignature recoveringUsingBackupAtURL:backupBundleURL]
|
||||
then:^{
|
||||
return [[self
|
||||
deleteBackupAtURL:backupBundleURL]
|
||||
catchTo:[RACSignal empty]];
|
||||
}];
|
||||
concat:[[self
|
||||
deleteBackupAtURL:backupBundleURL]
|
||||
catchTo:[RACSignal empty]]];
|
||||
}]
|
||||
flatten]
|
||||
setNameWithFormat:@"%@ -verifyInPlaceWithState: %@", self, state];
|
||||
@@ -438,26 +423,23 @@ typedef struct {
|
||||
- (RACSignal *)deleteBackupAtURL:(NSURL *)backupURL {
|
||||
NSParameterAssert(backupURL != nil);
|
||||
|
||||
return [[[RACSignal
|
||||
return [[RACSignal
|
||||
defer:^{
|
||||
NSError *error = nil;
|
||||
if ([NSFileManager.defaultManager removeItemAtURL:backupURL error:&error]) {
|
||||
return [RACSignal empty];
|
||||
} else {
|
||||
if (![NSFileManager.defaultManager removeItemAtURL:backupURL error:&error]) {
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
}]
|
||||
then:^{
|
||||
|
||||
// Also remove the temporary directory that the backup lived in.
|
||||
NSURL *temporaryDirectoryURL = backupURL.URLByDeletingLastPathComponent;
|
||||
|
||||
// However, use rmdir() to skip it in case there are other files
|
||||
// contained within (for whatever reason).
|
||||
if (rmdir(temporaryDirectoryURL.path.fileSystemRepresentation) == 0) {
|
||||
return [RACSignal empty];
|
||||
} else {
|
||||
if (rmdir(temporaryDirectoryURL.path.fileSystemRepresentation) != 0) {
|
||||
return [RACSignal error:[NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]];
|
||||
}
|
||||
|
||||
return [RACSignal empty];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -deleteBackupAtURL: %@", self, backupURL];
|
||||
}
|
||||
@@ -471,10 +453,11 @@ typedef struct {
|
||||
catch:^(NSError *error) {
|
||||
if (backupBundleURL == nil) return [RACSignal error:error];
|
||||
|
||||
return [[[[self
|
||||
installItemAtURL:bundleURL fromURL:backupBundleURL]
|
||||
initially:^{
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
[NSFileManager.defaultManager removeItemAtURL:bundleURL error:NULL];
|
||||
|
||||
return [self installItemAtURL:bundleURL fromURL:backupBundleURL];
|
||||
}]
|
||||
doCompleted:^{
|
||||
NSLog(@"Restored backup bundle to %@", bundleURL);
|
||||
@@ -495,9 +478,7 @@ typedef struct {
|
||||
|
||||
return [[[[self
|
||||
verifyBundleAtURL:targetURL usingSignature:signature recoveringUsingBackupAtURL:nil]
|
||||
then:^{
|
||||
return [RACSignal return:@YES];
|
||||
}]
|
||||
concat:[RACSignal return:@YES]]
|
||||
catch:^(NSError *error) {
|
||||
BOOL directory;
|
||||
if ([NSFileManager.defaultManager fileExistsAtPath:sourceURL.path isDirectory:&directory]) {
|
||||
@@ -563,7 +544,7 @@ typedef struct {
|
||||
return YES;
|
||||
}];
|
||||
|
||||
return enumerator.rac_sequence.signal;
|
||||
return enumerator.allObjects.rac_signal;
|
||||
}]
|
||||
flattenMap:^(NSURL *URL) {
|
||||
const char *path = URL.path.fileSystemRepresentation;
|
||||
|
||||
@@ -72,7 +72,7 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
|
||||
|
||||
+ (RACSignal *)shipItAuthorization {
|
||||
return [[RACSignal
|
||||
createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
|
||||
create:^(id<RACSubscriber> subscriber) {
|
||||
AuthorizationItem rightItems[] = {
|
||||
{
|
||||
.name = kSMRightModifySystemDaemons,
|
||||
@@ -116,8 +116,6 @@ const NSInteger SQRLShipItLauncherErrorCouldNotStartService = 1;
|
||||
} else {
|
||||
[subscriber sendError:[NSError errorWithDomain:NSOSStatusErrorDomain code:authorizationError userInfo:nil]];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}]
|
||||
setNameWithFormat:@"+shipItAuthorization"];
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
return [[[[RACSignal
|
||||
defer:^{
|
||||
NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:self.bundleIdentifier];
|
||||
return [apps.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler];
|
||||
return apps.rac_signal;
|
||||
}]
|
||||
filter:^(NSRunningApplication *application) {
|
||||
return [application.bundleURL.URLByStandardizingPath isEqual:self.bundleURL];
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
- (RACSignal *)waitForTerminationOfProcessIdentifier:(pid_t)processIdentifier {
|
||||
return [[RACSignal
|
||||
createSignal:^(id<RACSubscriber> subscriber) {
|
||||
create:^(id<RACSubscriber> subscriber) {
|
||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, processIdentifier, DISPATCH_PROC_EXIT, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
|
||||
|
||||
dispatch_source_set_registration_handler(source, ^{
|
||||
@@ -72,10 +72,10 @@
|
||||
});
|
||||
|
||||
dispatch_resume(source);
|
||||
return [RACDisposable disposableWithBlock:^{
|
||||
[subscriber.disposable addDisposable:[RACDisposable disposableWithBlock:^{
|
||||
dispatch_source_cancel(source);
|
||||
dispatch_release(source);
|
||||
}];
|
||||
}]];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -waitForTerminationOfProcessIdentifier: %i", self, (int)processIdentifier];
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ extern NSString * const SQRLUpdaterServerDataErrorKey;
|
||||
// error with code `SQRLUpdaterErrorInvalidJSON` is generated.
|
||||
extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
|
||||
@class RACCommand;
|
||||
@class RACAction;
|
||||
@class RACDisposable;
|
||||
@class RACSignal;
|
||||
|
||||
@@ -52,14 +52,12 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
|
||||
// Kicks off a check for updates.
|
||||
//
|
||||
// If an update is available, it will be sent on `updates` once downloaded.
|
||||
@property (nonatomic, strong, readonly) RACCommand *checkForUpdatesCommand;
|
||||
// If an update is available, it will be sent on
|
||||
// `[checkForUpdatesAction deferred]` and `updates` once downloaded.
|
||||
@property (nonatomic, strong, readonly) RACAction *checkForUpdatesAction;
|
||||
|
||||
// Sends an `SQRLDownloadedUpdate` object on the main thread whenever a new
|
||||
// update is available.
|
||||
//
|
||||
// This signal is actually just `checkForUpdatesCommand.executionSignals`,
|
||||
// flattened for convenience.
|
||||
// update is available. Completes when the receiver deallocates.
|
||||
@property (nonatomic, strong, readonly) RACSignal *updates;
|
||||
|
||||
// The request that will be sent to check for updates.
|
||||
@@ -106,8 +104,8 @@ extern NSString * const SQRLUpdaterJSONObjectErrorKey;
|
||||
// relaunch. Otherwise, you can simply use `-[NSApplication terminate:]` or any
|
||||
// other exit mechanism.
|
||||
//
|
||||
// After invoking this method, the receiver is responsible for terminating the
|
||||
// application upon success. The app must not be terminated in any other way
|
||||
// After subscribing to this signal, the receiver is responsible for terminating
|
||||
// the application upon success. The app must not be terminated in any other way
|
||||
// unless an error occurs.
|
||||
//
|
||||
// Returns a signal that will error on the main scheduler if anything goes
|
||||
|
||||
+123
-94
@@ -136,14 +136,15 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
|
||||
@end
|
||||
|
||||
@implementation SQRLUpdater
|
||||
@implementation SQRLUpdater {
|
||||
RACSubject *_updates;
|
||||
}
|
||||
|
||||
#pragma mark Properties
|
||||
|
||||
- (RACSignal *)updates {
|
||||
return [[self.checkForUpdatesCommand.executionSignals
|
||||
concat]
|
||||
setNameWithFormat:@"%@ -updates", self];
|
||||
return [_updates
|
||||
deliverOn:RACScheduler.mainThreadScheduler];
|
||||
}
|
||||
|
||||
#pragma mark Lifecycle
|
||||
@@ -174,10 +175,9 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
#endif
|
||||
}
|
||||
|
||||
BOOL updatesDisabled = (getenv("DISABLE_UPDATE_CHECK") != NULL);
|
||||
@weakify(self);
|
||||
|
||||
_prunedUpdateDirectories = [[[[RACSignal
|
||||
_prunedUpdateDirectories = [[[[[[[[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager applicationSupportURL];
|
||||
@@ -189,7 +189,7 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
return YES;
|
||||
}];
|
||||
|
||||
return [[enumerator.rac_sequence.signal
|
||||
return [[enumerator.allObjects.rac_signal
|
||||
filter:^(NSURL *enumeratedURL) {
|
||||
NSString *name = enumeratedURL.lastPathComponent;
|
||||
return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
|
||||
@@ -201,57 +201,32 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
}
|
||||
}];
|
||||
}]
|
||||
replayLazily]
|
||||
collect]
|
||||
// These operators ensure that the actual work only executes once,
|
||||
// without starting it immediately.
|
||||
concat:[RACSignal never]]
|
||||
shareWhileActive]
|
||||
take:1]
|
||||
flattenMap:^(NSArray *URLs) {
|
||||
return URLs.rac_signal;
|
||||
}]
|
||||
setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
|
||||
_checkForUpdatesCommand = [[RACCommand alloc] initWithEnabled:[RACSignal return:@(!updatesDisabled)] signalBlock:^(id _) {
|
||||
@strongify(self);
|
||||
NSParameterAssert(self.updateRequest != nil);
|
||||
_checkForUpdatesAction = [[[RACSignal
|
||||
defer:^{
|
||||
@strongify(self);
|
||||
return [RACSignal return:self.updateRequest];
|
||||
}]
|
||||
flattenMap:^(NSURLRequest *request) {
|
||||
return [self checkForUpdates:request];
|
||||
}]
|
||||
action];
|
||||
|
||||
// TODO: Maybe allow this to be an argument to the command?
|
||||
NSMutableURLRequest *request = [self.updateRequest mutableCopy];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
|
||||
_updates = [[RACSubject
|
||||
subject]
|
||||
setNameWithFormat:@"%@ updates", self];
|
||||
|
||||
// Prune old updates before the first update check.
|
||||
return [[[[[[[self.prunedUpdateDirectories
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error pruning old updates: %@", error);
|
||||
return [RACSignal empty];
|
||||
}]
|
||||
then:^{
|
||||
return [NSURLConnection rac_sendAsynchronousRequest:request];
|
||||
}]
|
||||
reduceEach:^(NSURLResponse *response, NSData *bodyData) {
|
||||
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
|
||||
NSHTTPURLResponse *httpResponse = (id)response;
|
||||
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
|
||||
NSDictionary *errorInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Update check failed", nil),
|
||||
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The server sent an invalid response. Try again later.", nil),
|
||||
SQRLUpdaterServerDataErrorKey: bodyData,
|
||||
};
|
||||
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
|
||||
if (httpResponse.statusCode == 204 /* No Content */) {
|
||||
return [RACSignal empty];
|
||||
}
|
||||
}
|
||||
|
||||
return [RACSignal return:bodyData];
|
||||
}]
|
||||
flatten]
|
||||
flattenMap:^(NSData *data) {
|
||||
return [self updateFromJSONData:data];
|
||||
}]
|
||||
flattenMap:^(SQRLUpdate *update) {
|
||||
return [self downloadAndPrepareUpdate:update];
|
||||
}]
|
||||
deliverOn:RACScheduler.mainThreadScheduler];
|
||||
}];
|
||||
|
||||
_shipItLauncher = [[[RACSignal
|
||||
_shipItLauncher = [[[[[[[RACSignal
|
||||
defer:^{
|
||||
NSURL *targetURL = NSRunningApplication.currentApplication.bundleURL;
|
||||
|
||||
@@ -263,31 +238,90 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
// wait for another, more canonical error.
|
||||
return [SQRLShipItLauncher launchPrivileged:(gotWritable && !targetWritable.boolValue)];
|
||||
}]
|
||||
replayLazily]
|
||||
// These operators ensure that the actual work only executes once,
|
||||
// without starting it immediately. Super hacky.
|
||||
concat:[RACSignal return:RACUnit.defaultUnit]]
|
||||
concat:[RACSignal never]]
|
||||
shareWhileActive]
|
||||
take:1]
|
||||
ignoreValues]
|
||||
setNameWithFormat:@"shipItLauncher"];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[_updates sendCompleted];
|
||||
}
|
||||
|
||||
#pragma mark Checking for Updates
|
||||
|
||||
- (RACDisposable *)startAutomaticChecksWithInterval:(NSTimeInterval)interval {
|
||||
@weakify(self);
|
||||
|
||||
return [[[[[RACSignal
|
||||
return [[[[RACSignal
|
||||
interval:interval onScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground]]
|
||||
flattenMap:^(id _) {
|
||||
@strongify(self);
|
||||
return [[self.checkForUpdatesCommand
|
||||
execute:RACUnit.defaultUnit]
|
||||
|
||||
return [[[self.checkForUpdatesAction
|
||||
signalWithValue:nil]
|
||||
ignoreValues]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error checking for updates: %@", error);
|
||||
return [RACSignal empty];
|
||||
}];
|
||||
}]
|
||||
takeUntil:self.rac_willDeallocSignal]
|
||||
publish]
|
||||
connect];
|
||||
subscribeCompleted:^{}];
|
||||
}
|
||||
|
||||
- (RACSignal *)checkForUpdates:(NSURLRequest *)request {
|
||||
NSParameterAssert(request != nil);
|
||||
|
||||
BOOL updatesDisabled = (getenv("DISABLE_UPDATE_CHECK") != NULL);
|
||||
if (updatesDisabled) return [RACSignal empty];
|
||||
|
||||
NSMutableURLRequest *newRequest = [request mutableCopy];
|
||||
[newRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
|
||||
|
||||
return [[[[[[[[self.prunedUpdateDirectories
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error pruning old updates: %@", error);
|
||||
return [RACSignal empty];
|
||||
}]
|
||||
ignoreValues]
|
||||
concat:[NSURLConnection rac_sendAsynchronousRequest:newRequest]]
|
||||
reduceEach:^(NSURLResponse *response, NSData *bodyData) {
|
||||
if ([response isKindOfClass:NSHTTPURLResponse.class]) {
|
||||
NSHTTPURLResponse *httpResponse = (id)response;
|
||||
if (!(httpResponse.statusCode >= 200 && httpResponse.statusCode <= 299)) {
|
||||
NSDictionary *errorInfo = @{
|
||||
NSLocalizedDescriptionKey: NSLocalizedString(@"Update check failed", nil),
|
||||
NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"The server sent an invalid response. Try again later.", nil),
|
||||
SQRLUpdaterServerDataErrorKey: bodyData,
|
||||
};
|
||||
NSError *error = [NSError errorWithDomain:SQRLUpdaterErrorDomain code:SQRLUpdaterErrorInvalidServerResponse userInfo:errorInfo];
|
||||
return [RACSignal error:error];
|
||||
}
|
||||
|
||||
if (httpResponse.statusCode == 204 /* No Content */) {
|
||||
return [RACSignal empty];
|
||||
}
|
||||
}
|
||||
|
||||
return [RACSignal return:bodyData];
|
||||
}]
|
||||
flatten]
|
||||
flattenMap:^(NSData *data) {
|
||||
return [self updateFromJSONData:data];
|
||||
}]
|
||||
flattenMap:^(SQRLUpdate *update) {
|
||||
return [self downloadAndPrepareUpdate:update];
|
||||
}]
|
||||
doNext:^(SQRLDownloadedUpdate *update) {
|
||||
[self->_updates sendNext:update];
|
||||
}];
|
||||
}
|
||||
|
||||
- (RACSignal *)updateFromJSONData:(NSData *)data {
|
||||
@@ -403,9 +437,7 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
}
|
||||
}];
|
||||
}]
|
||||
then:^{
|
||||
return [self updateBundleMatchingCurrentApplicationInDirectory:downloadDirectory];
|
||||
}]
|
||||
concat:[self updateBundleMatchingCurrentApplicationInDirectory:downloadDirectory]]
|
||||
setNameWithFormat:@"%@ -downloadBundleForUpdate: %@ intoDirectory: %@", self, update, downloadDirectory];
|
||||
}
|
||||
|
||||
@@ -451,25 +483,27 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
NSLog(@"Error enumerating item %@ within directory %@: %@", URL, directory, error);
|
||||
return YES;
|
||||
}];
|
||||
|
||||
NSURL *updateBundleURL = [enumerator.rac_sequence objectPassingTest:^(NSURL *URL) {
|
||||
NSString *type = nil;
|
||||
NSError *error = nil;
|
||||
if (![URL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:&error]) {
|
||||
NSLog(@"Error retrieving UTI for item at %@: %@", URL, error);
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (!UTTypeConformsTo((__bridge CFStringRef)type, kUTTypeApplicationBundle)) return NO;
|
||||
NSURL *updateBundleURL = [[enumerator.allObjects.rac_signal
|
||||
filter:^(NSURL *URL) {
|
||||
NSString *type = nil;
|
||||
NSError *error = nil;
|
||||
if (![URL getResourceValue:&type forKey:NSURLTypeIdentifierKey error:&error]) {
|
||||
NSLog(@"Error retrieving UTI for item at %@: %@", URL, error);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSBundle *bundle = [NSBundle bundleWithURL:URL];
|
||||
if (bundle == nil) {
|
||||
NSLog(@"Could not open application bundle at %@", URL);
|
||||
return NO;
|
||||
}
|
||||
if (!UTTypeConformsTo((__bridge CFStringRef)type, kUTTypeApplicationBundle)) return NO;
|
||||
|
||||
return [bundle.bundleIdentifier isEqual:NSRunningApplication.currentApplication.bundleIdentifier];
|
||||
}];
|
||||
NSBundle *bundle = [NSBundle bundleWithURL:URL];
|
||||
if (bundle == nil) {
|
||||
NSLog(@"Could not open application bundle at %@", URL);
|
||||
return NO;
|
||||
}
|
||||
|
||||
return [bundle.bundleIdentifier isEqual:NSRunningApplication.currentApplication.bundleIdentifier];
|
||||
}]
|
||||
first];
|
||||
|
||||
if (updateBundleURL != nil) {
|
||||
return [RACSignal return:updateBundleURL];
|
||||
@@ -504,14 +538,11 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
|
||||
return [[[[self.signature
|
||||
verifyBundleAtURL:updateBundle.bundleURL]
|
||||
then:^{
|
||||
SQRLDownloadedUpdate *downloadedUpdate = [[SQRLDownloadedUpdate alloc] initWithUpdate:update bundle:updateBundle];
|
||||
return [RACSignal return:downloadedUpdate];
|
||||
}]
|
||||
concat:[RACSignal return:[[SQRLDownloadedUpdate alloc] initWithUpdate:update bundle:updateBundle]]]
|
||||
flattenMap:^(SQRLDownloadedUpdate *downloadedUpdate) {
|
||||
return [[self prepareUpdateForInstallation:downloadedUpdate] then:^{
|
||||
return [RACSignal return:downloadedUpdate];
|
||||
}];
|
||||
return [[self
|
||||
prepareUpdateForInstallation:downloadedUpdate]
|
||||
concat:[RACSignal return:downloadedUpdate]];
|
||||
}]
|
||||
setNameWithFormat:@"%@ -verifyAndPrepareUpdate: %@ fromBundle: %@", self, update, updateBundle];
|
||||
}
|
||||
@@ -538,25 +569,24 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
- (RACSignal *)prepareUpdateForInstallation:(SQRLDownloadedUpdate *)update {
|
||||
NSParameterAssert(update != nil);
|
||||
|
||||
return [[[[[[[SQRLShipItState
|
||||
return [[[[[[[[SQRLShipItState
|
||||
readUsingURL:self.shipItStateURL]
|
||||
catchTo:[RACSignal empty]]
|
||||
flattenMap:^(SQRLShipItState *existingState) {
|
||||
return [self validateExistingState:existingState];
|
||||
}]
|
||||
then:^{
|
||||
ignoreValues]
|
||||
concat:[RACSignal defer:^{
|
||||
SQRLShipItState *state = [[SQRLShipItState alloc] initWithTargetBundleURL:NSRunningApplication.currentApplication.bundleURL updateBundleURL:update.bundle.bundleURL bundleIdentifier:NSRunningApplication.currentApplication.bundleIdentifier codeSignature:self.signature];
|
||||
return [state writeUsingURL:self.shipItStateURL];
|
||||
}]
|
||||
then:^{
|
||||
return self.shipItLauncher;
|
||||
}]
|
||||
}]]
|
||||
concat:self.shipItLauncher]
|
||||
sqrl_addTransactionWithName:NSLocalizedString(@"Preparing update", nil) description:NSLocalizedString(@"An update for %@ is being prepared. Interrupting the process could corrupt the application.", nil), NSRunningApplication.currentApplication.bundleIdentifier]
|
||||
setNameWithFormat:@"%@ -prepareUpdateForInstallation: %@", self, update];
|
||||
}
|
||||
|
||||
- (RACSignal *)relaunchToInstallUpdate {
|
||||
return [[[[[[[[SQRLShipItState
|
||||
return [[[[[[[SQRLShipItState
|
||||
readUsingURL:self.shipItStateURL]
|
||||
flattenMap:^(SQRLShipItState *existingState) {
|
||||
return [self validateExistingState:existingState];
|
||||
@@ -574,7 +604,6 @@ static NSString * const SQRLUpdaterUniqueTemporaryDirectoryPrefix = @"update.";
|
||||
// Never allow `completed` to escape this signal chain (in case
|
||||
// -terminate: is asynchronous or something crazy).
|
||||
concat:[RACSignal never]]
|
||||
replay]
|
||||
setNameWithFormat:@"%@ -relaunchToInstallUpdate", self];
|
||||
}
|
||||
|
||||
|
||||
+12
-14
@@ -130,25 +130,23 @@ const NSInteger SQRLZipArchiverShellTaskFailed = 1;
|
||||
// This is important because the signals on `self` complete upon
|
||||
// dealloc.
|
||||
return:self]
|
||||
then:^{
|
||||
return [RACSignal
|
||||
zip:@[ self.taskTerminated, self.standardErrorData ]
|
||||
reduce:^(NSNumber *exitStatus, NSData *errorData) {
|
||||
if (exitStatus.intValue == 0) return [RACSignal empty];
|
||||
ignoreValues]
|
||||
concat:[RACSignal
|
||||
zip:@[ self.taskTerminated, self.standardErrorData ]
|
||||
reduce:^(NSNumber *exitStatus, NSData *errorData) {
|
||||
if (exitStatus.intValue == 0) return [RACSignal empty];
|
||||
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[SQRLZipArchiverExitCodeErrorKey] = exitStatus;
|
||||
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
userInfo[SQRLZipArchiverExitCodeErrorKey] = exitStatus;
|
||||
|
||||
NSString *errorString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];
|
||||
errorString = [errorString stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
||||
if (errorString.length > 0) userInfo[NSLocalizedDescriptionKey] = errorString;
|
||||
NSString *errorString = [[NSString alloc] initWithData:errorData encoding:NSUTF8StringEncoding];
|
||||
errorString = [errorString stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet];
|
||||
if (errorString.length > 0) userInfo[NSLocalizedDescriptionKey] = errorString;
|
||||
|
||||
return [RACSignal error:[NSError errorWithDomain:SQRLZipArchiverErrorDomain code:SQRLZipArchiverShellTaskFailed userInfo:userInfo]];
|
||||
}];
|
||||
}]
|
||||
return [RACSignal error:[NSError errorWithDomain:SQRLZipArchiverErrorDomain code:SQRLZipArchiverShellTaskFailed userInfo:userInfo]];
|
||||
}]]
|
||||
take:1]
|
||||
flatten]
|
||||
replay]
|
||||
setNameWithFormat:@"-launchWithArguments: %@", arguments];
|
||||
|
||||
self.dittoTask.arguments = arguments;
|
||||
|
||||
+15
-16
@@ -55,16 +55,15 @@ int main(int argc, const char * argv[]) {
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:@(jobLabel)];
|
||||
RACSignal *stateLocation = directoryManager.shipItStateURL;
|
||||
|
||||
[[[[[[SQRLShipItState
|
||||
[[[[[[[SQRLShipItState
|
||||
readUsingURL:stateLocation]
|
||||
flattenMap:^(SQRLShipItState *state) {
|
||||
return waitForTerminationIfNecessary(state);
|
||||
}]
|
||||
then:^{
|
||||
// Read the latest state, in case it was modified by the
|
||||
// controlling application in the meantime.
|
||||
return [SQRLShipItState readUsingURL:stateLocation];
|
||||
}]
|
||||
ignoreValues]
|
||||
// Read the latest state, in case it was modified by the
|
||||
// controlling application in the meantime.
|
||||
concat:[SQRLShipItState readUsingURL:stateLocation]]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error reading saved installer state: %@", error);
|
||||
|
||||
@@ -79,10 +78,11 @@ int main(int argc, const char * argv[]) {
|
||||
RACSignal *action;
|
||||
|
||||
if (attempt > SQRLShipItMaximumInstallationAttempts) {
|
||||
action = [[[installer.abortInstallationCommand
|
||||
execute:state]
|
||||
initially:^{
|
||||
action = [[RACSignal
|
||||
defer:^{
|
||||
NSLog(@"Too many attempts to install from state %i, aborting update", (int)state.installerState);
|
||||
|
||||
return [installer abortInstallationWithState:state];
|
||||
}]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error aborting installation: %@", error);
|
||||
@@ -91,9 +91,8 @@ int main(int argc, const char * argv[]) {
|
||||
return [RACSignal empty];
|
||||
}];
|
||||
} else {
|
||||
action = [[[[[state
|
||||
writeUsingURL:stateLocation]
|
||||
initially:^{
|
||||
action = [[[[RACSignal
|
||||
defer:^{
|
||||
if (freshInstall) {
|
||||
NSLog(@"Beginning installation");
|
||||
state.installerState = SQRLInstallerStateClearingQuarantine;
|
||||
@@ -102,10 +101,10 @@ int main(int argc, const char * argv[]) {
|
||||
}
|
||||
|
||||
state.installationStateAttempt = attempt;
|
||||
|
||||
return [state writeUsingURL:stateLocation];
|
||||
}]
|
||||
then:^{
|
||||
return [installer.installUpdateCommand execute:state];
|
||||
}]
|
||||
concat:[installer installUpdateWithState:state]]
|
||||
doCompleted:^{
|
||||
NSLog(@"Installation completed successfully");
|
||||
}]
|
||||
@@ -117,7 +116,7 @@ int main(int argc, const char * argv[]) {
|
||||
// fails.
|
||||
action = [[action
|
||||
deliverOn:RACScheduler.mainThreadScheduler]
|
||||
finally:^{
|
||||
doFinished:^{
|
||||
NSURL *bundleURL = state.targetBundleURL;
|
||||
if (bundleURL == nil) {
|
||||
NSLog(@"Missing target bundle URL, cannot relaunch application");
|
||||
|
||||
@@ -77,7 +77,7 @@ it(@"should install an update in process", ^{
|
||||
expect(installer).notTo.beNil();
|
||||
|
||||
NSError *installError = nil;
|
||||
BOOL install = [[installer.installUpdateCommand execute:state] asynchronouslyWaitUntilCompleted:&installError];
|
||||
BOOL install = [[installer installUpdateWithState:state] asynchronouslyWaitUntilCompleted:&installError];
|
||||
expect(install).to.beTruthy();
|
||||
expect(installError).to.beNil();
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ static void SQRLSignalHandler(int sig) {
|
||||
|
||||
// Returns an _unlaunched_ task that will follow log files at the given paths,
|
||||
// then pipe that output through this process.
|
||||
+ (NSTask *)tailTaskWithPaths:(RACSequence *)paths;
|
||||
+ (NSTask *)tailTaskWithPaths:(RACSignal *)paths;
|
||||
|
||||
@end
|
||||
|
||||
@@ -75,23 +75,24 @@ static void SQRLSignalHandler(int sig) {
|
||||
@"otest-x86_64.ShipIt",
|
||||
];
|
||||
|
||||
RACSequence *URLs = [folders.rac_sequence flattenMap:^(NSString *folder) {
|
||||
NSURL *baseURL = [appSupportURL URLByAppendingPathComponent:folder];
|
||||
return @[
|
||||
[baseURL URLByAppendingPathComponent:@"ShipIt_stdout.log"],
|
||||
[baseURL URLByAppendingPathComponent:@"ShipIt_stderr.log"]
|
||||
].rac_sequence;
|
||||
}];
|
||||
RACSignal *logLocations = [[[[folders.rac_signal
|
||||
flattenMap:^(NSString *folder) {
|
||||
return [folder stringsByAppendingPaths:@[
|
||||
@"ShipIt_stdout.log",
|
||||
@"ShipIt_stderr.log",
|
||||
]].rac_signal;
|
||||
}]
|
||||
map:^(NSString *path) {
|
||||
return [appSupportURL URLByAppendingPathComponent:path];
|
||||
}]
|
||||
doNext:^(NSURL *location) {
|
||||
[[NSData data] writeToURL:location atomically:YES];
|
||||
}]
|
||||
map:^(NSURL *location) {
|
||||
return location.path;
|
||||
}];
|
||||
|
||||
for (NSURL *URL in URLs) {
|
||||
[[NSData data] writeToURL:URL atomically:YES];
|
||||
}
|
||||
|
||||
RACSequence *paths = [URLs map:^(NSURL *URL) {
|
||||
return URL.path;
|
||||
}];
|
||||
|
||||
NSTask *readShipIt = [self tailTaskWithPaths:paths];
|
||||
NSTask *readShipIt = [self tailTaskWithPaths:logLocations];
|
||||
[readShipIt launch];
|
||||
|
||||
NSAssert([readShipIt isRunning], @"Could not start task %@", readShipIt);
|
||||
@@ -136,7 +137,7 @@ static void SQRLSignalHandler(int sig) {
|
||||
|
||||
#pragma mark Logging
|
||||
|
||||
+ (NSTask *)tailTaskWithPaths:(RACSequence *)paths {
|
||||
+ (NSTask *)tailTaskWithPaths:(RACSignal *)paths {
|
||||
NSPipe *outputPipe = [NSPipe pipe];
|
||||
NSFileHandle *outputHandle = outputPipe.fileHandleForReading;
|
||||
|
||||
@@ -197,7 +198,7 @@ static void SQRLSignalHandler(int sig) {
|
||||
NSURL *testAppLog = [fixtureURL.URLByDeletingLastPathComponent URLByAppendingPathComponent:@"TestApplication.log"];
|
||||
[[NSData data] writeToURL:testAppLog atomically:YES];
|
||||
|
||||
NSTask *readTestApp = [self.class tailTaskWithPaths:[RACSequence return:testAppLog.path]];
|
||||
NSTask *readTestApp = [self.class tailTaskWithPaths:[RACSignal return:testAppLog.path]];
|
||||
[readTestApp launch];
|
||||
|
||||
STAssertTrue([readTestApp isRunning], @"Could not start task %@ to read %@", readTestApp, testAppLog);
|
||||
|
||||
@@ -154,7 +154,7 @@ describe(@"updating", ^{
|
||||
NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:appSupportURL includingPropertiesForKeys:nil options:0 error:NULL];
|
||||
if (contents == nil) return [RACSignal empty];
|
||||
|
||||
return contents.rac_sequence.signal;
|
||||
return contents.rac_signal;
|
||||
}]
|
||||
filter:^(NSURL *directoryURL) {
|
||||
return [directoryURL.lastPathComponent hasPrefix:@"update."];
|
||||
@@ -179,7 +179,7 @@ describe(@"updating", ^{
|
||||
|
||||
// Also test that this is the only update directory (any others
|
||||
// should've been removed).
|
||||
NSArray *directoryURLs = [updateDirectoryURLs toArray];
|
||||
NSArray *directoryURLs = [updateDirectoryURLs array];
|
||||
expect(directoryURLs.count).to.equal(1);
|
||||
|
||||
NSArray *contents = [NSFileManager.defaultManager contentsOfDirectoryAtURL:directoryURLs[0] includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsHiddenFiles error:NULL];
|
||||
@@ -208,7 +208,7 @@ describe(@"response handling", ^{
|
||||
}];
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL result = [[updater.checkForUpdatesCommand execute:nil] asynchronouslyWaitUntilCompleted:&error];
|
||||
BOOL result = [[updater.checkForUpdatesAction signalWithValue:nil] asynchronouslyWaitUntilCompleted:&error];
|
||||
expect(result).to.beFalsy();
|
||||
expect(error.domain).to.equal(SQRLUpdaterErrorDomain);
|
||||
expect(error.code).to.equal(SQRLUpdaterErrorInvalidServerResponse);
|
||||
@@ -225,7 +225,7 @@ describe(@"response handling", ^{
|
||||
}];
|
||||
|
||||
NSError *error = nil;
|
||||
BOOL result = [[updater.checkForUpdatesCommand execute:nil] asynchronouslyWaitUntilCompleted:&error];
|
||||
BOOL result = [[updater.checkForUpdatesAction signalWithValue:nil] asynchronouslyWaitUntilCompleted:&error];
|
||||
expect(result).to.beFalsy();
|
||||
expect(error.domain).to.equal(SQRLUpdaterErrorDomain);
|
||||
expect(error.code).to.equal(SQRLUpdaterErrorInvalidServerBody);
|
||||
|
||||
@@ -70,18 +70,18 @@
|
||||
NSLog(@"***** UPDATE CHECK %lu *****", (unsigned long)updateCheckCount);
|
||||
updateCheckCount++;
|
||||
|
||||
return [self.updater.checkForUpdatesCommand execute:RACUnit.defaultUnit];
|
||||
return [self.updater.checkForUpdatesAction signalWithValue:nil];
|
||||
}]
|
||||
doNext:^(SQRLDownloadedUpdate *update) {
|
||||
NSLog(@"Got a candidate update: %@", update);
|
||||
}]
|
||||
// Retry until we get the expected release.
|
||||
repeat]
|
||||
skipUntilBlock:^(SQRLDownloadedUpdate *download) {
|
||||
skipWhile:^ BOOL (SQRLDownloadedUpdate *download) {
|
||||
SQRLTestUpdate *testUpdate = (id)download.update;
|
||||
NSAssert([testUpdate isKindOfClass:SQRLTestUpdate.class], @"Unexpected update type: %@", testUpdate);
|
||||
|
||||
return testUpdate.final;
|
||||
return !testUpdate.final;
|
||||
}]
|
||||
take:1]
|
||||
doNext:^(id _) {
|
||||
@@ -92,12 +92,12 @@
|
||||
NSLog(@"Error in updater: %@", error);
|
||||
return [RACSignal empty];
|
||||
}]
|
||||
then:^{
|
||||
concat:[RACSignal defer:^{
|
||||
NSString *delayString = NSProcessInfo.processInfo.environment[@"SQRLUpdateDelay"];
|
||||
if (delayString == nil) return [RACSignal empty];
|
||||
|
||||
return [[RACSignal interval:delayString.doubleValue onScheduler:RACScheduler.mainThreadScheduler] take:1];
|
||||
}]
|
||||
}]]
|
||||
subscribeCompleted:^{
|
||||
[NSApp terminate:self];
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user