mirror of
https://github.com/textmate/textmate.git
synced 2026-01-23 05:37:55 -05:00
237 lines
9.4 KiB
Plaintext
237 lines
9.4 KiB
Plaintext
#import "SoftwareUpdate.h"
|
|
#import "DownloadWindowController.h"
|
|
#import "ReleaseNotesWindowController.h"
|
|
#import "sw_update.h"
|
|
#import <OakAppKit/OakAppKit.h>
|
|
#import <OakAppKit/NSMenu Additions.h>
|
|
#import <OakFoundation/OakFoundation.h>
|
|
#import <OakFoundation/NSDate Additions.h>
|
|
#import <OakFoundation/NSString Additions.h>
|
|
#import <OakSystem/application.h>
|
|
#import <network/network.h>
|
|
#import <ns/ns.h>
|
|
#import <oak/debug.h>
|
|
|
|
OAK_DEBUG_VAR(SoftwareUpdate_Check);
|
|
OAK_DEBUG_VAR(SoftwareUpdate_ReleaseNotes);
|
|
|
|
NSString* const kUserDefaultsDisableSoftwareUpdatesKey = @"SoftwareUpdateDisablePolling";
|
|
NSString* const kUserDefaultsSoftwareUpdateChannelKey = @"SoftwareUpdateChannel"; // release (default), beta, nightly
|
|
NSString* const kUserDefaultsSubmitUsageInfoKey = @"SoftwareUpdateSubmitUsageInfo";
|
|
NSString* const kUserDefaultsAskBeforeUpdatingKey = @"SoftwareUpdateAskBeforeUpdating";
|
|
NSString* const kUserDefaultsLastSoftwareUpdateCheckKey = @"SoftwareUpdateLastPoll";
|
|
NSString* const kUserDefaultsSoftwareUpdateSuspendUntilKey = @"SoftwareUpdateSuspendUntil";
|
|
|
|
NSString* const kSoftwareUpdateChannelRelease = @"release";
|
|
NSString* const kSoftwareUpdateChannelBeta = @"beta";
|
|
NSString* const kSoftwareUpdateChannelNightly = @"nightly";
|
|
|
|
@interface SoftwareUpdate ()
|
|
@property (nonatomic, retain) NSDate* lastPoll;
|
|
@property (nonatomic, assign) BOOL isChecking;
|
|
@property (nonatomic, retain) NSString* errorString;
|
|
@property (nonatomic, retain) NSTimer* pollTimer;
|
|
@property (nonatomic, retain) DownloadWindowController* downloadWindowController;
|
|
- (void)scheduleVersionCheck:(id)sender;
|
|
@end
|
|
|
|
static SoftwareUpdate* SharedInstance;
|
|
|
|
@implementation SoftwareUpdate
|
|
@synthesize channels, isChecking, errorString, pollTimer, downloadWindowController;
|
|
|
|
+ (SoftwareUpdate*)sharedInstance
|
|
{
|
|
return SharedInstance ?: [[self new] autorelease];
|
|
}
|
|
|
|
+ (void)initialize
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] registerDefaults:
|
|
[NSDictionary dictionaryWithObjectsAndKeys:
|
|
kSoftwareUpdateChannelRelease, kUserDefaultsSoftwareUpdateChannelKey,
|
|
nil]];
|
|
}
|
|
|
|
- (void)showReleaseNotes:(id)sender
|
|
{
|
|
D(DBF_SoftwareUpdate_ReleaseNotes, bug("\n"););
|
|
[ReleaseNotesWindowController showPath:[[NSBundle mainBundle] pathForResource:@"ReleaseNotes" ofType:@"html"]];
|
|
}
|
|
|
|
- (void)installReleaseNotesMenuItem:(id)sender
|
|
{
|
|
for(NSMenuItem* item in [[[NSApp mainMenu] itemArray] reverseObjectEnumerator])
|
|
{
|
|
if([[item submenu] indexOfItemWithTarget:nil andAction:@selector(showHelp:)] != -1)
|
|
{
|
|
[[[item submenu] addItemWithTitle:@"Release Notes" action:@selector(showReleaseNotes:) keyEquivalent:@""] setTarget:self];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if(SharedInstance)
|
|
{
|
|
[self release];
|
|
}
|
|
else if(self = SharedInstance = [[super init] retain])
|
|
{
|
|
D(DBF_SoftwareUpdate_Check, bug("\n"););
|
|
pollInterval = 60*60;
|
|
|
|
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(scheduleVersionCheck:) name:NSWorkspaceDidWakeNotification object:[NSWorkspace sharedWorkspace]];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:[NSUserDefaults standardUserDefaults]];
|
|
[ReleaseNotesWindowController showPathIfUpdated:[[NSBundle mainBundle] pathForResource:@"ReleaseNotes" ofType:@"html"]];
|
|
[self installReleaseNotesMenuItem:self];
|
|
}
|
|
return SharedInstance;
|
|
}
|
|
|
|
- (void)scheduleVersionCheck:(id)sender
|
|
{
|
|
D(DBF_SoftwareUpdate_Check, bug("had pending check: %s\n", BSTR(self.pollTimer)););
|
|
[self.pollTimer invalidate];
|
|
self.pollTimer = nil;
|
|
|
|
struct statfs sfsb;
|
|
BOOL readOnlyFileSystem = statfs(oak::application_t::path().c_str(), &sfsb) != 0 || (sfsb.f_flags & MNT_RDONLY);
|
|
BOOL disablePolling = [[[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsDisableSoftwareUpdatesKey] boolValue];
|
|
D(DBF_SoftwareUpdate_Check, bug("download visible %s, disable polling %s, read only file system %s → %s\n", BSTR(downloadWindowController.isVisible), BSTR(disablePolling), BSTR(readOnlyFileSystem), BSTR(!downloadWindowController.isVisible && !disablePolling && !readOnlyFileSystem)););
|
|
if(downloadWindowController.isVisible || disablePolling || readOnlyFileSystem)
|
|
return;
|
|
|
|
NSDate* nextCheck = [(self.lastPoll ?: [NSDate distantPast]) addTimeInterval:pollInterval];
|
|
if(NSDate* suspendUntil = [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsSoftwareUpdateSuspendUntilKey])
|
|
nextCheck = [nextCheck laterDate:suspendUntil];
|
|
|
|
D(DBF_SoftwareUpdate_Check, bug("perform next check in %.1f hours\n", std::max<NSTimeInterval>(1, [nextCheck timeIntervalSinceNow])/60/60););
|
|
self.pollTimer = [NSTimer scheduledTimerWithTimeInterval:std::max<NSTimeInterval>(1, [nextCheck timeIntervalSinceNow]) target:self selector:@selector(performVersionCheck:) userInfo:nil repeats:NO];
|
|
}
|
|
|
|
- (void)userDefaultsDidChange:(id)sender
|
|
{
|
|
[self scheduleVersionCheck:self];
|
|
}
|
|
|
|
// ========================
|
|
// = Performing the check =
|
|
// ========================
|
|
|
|
- (void)performVersionCheck:(NSTimer*)aTimer
|
|
{
|
|
D(DBF_SoftwareUpdate_Check, bug("last check was %.1f hours ago\n", -[self.lastPoll timeIntervalSinceNow] / (60*60)););
|
|
if(!isChecking)
|
|
{
|
|
self.isChecking = YES;
|
|
|
|
NSURL* url = [channels objectForKey:[[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsSoftwareUpdateChannelKey]];
|
|
[self performSelectorInBackground:@selector(performBackgroundVersionCheck:) withObject:@{ @"infoURL" : url, @"timer" : YES_obj }];
|
|
}
|
|
}
|
|
|
|
- (IBAction)checkForUpdates:(id)sender
|
|
{
|
|
D(DBF_SoftwareUpdate_ReleaseNotes, bug("\n"););
|
|
if(!isChecking)
|
|
{
|
|
self.isChecking = YES;
|
|
|
|
NSURL* url = [channels objectForKey:OakIsAlternateKeyOrMouseEvent(NSAlternateKeyMask) ? kSoftwareUpdateChannelNightly : [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsSoftwareUpdateChannelKey]];
|
|
BOOL force = OakIsAlternateKeyOrMouseEvent(NSShiftKeyMask);
|
|
[self performSelectorInBackground:@selector(performBackgroundVersionCheck:) withObject:@{ @"infoURL" : url, @"force" : @(force) }];
|
|
}
|
|
}
|
|
|
|
- (void)performBackgroundVersionCheck:(NSDictionary*)someOptions
|
|
{
|
|
NSAutoreleasePool* pool = [NSAutoreleasePool new];
|
|
|
|
NSURL* url = [someOptions objectForKey:@"infoURL"];
|
|
std::string error = NULL_STR;
|
|
auto info = sw_update::download_info(to_s([url absoluteString]), &error);
|
|
|
|
NSMutableDictionary* res = [[someOptions mutableCopy] autorelease];
|
|
if(error != NULL_STR)
|
|
{
|
|
[res setObject:[NSString stringWithCxxString:error] forKey:@"error"];
|
|
}
|
|
else
|
|
{
|
|
[res setObject:[NSString stringWithCxxString:info.url] forKey:@"url"];
|
|
[res setObject:@(info.version) forKey:@"version"];
|
|
}
|
|
|
|
[self performSelectorOnMainThread:@selector(didPerformBackgroundVersionCheck:) withObject:res waitUntilDone:NO];
|
|
[pool drain];
|
|
}
|
|
|
|
- (void)didPerformBackgroundVersionCheck:(NSDictionary*)info
|
|
{
|
|
long version = [[info objectForKey:@"version"] longValue];
|
|
NSString* downloadURL = [info objectForKey:@"url"];
|
|
NSString* error = [info objectForKey:@"error"];
|
|
BOOL timer = [[info objectForKey:@"timer"] boolValue];
|
|
BOOL force = [[info objectForKey:@"force"] boolValue];
|
|
BOOL ask = [[NSUserDefaults standardUserDefaults] boolForKey:kUserDefaultsAskBeforeUpdatingKey];
|
|
|
|
if(version && downloadURL)
|
|
{
|
|
NSString* appName = [NSString stringWithCxxString:oak::application_t::name()];
|
|
NSInteger thisVersion = force ? 0 : strtol(oak::application_t::revision().c_str(), NULL, 10);
|
|
if(version <= thisVersion)
|
|
{
|
|
if(!timer)
|
|
NSRunInformationalAlertPanel(@"Up To Date", @"%@ %ld is the latest version available—you have %ld.", @"Continue", nil, nil, appName, version, thisVersion);
|
|
}
|
|
else
|
|
{
|
|
bool interactive = ask || !timer;
|
|
int choice = interactive ? NSRunInformationalAlertPanel(@"New Version Available", @"%@ %ld is now available—you have %ld. Would you like to download it now?", @"Download & Install", nil, @"Later", appName, version, thisVersion) : NSAlertDefaultReturn;
|
|
if(choice == NSAlertDefaultReturn) // "Download & Install"
|
|
{
|
|
self.downloadWindowController = [[DownloadWindowController alloc] initWithURL:downloadURL displayString:[NSString stringWithFormat:@"Downloading %@ %ld…", appName, version] keyChain:keyChain];
|
|
downloadWindowController.versionOfDownload = [NSString stringWithFormat:@"%ld", version];
|
|
if(!interactive && [NSApp isActive])
|
|
[[downloadWindowController window] orderFront:self];
|
|
else [downloadWindowController showWindow:self];
|
|
}
|
|
else if(choice == NSAlertOtherReturn) // "Later"
|
|
{
|
|
[[NSUserDefaults standardUserDefaults] setObject:[[NSDate date] addTimeInterval:24*60*60] forKey:kUserDefaultsSoftwareUpdateSuspendUntilKey];
|
|
}
|
|
}
|
|
}
|
|
else if(error && !timer)
|
|
{
|
|
NSRunInformationalAlertPanel(@"Error checking for new version", @"%@", @"Continue", nil, nil, error);
|
|
error = nil;
|
|
}
|
|
|
|
self.isChecking = NO;
|
|
self.lastPoll = [NSDate date];
|
|
self.errorString = error;
|
|
}
|
|
|
|
// ==============
|
|
// = Properties =
|
|
// ==============
|
|
|
|
- (void)setChannels:(NSDictionary*)someChannels
|
|
{
|
|
[channels autorelease];
|
|
channels = [someChannels retain];
|
|
[self scheduleVersionCheck:nil];
|
|
}
|
|
|
|
- (void)setSignee:(key_chain_t::key_t const&)aSignee
|
|
{
|
|
keyChain.add(aSignee);
|
|
}
|
|
|
|
- (NSDate*)lastPoll { return [[NSUserDefaults standardUserDefaults] objectForKey:kUserDefaultsLastSoftwareUpdateCheckKey]; }
|
|
- (void)setLastPoll:(NSDate*)newDate { [[NSUserDefaults standardUserDefaults] setObject:newDate forKey:kUserDefaultsLastSoftwareUpdateCheckKey]; }
|
|
@end
|