Skip to content

Commit

Permalink
feat: adds automated update check
Browse files Browse the repository at this point in the history
  • Loading branch information
tillt committed Dec 10, 2023
1 parent a67dcbc commit e71aaf9
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 2 deletions.
6 changes: 6 additions & 0 deletions KompleteSynthesia.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
BB8A5B9C298C45BB0076066D /* VideoController.m in Sources */ = {isa = PBXBuildFile; fileRef = BB8A5B9B298C45BB0076066D /* VideoController.m */; };
BBA0166929611D1D0018B2DB /* MIDI2HIDController.m in Sources */ = {isa = PBXBuildFile; fileRef = BBA0166829611D1D0018B2DB /* MIDI2HIDController.m */; };
BBB5EFAF29738E6F00C1BAC3 /* USBController.m in Sources */ = {isa = PBXBuildFile; fileRef = BBB5EFAE29738E6F00C1BAC3 /* USBController.m */; };
BBD160CC2B252C6E0054E976 /* UpdateManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD160CB2B252C6E0054E976 /* UpdateManager.m */; };
BBE086C22989965900DC59B3 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBE086C12989965900DC59B3 /* CoreMIDI.framework */; };
BBE086C42989966900DC59B3 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBE086C32989966900DC59B3 /* IOKit.framework */; };
/* End PBXBuildFile section */
Expand Down Expand Up @@ -70,6 +71,8 @@
BBA0166829611D1D0018B2DB /* MIDI2HIDController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MIDI2HIDController.m; sourceTree = "<group>"; };
BBB5EFAD29738E6F00C1BAC3 /* USBController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = USBController.h; sourceTree = "<group>"; };
BBB5EFAE29738E6F00C1BAC3 /* USBController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = USBController.m; sourceTree = "<group>"; };
BBD160CA2B252C6E0054E976 /* UpdateManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UpdateManager.h; sourceTree = "<group>"; };
BBD160CB2B252C6E0054E976 /* UpdateManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UpdateManager.m; sourceTree = "<group>"; };
BBE086C12989965900DC59B3 /* CoreMIDI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMIDI.framework; path = System/Library/Frameworks/CoreMIDI.framework; sourceTree = SDKROOT; };
BBE086C32989966900DC59B3 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -136,6 +139,8 @@
BB229912299040C500CFFB7C /* ApplicationObserver.m */,
BB8A5B9A298C45BB0076066D /* VideoController.h */,
BB8A5B9B298C45BB0076066D /* VideoController.m */,
BBD160CA2B252C6E0054E976 /* UpdateManager.h */,
BBD160CB2B252C6E0054E976 /* UpdateManager.m */,
BB49B3B8295CC8AF00D18605 /* main.m */,
BB49B3BA295CC8AF00D18605 /* KompleteSynthesia.entitlements */,
BB5C2E992AF6E96A0043F444 /* VirtualEvent.h */,
Expand Down Expand Up @@ -289,6 +294,7 @@
BB31821629679983005EB410 /* MIDIController.m in Sources */,
BB49B3B2295CC8AE00D18605 /* AppDelegate.m in Sources */,
BB49B3C3295CC91000D18605 /* LogViewController.m in Sources */,
BBD160CC2B252C6E0054E976 /* UpdateManager.m in Sources */,
BB76A1A4297B333D00A869D8 /* PreferencesWindowController.m in Sources */,
BB5C2E9B2AF6E96A0043F444 /* VirtualEvent.m in Sources */,
BBA0166929611D1D0018B2DB /* MIDI2HIDController.m in Sources */,
Expand Down
17 changes: 17 additions & 0 deletions KompleteSynthesia/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "VideoController.h"
#import "PreferencesWindowController.h"
#import "ApplicationObserver.h"
#import "UpdateManager.h"

@interface AppDelegate ()

Expand Down Expand Up @@ -233,6 +234,15 @@ - (void)applicationDidFinishInitializingWithSynthesiaRunning

[_midi2hidController swoosh];
[self updateButtonStates];

[userDefaults registerDefaults:@{kAppDefaultCheckForUpdate: @(YES)}];
BOOL checkForUpdate = [userDefaults boolForKey:kAppDefaultCheckForUpdate];
if (checkForUpdate) {
[UpdateManager UpdateCheckWithCompletion:^(NSString* state) {
NSString* message = [NSString stringWithFormat:@"update check: %@", state];
[self.log logLine:message];
}];
}
}

- (void)preferences:(id)sender
Expand Down Expand Up @@ -403,6 +413,13 @@ - (void)preferencesUpdatedActivate
forKey:kAppDefaultActivateSynthesia];
}

- (void)preferencesUpdatedUpdates:(BOOL)enabled
{
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setBool:enabled
forKey:kAppDefaultCheckForUpdate];
}

