1010
1111@implementation UpdateHandler
1212
13+ // MARK: - Homebrew Detection
14+
15+ + (BOOL )isInstalledViaHomebrew {
16+ NSFileManager *fileManager = [NSFileManager defaultManager ];
17+ // Check Apple Silicon Homebrew path
18+ if ([fileManager fileExistsAtPath: @" /opt/homebrew/Caskroom/appbox" ]) {
19+ return YES ;
20+ }
21+ // Check Intel Homebrew path
22+ if ([fileManager fileExistsAtPath: @" /usr/local/Caskroom/appbox" ]) {
23+ return YES ;
24+ }
25+ return NO ;
26+ }
27+
1328// MARK: - Check for update
1429
30+ + (void )showHomebrewUpdateAlert {
31+ NSString *command = @" brew upgrade --cask appbox" ;
32+
33+ NSAlert *alert = [[NSAlert alloc ] init ];
34+ [alert setMessageText: @" New Version Available" ];
35+ [alert setInformativeText: @" A newer version of \" AppBox\" is available.\n\n Since you installed AppBox via Homebrew, please run this command in Terminal:" ];
36+ [alert setAlertStyle: NSAlertStyleInformational];
37+ [alert setAccessoryView: [self commandTextFieldWithString: command width: 250 ]];
38+ [alert addButtonWithTitle: @" Copy & Close" ];
39+ [alert addButtonWithTitle: @" Release Notes" ];
40+ [alert addButtonWithTitle: @" Close" ];
41+ NSModalResponse response = [alert runModal ];
42+ if (response == NSAlertFirstButtonReturn ) {
43+ [[NSPasteboard generalPasteboard ] clearContents ];
44+ [[NSPasteboard generalPasteboard ] setString: command forType: NSPasteboardTypeString ];
45+ } else if (response == NSAlertSecondButtonReturn ) {
46+ [[NSWorkspace sharedWorkspace ] openURL: [NSURL URLWithString: abGitHubLatestReleaseURL]];
47+ }
48+ }
49+
1550+ (void )showUpdateAlertWithUpdateURL : (NSURL *)url {
51+ NSString *command = @" curl -s https://getappbox.com/install.sh | bash" ;
52+
1653 NSAlert *alert = [[NSAlert alloc ] init ];
17- [alert setMessageText: @" New Version Available" ];
18- [alert setInformativeText: @" A newer version of the \" AppBox\" is available. Do you want to update it? \n\n\n " ];
19- [alert setAlertStyle: NSAlertStyleInformational];
20- [alert addButtonWithTitle: @" YES" ];
21- [alert addButtonWithTitle: @" NO" ];
22- if ([alert runModal ] == NSAlertFirstButtonReturn ){
23- [[NSWorkspace sharedWorkspace ] openURL: url];
54+ [alert setMessageText: @" New Version Available" ];
55+ [alert setInformativeText: @" A newer version of \" AppBox\" is available.\n\n To update, run this command in Terminal:" ];
56+ [alert setAlertStyle: NSAlertStyleInformational];
57+ [alert setAccessoryView: [self commandTextFieldWithString: command width: 400 ]];
58+ [alert addButtonWithTitle: @" Copy & Close" ];
59+ [alert addButtonWithTitle: @" Release Notes" ];
60+ [alert addButtonWithTitle: @" Close" ];
61+ NSModalResponse response = [alert runModal ];
62+ if (response == NSAlertFirstButtonReturn ){
63+ [[NSPasteboard generalPasteboard ] clearContents ];
64+ [[NSPasteboard generalPasteboard ] setString: command forType: NSPasteboardTypeString ];
65+ } else if (response == NSAlertSecondButtonReturn ) {
66+ [[NSWorkspace sharedWorkspace ] openURL: [NSURL URLWithString: abGitHubLatestReleaseURL]];
2467 }
2568}
2669
70+ + (NSView *)commandTextFieldWithString : (NSString *)command width : (CGFloat)width {
71+ NSTextField *textField = [[NSTextField alloc ] initWithFrame: NSMakeRect (0 , 0 , width, 20 )];
72+ [textField setStringValue: [NSString stringWithFormat: @" %@ " , command]];
73+ [textField setEditable: NO ];
74+ [textField setSelectable: YES ];
75+ [textField setBordered: YES ];
76+ [textField setBezelStyle: NSTextFieldRoundedBezel];
77+ [textField setFont: [NSFont monospacedSystemFontOfSize: 12.0 weight: NSFontWeightMedium ]];
78+ [textField setBackgroundColor: [NSColor controlBackgroundColor ]];
79+ [textField setAlignment: NSTextAlignmentCenter];
80+ return textField;
81+ }
82+
2783+ (void )showAlreadyUptoDateAlert {
2884 NSString *versionString = [[[NSBundle mainBundle ] infoDictionary ] valueForKey: @" CFBundleShortVersionString" ];
2985 [Common showAlertWithTitle: @" You’re up-to-date!" andMessage: [NSString stringWithFormat: @" AppBox %@ is currently the newest version available." , versionString]];
@@ -32,47 +88,80 @@ + (void)showAlreadyUptoDateAlert{
3288+ (void )isNewVersionAvailableCompletion : (void (^)(bool available, NSURL *url))completion {
3389 @try {
3490 DDLogDebug (@" Checking for new version..." );
35- [NetworkHandler requestWithURL: abGitHubLatestRelease withParameters: nil andRequestType: RequestGET andCompletetion: ^(id responseObj, NSInteger httpStatus, NSError *error) {
36- // handle error and check for all required keys
37- @try {
38- if (error == nil && [responseObj isKindOfClass: [NSDictionary class ]] &&
39- [((NSDictionary *)responseObj).allKeys containsObject: @" tag_name" ] &&
40- [((NSDictionary *)responseObj).allKeys containsObject: @" html_url" ]){
41-
42- // get tag name, because it's always be latest version
43- NSString *tag = [responseObj valueForKey: @" tag_name" ];
44-
45- // get version string from bundle info.plist
46- NSString *versionString = [[[NSBundle mainBundle ] infoDictionary ] valueForKey: @" CFBundleShortVersionString" ];
47- if (!versionString || !tag) {
48- completion (false , nil );
49- return ;
50- }
51-
52- // log current and latest version
53- DDLogDebug (@" Current Version - %@ <=> Latest Version - %@ " , versionString, tag);
54-
55- // Use proper numeric version comparison
56- NSString *latestVersion = [self extractVersionString: tag];
57- NSString *currentVersion = [self extractVersionString: versionString];
58- NSComparisonResult result = [latestVersion compare: currentVersion options: NSNumericSearch];
59-
60- // return result based on version strings
61- completion ((result == NSOrderedDescending), [NSURL URLWithString: [responseObj valueForKey: @" html_url" ]]);
62- }else {
63- completion (false , nil );
64- }
65- }
66- @catch (NSException *exception) {
67- DDLogError (@" Exception %@ " ,exception.abDescription );
68- }
69- }];
91+
92+ if ([self isInstalledViaHomebrew ]) {
93+ // Check Homebrew API for latest available version
94+ [NetworkHandler requestWithURL: abHomebrewCaskAPI withParameters: nil andRequestType: RequestGET andCompletetion: ^(id responseObj, NSInteger httpStatus, NSError *error) {
95+ @try {
96+ if (error == nil && [responseObj isKindOfClass: [NSDictionary class ]] &&
97+ [((NSDictionary *)responseObj).allKeys containsObject: @" version" ]) {
98+
99+ NSString *latestVersion = [responseObj valueForKey: @" version" ];
100+ NSString *versionString = [[[NSBundle mainBundle ] infoDictionary ] valueForKey: @" CFBundleShortVersionString" ];
101+ if (!versionString || !latestVersion) {
102+ completion (false , nil );
103+ return ;
104+ }
105+
106+ DDLogDebug (@" Current Version - %@ <=> Homebrew Latest Version - %@ " , versionString, latestVersion);
107+
108+ NSString *cleanLatest = [self extractVersionString: latestVersion];
109+ NSString *cleanCurrent = [self extractVersionString: versionString];
110+ NSComparisonResult result = [cleanLatest compare: cleanCurrent options: NSNumericSearch];
111+
112+ completion ((result == NSOrderedDescending), nil );
113+ } else {
114+ // Homebrew API failed — fallback to GitHub release check
115+ DDLogInfo (@" Homebrew API failed, falling back to GitHub release check." );
116+ [self checkGitHubReleaseWithCompletion: completion];
117+ }
118+ }
119+ @catch (NSException *exception) {
120+ DDLogError (@" Exception %@ " ,exception.abDescription );
121+ [self checkGitHubReleaseWithCompletion: completion];
122+ }
123+ }];
124+ } else {
125+ [self checkGitHubReleaseWithCompletion: completion];
126+ }
70127 } @catch (NSException *exception) {
71128 completion (false , nil );
72129 DDLogInfo (@" Exception %@ " ,exception.abDescription );
73130 }
74131}
75132
133+ + (void )checkGitHubReleaseWithCompletion : (void (^)(bool available, NSURL *url))completion {
134+ [NetworkHandler requestWithURL: abGitHubLatestRelease withParameters: nil andRequestType: RequestGET andCompletetion: ^(id responseObj, NSInteger httpStatus, NSError *error) {
135+ @try {
136+ if (error == nil && [responseObj isKindOfClass: [NSDictionary class ]] &&
137+ [((NSDictionary *)responseObj).allKeys containsObject: @" tag_name" ] &&
138+ [((NSDictionary *)responseObj).allKeys containsObject: @" html_url" ]){
139+
140+ NSString *tag = [responseObj valueForKey: @" tag_name" ];
141+ NSString *versionString = [[[NSBundle mainBundle ] infoDictionary ] valueForKey: @" CFBundleShortVersionString" ];
142+ if (!versionString || !tag) {
143+ completion (false , nil );
144+ return ;
145+ }
146+
147+ DDLogDebug (@" Current Version - %@ <=> GitHub Latest Version - %@ " , versionString, tag);
148+
149+ NSString *latestVersion = [self extractVersionString: tag];
150+ NSString *currentVersion = [self extractVersionString: versionString];
151+ NSComparisonResult result = [latestVersion compare: currentVersion options: NSNumericSearch];
152+
153+ completion ((result == NSOrderedDescending), [NSURL URLWithString: [responseObj valueForKey: @" html_url" ]]);
154+ } else {
155+ completion (false , nil );
156+ }
157+ }
158+ @catch (NSException *exception) {
159+ DDLogError (@" Exception %@ " ,exception.abDescription );
160+ completion (false , nil );
161+ }
162+ }];
163+ }
164+
76165+ (NSString *)extractVersionString : (NSString *)input {
77166 if (!input) return @" 0" ;
78167 // Remove everything except digits and dots
0 commit comments