Cog/Frameworks/Sparkle/Sparkle/Autoupdate/Autoupdate.m
2014-08-01 03:53:07 -07:00

223 lines
7.3 KiB
Objective-C

#import <AppKit/AppKit.h>
#import "SUInstaller.h"
#import "SUHost.h"
#import "SUStandardVersionComparator.h"
#import "SUStatusController.h"
#import "SUPlainInstallerInternals.h"
#import "SULog.h"
#include <unistd.h>
/*!
* If the Installation takes longer than this time the Application Icon is shown in the Dock so that the user has some feedback.
*/
static const NSTimeInterval SUInstallationTimeLimit = 5;
/*!
* Time this app uses to recheck if the parent has already died.
*/
static const NSTimeInterval SUParentQuitCheckInterval = .25;
@interface TerminationListener : NSObject <SUInstallerDelegate>
@property (assign) const char *hostpath;
@property (assign) const char *executablepath;
@property (assign) pid_t parentprocessid;
@property (assign) const char *folderpath;
@property (copy) NSString *selfPath;
@property (copy) NSString *installationPath;
@property (strong) NSTimer *watchdogTimer;
@property (strong) NSTimer *longInstallationTimer;
@property (strong) SUHost *host;
@property (assign) BOOL shouldRelaunch;
@property (assign) BOOL shouldShowUI;
- (void)parentHasQuit;
- (void)relaunch;
- (void)install;
- (void)showAppIconInDock:(NSTimer *)aTimer;
- (void)watchdog:(NSTimer *)aTimer;
@end
@implementation TerminationListener
@synthesize hostpath;
@synthesize executablepath;
@synthesize parentprocessid;
@synthesize folderpath;
@synthesize selfPath;
@synthesize installationPath;
@synthesize watchdogTimer;
@synthesize longInstallationTimer;
@synthesize host;
@synthesize shouldRelaunch;
@synthesize shouldShowUI;
- (instancetype)initWithHostPath:(const char *)inhostpath executablePath:(const char *)execpath parentProcessId:(pid_t)ppid folderPath:(const char *)infolderpath shouldRelaunch:(BOOL)relaunch shouldShowUI:(BOOL)showUI selfPath:(NSString *)inSelfPath
{
if (!(self = [super init])) {
return nil;
}
hostpath = inhostpath;
executablepath = execpath;
parentprocessid = ppid;
folderpath = infolderpath;
selfPath = inSelfPath;
shouldRelaunch = relaunch;
shouldShowUI = showUI;
BOOL alreadyTerminated = (getppid() == 1); // ppid is launchd (1) => parent terminated already
if (alreadyTerminated)
[self parentHasQuit];
else
watchdogTimer = [NSTimer scheduledTimerWithTimeInterval:SUParentQuitCheckInterval target:self selector:@selector(watchdog:) userInfo:nil repeats:YES];
return self;
}
- (void)dealloc
{
[longInstallationTimer invalidate];
}
- (void)parentHasQuit
{
[self.watchdogTimer invalidate];
self.longInstallationTimer = [NSTimer scheduledTimerWithTimeInterval:SUInstallationTimeLimit
target: self selector: @selector(showAppIconInDock:)
userInfo:nil repeats:NO];
if (self.folderpath)
[self install];
else
[self relaunch];
}
- (void)watchdog:(NSTimer *)__unused aTimer
{
if (![NSRunningApplication runningApplicationWithProcessIdentifier:self.parentprocessid]) {
[self parentHasQuit];
}
}
- (void)showAppIconInDock:(NSTimer *)__unused aTimer
{
ProcessSerialNumber psn = { 0, kCurrentProcess };
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}
- (void)relaunch __attribute__((noreturn))
{
if (self.shouldRelaunch)
{
NSString *appPath = nil;
if (!self.folderpath || strcmp(self.executablepath, self.hostpath) != 0)
appPath = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:self.executablepath length:strlen(self.executablepath)];
else
appPath = self.installationPath;
[[NSWorkspace sharedWorkspace] openFile:appPath];
}
if (self.folderpath)
{
NSError *theError = nil;
if (![SUPlainInstaller _removeFileAtPath:[SUInstaller updateFolder] error:&theError])
SULog(@"Couldn't remove update folder: %@.", theError);
}
[[NSFileManager defaultManager] removeItemAtPath:self.selfPath error:NULL];
exit(EXIT_SUCCESS);
}
- (void)install
{
NSBundle *theBundle = [NSBundle bundleWithPath:[[NSFileManager defaultManager] stringWithFileSystemRepresentation:self.hostpath length:strlen(self.hostpath)]];
self.host = [[SUHost alloc] initWithBundle:theBundle];
self.installationPath = [[self.host installationPath] copy];
if (self.shouldShowUI) {
SUStatusController *statusCtl = [[SUStatusController alloc] initWithHost:self.host]; // We quit anyway after we've installed, so leak this for now.
[statusCtl setButtonTitle:SULocalizedString(@"Cancel Update", @"") target:nil action:Nil isDefault:NO];
[statusCtl beginActionWithTitle:SULocalizedString(@"Installing update...", @"")
maxProgressValue: 0 statusText: @""];
[statusCtl showWindow:self];
}
[SUInstaller installFromUpdateFolder:[[NSFileManager defaultManager] stringWithFileSystemRepresentation:self.folderpath length:strlen(self.folderpath)]
overHost:self.host
installationPath:self.installationPath
delegate:self
versionComparator:[SUStandardVersionComparator defaultComparator]];
}
- (void)installerFinishedForHost:(SUHost *)__unused aHost
{
[self relaunch];
}
- (void)installerForHost:(SUHost *)__unused host failedWithError:(NSError *)error __attribute__((noreturn))
{
if (self.shouldShowUI)
NSRunAlertPanel(@"", @"%@", @"OK", @"", @"", [error localizedDescription]);
exit(EXIT_FAILURE);
}
@end
int main(int argc, const char *argv[])
{
if (argc < 5 || argc > 7) {
return EXIT_FAILURE;
}
@autoreleasepool {
//ProcessSerialNumber psn = { 0, kCurrentProcess };
//TransformProcessType( &psn, kProcessTransformToForegroundApplication );
#if 0 // Cmdline tool
NSString* selfPath = nil;
if (argv[0][0] == '/') {
selfPath = [[NSFileManager defaultManager] stringWithFileSystemRepresentation: argv[0] length: strlen(argv[0])];
}
else
{
selfPath = [[NSFileManager defaultManager] currentDirectoryPath];
selfPath = [selfPath stringByAppendingPathComponent: [[NSFileManager defaultManager] stringWithFileSystemRepresentation: argv[0] length: strlen(argv[0])]];
}
#else
NSString *selfPath = [[NSBundle mainBundle] bundlePath];
#endif
BOOL shouldShowUI = (argc > 6) ? !!atoi(argv[6]) : YES;
if (shouldShowUI)
{
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
[NSApplication sharedApplication];
TerminationListener *termListen = [[TerminationListener alloc] initWithHostPath:(argc > 1) ? argv[1] : NULL
executablePath:(argc > 2) ? argv[2] : NULL
parentProcessId:(argc > 3) ? atoi(argv[3]) : 0
folderPath:(argc > 4) ? argv[4] : NULL
shouldRelaunch:(argc > 5) ? !!atoi(argv[5]) : YES
shouldShowUI:shouldShowUI
selfPath:selfPath];
[termListen class];
[[NSApplication sharedApplication] run];
}
return EXIT_SUCCESS;
}