From aa87d1be6a82e55bd715e9470249b255e90fb027 Mon Sep 17 00:00:00 2001 From: relikd Date: Wed, 25 Sep 2019 13:06:04 +0200 Subject: [PATCH] URLScheme for handling app URLs --- baRSS.xcodeproj/project.pbxproj | 6 ++ baRSS/AppHook.m | 68 +--------------------- baRSS/Constants.h | 24 -------- baRSS/Feed Import/OpmlFile.h | 2 +- baRSS/Feed Import/OpmlFile.m | 3 +- baRSS/Helper/URLScheme.h | 27 +++++++++ baRSS/Helper/URLScheme.m | 99 +++++++++++++++++++++++++++++++++ baRSS/Info.plist | 2 +- 8 files changed, 139 insertions(+), 92 deletions(-) create mode 100644 baRSS/Helper/URLScheme.h create mode 100644 baRSS/Helper/URLScheme.m diff --git a/baRSS.xcodeproj/project.pbxproj b/baRSS.xcodeproj/project.pbxproj index a6e1da4..4c3e9a0 100644 --- a/baRSS.xcodeproj/project.pbxproj +++ b/baRSS.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 5478DF04225A7AE200D30C64 /* SettingsFeedsView.m in Sources */ = {isa = PBXBuildFile; fileRef = 5478DF03225A7AE200D30C64 /* SettingsFeedsView.m */; }; 548C6D0A230C33DE003A1AAF /* NSURL+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 548C6D09230C33DE003A1AAF /* NSURL+Ext.m */; }; 5491005D2331435E00858AE2 /* Download3rdParty.m in Sources */ = {isa = PBXBuildFile; fileRef = 5491005C2331435E00858AE2 /* Download3rdParty.m */; }; + 54910067233A4D4000858AE2 /* URLScheme.m in Sources */ = {isa = PBXBuildFile; fileRef = 54910066233A4D4000858AE2 /* URLScheme.m */; }; 5496B511214D6275003ED4ED /* UserPrefs.m in Sources */ = {isa = PBXBuildFile; fileRef = 5496B510214D6275003ED4ED /* UserPrefs.m */; }; 54A07A7F220E04CF00082C51 /* NSFetchRequest+Ext.m in Sources */ = {isa = PBXBuildFile; fileRef = 54A07A7E220E04CF00082C51 /* NSFetchRequest+Ext.m */; }; 54A07A82220E723D00082C51 /* MapUnreadTotal.m in Sources */ = {isa = PBXBuildFile; fileRef = 54A07A81220E723D00082C51 /* MapUnreadTotal.m */; }; @@ -138,6 +139,8 @@ 548C6D09230C33DE003A1AAF /* NSURL+Ext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+Ext.m"; sourceTree = ""; }; 5491005B2331435E00858AE2 /* Download3rdParty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Download3rdParty.h; sourceTree = ""; }; 5491005C2331435E00858AE2 /* Download3rdParty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Download3rdParty.m; sourceTree = ""; }; + 54910065233A4D4000858AE2 /* URLScheme.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = URLScheme.h; sourceTree = ""; }; + 54910066233A4D4000858AE2 /* URLScheme.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = URLScheme.m; sourceTree = ""; }; 5496B50F214D6275003ED4ED /* UserPrefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserPrefs.h; sourceTree = ""; }; 5496B510214D6275003ED4ED /* UserPrefs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = UserPrefs.m; sourceTree = ""; }; 54A07A7D220E04CF00082C51 /* NSFetchRequest+Ext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSFetchRequest+Ext.h"; sourceTree = ""; }; @@ -408,6 +411,8 @@ 54209E932117325100F3B5EF /* DrawImage.m */, 5496B50F214D6275003ED4ED /* UserPrefs.h */, 5496B510214D6275003ED4ED /* UserPrefs.m */, + 54910065233A4D4000858AE2 /* URLScheme.h */, + 54910066233A4D4000858AE2 /* URLScheme.m */, ); path = Helper; sourceTree = ""; @@ -586,6 +591,7 @@ 54A07A82220E723D00082C51 /* MapUnreadTotal.m in Sources */, 54ACC29821061FBA0020715F /* Preferences.m in Sources */, 54195886218E1BDB00581B79 /* NSMenu+Ext.m in Sources */, + 54910067233A4D4000858AE2 /* URLScheme.m in Sources */, 54F6025D21C1D4170006D338 /* OpmlFile.m in Sources */, 5496B511214D6275003ED4ED /* UserPrefs.m in Sources */, 546A6A2F22C585580034E806 /* SettingsAboutView.m in Sources */, diff --git a/baRSS/AppHook.m b/baRSS/AppHook.m index 3044c2d..13f9a69 100644 --- a/baRSS/AppHook.m +++ b/baRSS/AppHook.m @@ -21,20 +21,18 @@ // SOFTWARE. #import "AppHook.h" -#import "Constants.h" #import "DrawImage.h" #import "UserPrefs.h" #import "Preferences.h" #import "BarStatusItem.h" #import "UpdateScheduler.h" #import "StoreCoordinator.h" -#import "OpmlFile.h" #import "SettingsFeeds+DragDrop.h" +#import "URLScheme.h" #import "NSURL+Ext.h" -#import "NSDate+Ext.h" #import "NSError+Ext.h" -@interface AppHook() +@interface AppHook() @property (strong) NSWindowController *prefWindow; @end @@ -101,15 +99,6 @@ [UpdateScheduler scheduleNextFeed]; } -/// Close previous preferences window and re-open at the same position (will drop undo manager stack!) -- (void)reopenPreferencesIfOpen { - if (self.prefWindow) { - CGPoint screenPoint = self.prefWindow.window.frame.origin; - [self.prefWindow close]; - [[self openPreferences] setFrameOrigin:screenPoint]; - } -} - #pragma mark - Core Data stack @@ -182,58 +171,7 @@ /// Callback method fired when opened with an URL (@c feed: and @c barss: scheme) - (void)handleAppleEvent:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent { - NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; - NSString *scheme = [[[NSURL URLWithString:url] scheme] lowercaseString]; - url = [url substringFromIndex:scheme.length + 1]; // + ':' - if (url.length >= 2 && [[url substringToIndex:2] isEqualToString:@"//"]) { - url = [url substringFromIndex:2]; - } - if ([scheme isEqualToString:kURLSchemeFeed]) { - [UpdateScheduler autoDownloadAndParseURL:url]; - } else if ([scheme isEqualToString:kURLSchemeBarss]) { - NSMutableArray *comp = [[url pathComponents] mutableCopy]; - NSString *action = comp.firstObject; - if (action) { - [comp removeObjectAtIndex:0]; - [self handleConfigURLScheme:action parameters:comp]; - } - } -} - -/** - Helper method for handling the @c barss: scheme (see below). - @textblock - barss:open/preferences[/0-4] - barss:config/fixcache[/silent] - barss:backup[/show] - @/textblock - */ -- (void)handleConfigURLScheme:(const NSString*)action parameters:(NSArray*)params { - if ([action isEqualToString:kURLActionOpen]) { - if ([params.firstObject isEqualToString:kURLParamPreferences]) { - NSDecimalNumber *num = [NSDecimalNumber decimalNumberWithString:params.lastObject]; - [[self openPreferences] selectTab:num.unsignedIntegerValue]; - } - } else if ([action isEqualToString:kURLActionConfig]) { - if ([params.firstObject isEqualToString:kURLParamFixCache]) { - [StoreCoordinator cleanupAndShowAlert:![params.lastObject isEqualToString:kURLParamSilent]]; - } - } else if ([action isEqualToString:kURLActionBackup]) { - NSURL *baseURL = [NSURL backupPathURL]; - [baseURL mkdir]; // non destructive make dir - NSURL *dest = [baseURL file:[@"feeds_" stringByAppendingString:[NSDate dayStringISO8601]] ext:@"opml"]; - NSURL *sym = [baseURL file:@"feeds_latest" ext:@"opml"]; - [sym remove]; // remove old sym link, otherwise won't be updated - [[NSFileManager defaultManager] createSymbolicLinkAtURL:sym withDestinationURL:[NSURL URLWithString:dest.lastPathComponent] error:nil]; - [[OpmlFileExport withDelegate:self] writeOPMLFile:dest withOptions:OpmlFileExportOptionFullBackup]; - if ([params.firstObject isEqualToString:kURLParamShow]) - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[dest]]; - } -} - -/// Callback method for OPML backup export -- (NSArray*)opmlFileExportListOfFeedGroups:(OpmlFileExportOptions)options { - return [StoreCoordinator sortedFeedGroupsWithParent:nil inContext:nil]; + [URLScheme withURL:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]]; } diff --git a/baRSS/Constants.h b/baRSS/Constants.h index 0935823..0f8bc42 100644 --- a/baRSS/Constants.h +++ b/baRSS/Constants.h @@ -108,30 +108,6 @@ static NSNotificationName const kNotificationTotalUnreadCountChanged = @"baRSS-n static NSNotificationName const kNotificationTotalUnreadCountReset = @"baRSS-notification-total-unread-count-reset"; -#pragma mark - URL Scheme constants - -/// @c feed: URL scheme. Used for feed subscriptions. -/// @note E.g., @c feed://https://feeds.feedburner.com/simpledesktops -static NSString* const kURLSchemeFeed = @"feed"; -/// @c barss: URL scheme. Used for configuring the app. -/// @note E.g., @c barss://open/preferences -static NSString* const kURLSchemeBarss = @"barss"; -/// Use @c barss:open to display information -static NSString* const kURLActionOpen = @"open"; -/// Use @c barss:config to perform configuration steps -static NSString* const kURLActionConfig = @"config"; -/// Use @c barss:backup to backup opml file into container -static NSString* const kURLActionBackup = @"backup"; -/// Open preferences window with optional tab index. E.g., @c barss:open/preferences/1 -static NSString* const kURLParamPreferences = @"preferences"; -/// Run core data cleanup with optional silent parameter. E.g., @c barss:config/fixcache/silent -static NSString* const kURLParamFixCache = @"fixcache"; -/// Disables error alerts and other interactive UI -static NSString* const kURLParamSilent = @"silent"; -/// Show backup directory in Finder. E.g., @c barss:backup/show -static NSString* const kURLParamShow = @"show"; - - #pragma mark - Internal diff --git a/baRSS/Feed Import/OpmlFile.h b/baRSS/Feed Import/OpmlFile.h index de26975..1fa4fe6 100644 --- a/baRSS/Feed Import/OpmlFile.h +++ b/baRSS/Feed Import/OpmlFile.h @@ -57,7 +57,7 @@ typedef NS_OPTIONS(NSUInteger, OpmlFileExportOptions) { @interface OpmlFileExport : NSObject @property (weak) id delegate; -+ (instancetype)withDelegate:(id)delegate; ++ (instancetype)withDelegate:(nullable id)delegate; - (void)showExportDialog:(NSWindow*)window; - (nullable NSError*)writeOPMLFile:(NSURL*)url withOptions:(OpmlFileExportOptions)opt; @end diff --git a/baRSS/Feed Import/OpmlFile.m b/baRSS/Feed Import/OpmlFile.m index 0da09dc..90e77b4 100644 --- a/baRSS/Feed Import/OpmlFile.m +++ b/baRSS/Feed Import/OpmlFile.m @@ -197,7 +197,7 @@ static NSInteger RadioGroupSelection(NSView *view) { @implementation OpmlFileExport -+ (instancetype)withDelegate:(id)delegate { ++ (instancetype)withDelegate:(nullable id)delegate { OpmlFileExport *opml = [[super alloc] init]; opml.delegate = delegate; return opml; @@ -241,6 +241,7 @@ static NSInteger RadioGroupSelection(NSView *view) { */ - (nullable NSError*)writeOPMLFile:(NSURL*)url withOptions:(OpmlFileExportOptions)opt { NSArray *list = [self.delegate opmlFileExportListOfFeedGroups:opt]; + if (!list) list = [StoreCoordinator sortedFeedGroupsWithParent:nil inContext:nil]; // fetch all if delegate == nil NSError *error; // TODO: set error if nil or empty if (list.count > 0) { diff --git a/baRSS/Helper/URLScheme.h b/baRSS/Helper/URLScheme.h new file mode 100644 index 0000000..875cd47 --- /dev/null +++ b/baRSS/Helper/URLScheme.h @@ -0,0 +1,27 @@ +// +// The MIT License (MIT) +// Copyright (c) 2019 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +@import Cocoa; + +@interface URLScheme : NSObject ++ (void)withURL:(NSString*)url; +@end diff --git a/baRSS/Helper/URLScheme.m b/baRSS/Helper/URLScheme.m new file mode 100644 index 0000000..4c526ec --- /dev/null +++ b/baRSS/Helper/URLScheme.m @@ -0,0 +1,99 @@ +// +// The MIT License (MIT) +// Copyright (c) 2019 Oleg Geier +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#import "URLScheme.h" +#import "AppHook.h" // barss:open/preferences +#import "Preferences.h" // barss:open/preferences +#import "UpdateScheduler.h" // feed:http://URL +#import "StoreCoordinator.h" // barss:config/fixcache +#import "OpmlFile.h" // barss:backup +#import "NSURL+Ext.h" // barss:backup +#import "NSDate+Ext.h" // barss:backup + +@implementation URLScheme + +/// Handles open URL requests. Scheme may start with @c feed: or @c barss: ++ (void)withURL:(NSString*)url { + NSString *scheme = [[[NSURL URLWithString:url] scheme] lowercaseString]; + url = [url substringFromIndex:scheme.length + 1]; // + ':' + url = [url stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]]; + + if ([scheme isEqualToString:@"feed"]) [[URLScheme new] handleSchemeFeed:url]; + else if ([scheme isEqualToString:@"barss"]) [[URLScheme new] handleSchemeConfig:url]; +} + +/** + @c feed: URL scheme. Used for feed subscriptions. + @note E.g., @c feed://https://feeds.feedburner.com/simpledesktops + */ +- (void)handleSchemeFeed:(NSString*)url { + [UpdateScheduler autoDownloadAndParseURL:url]; +} + +/** + @c barss: URL scheme. Used for configuring the app. + @textblock + barss:open/preferences[/0-4] + barss:config/fixcache[/silent] + barss:backup[/show] + @/textblock + */ +- (void)handleSchemeConfig:(NSString*)url { + NSArray *comp = url.pathComponents; + NSString *action = comp.firstObject; + if (!action) return; + NSArray *params = [comp subarrayWithRange:NSMakeRange(1, comp.count - 1)]; + if ([action isEqualToString:@"open"]) [self handleActionOpen:params]; + else if ([action isEqualToString:@"config"]) [self handleActionConfig:params]; + else if ([action isEqualToString:@"backup"]) [self handleActionBackup:params]; +} + +/// @c barss:open/preferences[/0-4] +- (void)handleActionOpen:(NSArray*)params { + if ([params.firstObject isEqualToString:@"preferences"]) { + NSDecimalNumber *num = [NSDecimalNumber decimalNumberWithString:params.lastObject]; + [[(AppHook*)NSApp openPreferences] selectTab:num.unsignedIntegerValue]; + } +} + +/// @c barss:config/fixcache[/silent] +- (void)handleActionConfig:(NSArray*)params { + if ([params.firstObject isEqualToString:@"fixcache"]) { + [StoreCoordinator cleanupAndShowAlert:![params.lastObject isEqualToString:@"silent"]]; + } +} + +/// @c barss:backup[/show] +- (void)handleActionBackup:(NSArray*)params { + NSURL *baseURL = [NSURL backupPathURL]; + [baseURL mkdir]; // non destructive make dir + NSURL *dest = [baseURL file:[@"feeds_" stringByAppendingString:[NSDate dayStringISO8601]] ext:@"opml"]; + NSURL *sym = [baseURL file:@"feeds_latest" ext:@"opml"]; + [sym remove]; // remove old sym link, otherwise won't be updated + [[NSFileManager defaultManager] createSymbolicLinkAtURL:sym withDestinationURL:[NSURL URLWithString:dest.lastPathComponent] error:nil]; + [[OpmlFileExport withDelegate:nil] writeOPMLFile:dest withOptions:OpmlFileExportOptionFullBackup]; + if ([params.firstObject isEqualToString:@"show"]) { + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[dest]]; + } +} + +@end diff --git a/baRSS/Info.plist b/baRSS/Info.plist index e6c2b05..1bc3cef 100644 --- a/baRSS/Info.plist +++ b/baRSS/Info.plist @@ -70,7 +70,7 @@ CFBundleVersion - 13607 + 13702 LSApplicationCategoryType public.app-category.news LSMinimumSystemVersion