- (void)preferencesUpdatedMirror
{
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
Expand Down
8 changes: 7 additions & 1 deletion KompleteSynthesia/PreferencesWindowController.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ NS_ASSUME_NONNULL_BEGIN
@protocol PreferencesDelegate <NSObject>
- (void)preferencesUpdatedActivate;
- (void)preferencesUpdatedMirror;
//- (void)preferencesUpdatedMirror:(id)sender;
- (void)preferencesUpdatedUpdates:(BOOL)enabled;
- (void)preferencesUpdatedKeyState:(int)keyState forKeyIndex:(int)index;

@end

@interface PreferencesWindowController : NSWindowController<PaletteViewControllerDelegate>

@property (weak, nonatomic) IBOutlet NSTabView *tabView;

@property (weak, nonatomic) IBOutlet NSButton *forwardButtonsOnlyToSynthesia;
@property (weak, nonatomic) IBOutlet NSButton *checkForUpdates;
@property (weak, nonatomic) IBOutlet NSButton *mirrorSynthesiaToControllerScreen;
@property (weak, nonatomic) IBOutlet ColorField* colorUnpressed;
@property (weak, nonatomic) IBOutlet ColorField* colorPressed;
Expand All @@ -41,9 +43,13 @@ NS_ASSUME_NONNULL_BEGIN
@property (weak, nonatomic) MIDI2HIDController* midi2hid;
@property (weak, nonatomic) VideoController* video;

@property (weak, nonatomic) IBOutlet NSTextField* updateStatusField;
@property (weak, nonatomic) IBOutlet NSProgressIndicator* progress;

@property (nonatomic, weak) id<PreferencesDelegate> delegate;

- (IBAction)assertSynthesiaConfig:(id)sender;
- (IBAction)checkForUpdate:(id)sender;

@end

Expand Down
18 changes: 17 additions & 1 deletion KompleteSynthesia/PreferencesWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#import "SynthesiaController.h"
#import "MIDI2HIDController.h"
#import "VideoController.h"
#import "UpdateManager.h"

/// Preferences window controller.

Expand Down Expand Up @@ -40,10 +41,11 @@ - (void)windowDidLoad
colorField.keyState = _midi2hid.colors[key];
colorField.rounded = YES;
}

[self.forwardButtonsOnlyToSynthesia setState:_midi2hid.forwardButtonsToSynthesiaOnly ? NSControlStateValueOn : NSControlStateValueOff];
self.mirrorSynthesiaToControllerScreen.enabled = _video != nil;
[self.mirrorSynthesiaToControllerScreen setState:_video.mirrorSynthesiaApplicationWindow ? NSControlStateValueOn : NSControlStateValueOff];
[self.checkForUpdates setState:[UpdateManager CheckForUpdates] ? NSControlStateValueOn : NSControlStateValueOff];
}

- (IBAction)selectKeyState:(id)sender
Expand Down Expand Up @@ -107,6 +109,11 @@ - (IBAction)mirroringValueChanged:(id)sender
[self.delegate preferencesUpdatedMirror];
}

- (IBAction)updatesValueChanged:(id)sender
{
[self.delegate preferencesUpdatedUpdates:self.checkForUpdates.state == NSControlStateValueOn];
}

- (IBAction)assertSynthesiaConfig:(id)sender
{
NSError* error = nil;
Expand All @@ -129,4 +136,13 @@ - (IBAction)assertSynthesiaConfig:(id)sender
}
}

- (IBAction)checkForUpdate:(id)sender
{
[self.progress startAnimation:self];
[UpdateManager UpdateCheckWithCompletion:^(NSString* status) {
[self.progress stopAnimation:self];
[self.updateStatusField setStringValue:status];
}];
}

@end
38 changes: 38 additions & 0 deletions KompleteSynthesia/PreferencesWindowController.xib
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="PreferencesWindowController">
<connections>
<outlet property="checkForUpdates" destination="ZAD-im-Rhl" id="24T-Ff-5AE"/>
<outlet property="colorLeft" destination="stV-Ul-nZS" id="rCb-lz-ND7"/>
<outlet property="colorLeftPressed" destination="zoC-pY-pYp" id="3Bq-hw-Z1e"/>
<outlet property="colorLeftThumb" destination="0MB-gR-9Ea" id="yoo-A4-3Cd"/>
Expand All @@ -19,7 +20,9 @@
<outlet property="colorUnpressed" destination="0Hf-sh-MQb" id="j2R-b7-OXM"/>
<outlet property="forwardButtonsOnlyToSynthesia" destination="IYA-Ho-9yy" id="vYE-Rs-pYT"/>
<outlet property="mirrorSynthesiaToControllerScreen" destination="Sdc-LV-BaN" id="Mok-ZX-5hG"/>
<outlet property="progress" destination="FSg-8Q-ivY" id="QAR-GE-xjc"/>
<outlet property="tabView" destination="qrt-pB-3fV" id="3MD-ul-L0g"/>
<outlet property="updateStatusField" destination="GnD-vb-rq1" id="ZeG-y6-ye4"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
</customObject>
Expand Down Expand Up @@ -66,6 +69,41 @@
<action selector="fowardingValueChanged:" target="-2" id="YZk-dv-wMI"/>
</connections>
</button>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="FjY-bN-YkU">
<rect key="frame" x="10" y="124" width="148" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Check for Updates" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="G5i-ik-qwW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="checkForUpdate:" target="-2" id="ZPK-nT-JmF"/>
</connections>
</button>
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="GnD-vb-rq1">
<rect key="frame" x="181" y="131" width="280" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" id="53G-pC-VTn">
<font key="font" metaFont="system" size="11"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<progressIndicator fixedFrame="YES" maxValue="100" displayedWhenStopped="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="FSg-8Q-ivY">
<rect key="frame" x="159" y="134" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</progressIndicator>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZAD-im-Rhl">
<rect key="frame" x="15" y="91" width="444" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Automatically check for updates on startup" bezelStyle="regularSquare" imagePosition="left" inset="2" id="YSW-IP-A5l">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
<connections>
<action selector="updatesValueChanged:" target="-2" id="OgA-Ua-HRE"/>
</connections>
</button>
</subviews>
</view>
</tabViewItem>
Expand Down
21 changes: 21 additions & 0 deletions KompleteSynthesia/UpdateManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// UpdateManager.h
// KompleteSynthesia
//
// Created by Till Toenshoff on 10.12.23.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

extern NSString* kAppDefaultCheckForUpdate;

@interface UpdateManager : NSObject

+ (void)UpdateCheckWithCompletion:(void(^)(NSString* status))completion;
+ (BOOL)CheckForUpdates;

@end

NS_ASSUME_NONNULL_END
107 changes: 107 additions & 0 deletions KompleteSynthesia/UpdateManager.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
//
// UpdateManager.m
// KompleteSynthesia
//
// Created by Till Toenshoff on 10.12.23.
//

#import "UpdateManager.h"
#import <Cocoa/Cocoa.h>

/// Checks if we are currently running the latest version or if GitHub has a newer one available.

NSString* kAppDefaultCheckForUpdate = @"check_for_updates_on_startup";

NSString* kOwner = @"tillt";
NSString* kProject = @"KompleteSynthesia";

@implementation UpdateManager

+ (NSString*)LatestReleaseTag:(NSArray*)releases
{
// GitHub returns the tags in chronological order. That means we can pass through
// the returned tags and find the first one that is not a pre-release to find the
// latest release.
//
// Releases have a single "." splitting the major and minor version number.
for(NSDictionary* release in releases) {
NSString* tag = [release objectForKey:@"name"];
unsigned long dots = [[tag componentsSeparatedByString:@"."] count] - 1;
if (dots != 1) {
NSLog(@"skipping %@ as it is not a release", tag);
continue;
}
return tag;
}
return nil;
}

+ (void)UpdateCheckWithCompletion:(void(^)(NSString* status))completion
{
NSString* repo = [NSString stringWithFormat:@"https://api.github.com/repos/%@/%@/tags", kOwner, kProject];

NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:repo]];
[request setHTTPMethod:@"GET"];
NSURLSession* session = [NSURLSession sharedSession];
NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData* data,
NSURLResponse* response,
NSError* error) {
BOOL updateAvailable = NO;
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse *)response;
NSString* status = @"";
NSString* tag = @"";
if (httpResponse.statusCode == 200) {
NSError* error = nil;
id object = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if([object isKindOfClass:[NSArray class]]) {
NSArray* results = object;
tag = [UpdateManager LatestReleaseTag:results];
// FIXME: we are including pre-release tags and alike!
NSString* version = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"];
NSString* versionTag = [NSString stringWithFormat:@"v%@", version];
if ([tag compare:versionTag] != NSOrderedSame) {
NSLog(@"there is a different version available");
status = @"update available";
updateAvailable = YES;
} else {
NSLog(@"this is the latest version");
status = @"using latest version";
}
} else {
NSLog(@"Expected array of tags but got something else");
status = @"received unexpected contents";
}
} else {
NSLog(@"HTTP status code %d", (int)httpResponse.statusCode);
status = [NSString stringWithFormat:@"received HTTP status %d", (int)httpResponse.statusCode];
}

dispatch_async(dispatch_get_main_queue(), ^{
completion(status);

if (updateAvailable) {
NSAlert* alert = [NSAlert new];
alert.messageText = @"There is a new version of Komplete Synthesia available.";
alert.alertStyle = NSAlertStyleInformational;
[alert addButtonWithTitle:@"Download"];
[alert addButtonWithTitle:@"Cancel"];
long index = [alert runModal];
if (index == 1000) {
NSString* binaryName = [NSString stringWithFormat:@"KompleteSynthesia.%@.dmg", tag];
NSString* updateUrl = [NSString stringWithFormat:@"https://github.com/%@/releases/download/%@/%@/%@", kOwner, kProject, tag, binaryName];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:updateUrl]];
}
}
});
}];

[dataTask resume];
}

+ (BOOL)CheckForUpdates
{
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
return [userDefaults boolForKey:kAppDefaultCheckForUpdate];
}

@end

0 comments on commit e71aaf9

Please sign in to comment.