Backup URL scheme
This commit is contained in:
@@ -25,7 +25,7 @@ and this project does adhere to [Semantic Versioning](https://semver.org/spec/v2
|
|||||||
- *UI:* Accessibility hints for most UI elements
|
- *UI:* Accessibility hints for most UI elements
|
||||||
- *UI*: Show welcome message upon first usage (empty db)
|
- *UI*: Show welcome message upon first usage (empty db)
|
||||||
- Welcome message also adds Github releases feed
|
- Welcome message also adds Github releases feed
|
||||||
- Config URL scheme `barss:` with `open/preferences` and `config/fixcache`
|
- Config URL scheme `barss:` with `open/preferences`, `config/fixcache`, and `backup/show`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- *Adding feed:* Show proper HTTP status code error message (4xx and 5xx)
|
- *Adding feed:* Show proper HTTP status code error message (4xx and 5xx)
|
||||||
|
|||||||
@@ -28,11 +28,13 @@
|
|||||||
#import "BarStatusItem.h"
|
#import "BarStatusItem.h"
|
||||||
#import "UpdateScheduler.h"
|
#import "UpdateScheduler.h"
|
||||||
#import "StoreCoordinator.h"
|
#import "StoreCoordinator.h"
|
||||||
|
#import "OpmlFile.h"
|
||||||
#import "SettingsFeeds+DragDrop.h"
|
#import "SettingsFeeds+DragDrop.h"
|
||||||
#import "NSURL+Ext.h"
|
#import "NSURL+Ext.h"
|
||||||
|
#import "NSDate+Ext.h"
|
||||||
#import "NSError+Ext.h"
|
#import "NSError+Ext.h"
|
||||||
|
|
||||||
@interface AppHook()
|
@interface AppHook() <OpmlFileExportDelegate>
|
||||||
@property (strong) NSWindowController *prefWindow;
|
@property (strong) NSWindowController *prefWindow;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -203,6 +205,7 @@
|
|||||||
@textblock
|
@textblock
|
||||||
barss:open/preferences[/0-4]
|
barss:open/preferences[/0-4]
|
||||||
barss:config/fixcache[/silent]
|
barss:config/fixcache[/silent]
|
||||||
|
barss:backup[/show]
|
||||||
@/textblock
|
@/textblock
|
||||||
*/
|
*/
|
||||||
- (void)handleConfigURLScheme:(const NSString*)action parameters:(NSArray<NSString*>*)params {
|
- (void)handleConfigURLScheme:(const NSString*)action parameters:(NSArray<NSString*>*)params {
|
||||||
@@ -215,9 +218,24 @@
|
|||||||
if ([params.firstObject isEqualToString:kURLParamFixCache]) {
|
if ([params.firstObject isEqualToString:kURLParamFixCache]) {
|
||||||
[StoreCoordinator cleanupAndShowAlert:![params.lastObject isEqualToString:kURLParamSilent]];
|
[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<FeedGroup*>*)opmlFileExportListOfFeedGroups:(OpmlFileExportOptions)options {
|
||||||
|
return [StoreCoordinator sortedFeedGroupsWithParent:nil inContext:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Event Handling, Forward Send Key Down Events
|
#pragma mark - Event Handling, Forward Send Key Down Events
|
||||||
|
|
||||||
|
|||||||
@@ -120,12 +120,16 @@ static NSString* const kURLSchemeBarss = @"barss";
|
|||||||
static NSString* const kURLActionOpen = @"open";
|
static NSString* const kURLActionOpen = @"open";
|
||||||
/// Use @c barss:config to perform configuration steps
|
/// Use @c barss:config to perform configuration steps
|
||||||
static NSString* const kURLActionConfig = @"config";
|
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
|
/// Open preferences window with optional tab index. E.g., @c barss:open/preferences/1
|
||||||
static NSString* const kURLParamPreferences = @"preferences";
|
static NSString* const kURLParamPreferences = @"preferences";
|
||||||
/// Run core data cleanup with optional silent parameter. E.g., @c barss:config/fixcache/silent
|
/// Run core data cleanup with optional silent parameter. E.g., @c barss:config/fixcache/silent
|
||||||
static NSString* const kURLParamFixCache = @"fixcache";
|
static NSString* const kURLParamFixCache = @"fixcache";
|
||||||
/// Disables error alerts and other interactive UI
|
/// Disables error alerts and other interactive UI
|
||||||
static NSString* const kURLParamSilent = @"silent";
|
static NSString* const kURLParamSilent = @"silent";
|
||||||
|
/// Show backup directory in Finder. E.g., @c barss:backup/show
|
||||||
|
static NSString* const kURLParamShow = @"show";
|
||||||
|
|
||||||
|
|
||||||
#pragma mark - Internal
|
#pragma mark - Internal
|
||||||
|
|||||||
@@ -209,8 +209,7 @@
|
|||||||
|
|
||||||
/// Image file path at e.g., "Application Support/baRSS/favicons/p42". @warning File may not exist!
|
/// Image file path at e.g., "Application Support/baRSS/favicons/p42". @warning File may not exist!
|
||||||
- (NSURL*)iconPath {
|
- (NSURL*)iconPath {
|
||||||
NSString *pk = self.objectID.URIRepresentation.lastPathComponent;
|
return [[NSURL faviconsCacheURL] file:self.objectID.URIRepresentation.lastPathComponent ext:nil];
|
||||||
return [[NSURL faviconsCacheURL] URLByAppendingPathComponent:pk isDirectory:NO];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move favicon from @c $TMPDIR to permanent destination in Application Support.
|
/// Move favicon from @c $TMPDIR to permanent destination in Application Support.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ static int const dbFileVersion = 1; // update in case database structure changes
|
|||||||
|
|
||||||
@interface StoreCoordinator : NSObject
|
@interface StoreCoordinator : NSObject
|
||||||
// Managing contexts
|
// Managing contexts
|
||||||
|
+ (NSManagedObjectContext*)getMainContext;
|
||||||
+ (NSManagedObjectContext*)createChildContext;
|
+ (NSManagedObjectContext*)createChildContext;
|
||||||
+ (void)saveContext:(NSManagedObjectContext*)context andParent:(BOOL)flag;
|
+ (void)saveContext:(NSManagedObjectContext*)context andParent:(BOOL)flag;
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ static int const dbFileVersion = 1; // update in case database structure changes
|
|||||||
|
|
||||||
// Feed update
|
// Feed update
|
||||||
+ (NSDate*)nextScheduledUpdate;
|
+ (NSDate*)nextScheduledUpdate;
|
||||||
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(NSManagedObjectContext*)moc;
|
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(nullable NSManagedObjectContext*)moc;
|
||||||
|
|
||||||
// Count elements
|
// Count elements
|
||||||
+ (BOOL)isEmpty;
|
+ (BOOL)isEmpty;
|
||||||
@@ -45,10 +46,8 @@ static int const dbFileVersion = 1; // update in case database structure changes
|
|||||||
+ (NSArray<NSDictionary*>*)countAggregatedUnread;
|
+ (NSArray<NSDictionary*>*)countAggregatedUnread;
|
||||||
|
|
||||||
// Get List Of Elements
|
// Get List Of Elements
|
||||||
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(NSManagedObjectContext*)moc;
|
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(nullable NSManagedObjectContext*)moc;
|
||||||
+ (NSArray<FeedArticle*>*)sortedArticlesWithParent:(id)parent inContext:(NSManagedObjectContext*)moc;
|
+ (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(nullable NSManagedObjectContext*)moc;
|
||||||
+ (NSArray<Feed*>*)listOfFeedsMissingArticlesInContext:(NSManagedObjectContext*)moc;
|
|
||||||
+ (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(NSManagedObjectContext*)moc;
|
|
||||||
+ (NSString*)urlForFeedWithIndexPath:(nonnull NSString*)path;
|
+ (NSString*)urlForFeedWithIndexPath:(nonnull NSString*)path;
|
||||||
|
|
||||||
// Unread articles list & mark articled read
|
// Unread articles list & mark articled read
|
||||||
|
|||||||
@@ -98,14 +98,15 @@
|
|||||||
List of @c Feed items that need to be updated. Scheduled time is now (or in past).
|
List of @c Feed items that need to be updated. Scheduled time is now (or in past).
|
||||||
|
|
||||||
@param forceAll If @c YES get a list of all @c Feed regardless of schedules time.
|
@param forceAll If @c YES get a list of all @c Feed regardless of schedules time.
|
||||||
|
@param moc If @c nil perform requests on main context (ok for reading).
|
||||||
*/
|
*/
|
||||||
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(NSManagedObjectContext*)moc {
|
+ (NSArray<Feed*>*)listOfFeedsThatNeedUpdate:(BOOL)forceAll inContext:(nullable NSManagedObjectContext*)moc {
|
||||||
NSFetchRequest *fr = [Feed fetchRequest];
|
NSFetchRequest *fr = [Feed fetchRequest];
|
||||||
if (!forceAll) {
|
if (!forceAll) {
|
||||||
// when fetching also get those feeds that would need update soon (now + 2s)
|
// when fetching also get those feeds that would need update soon (now + 2s)
|
||||||
[fr where:@"meta.scheduled <= %@", [NSDate dateWithTimeIntervalSinceNow:+2]];
|
[fr where:@"meta.scheduled <= %@", [NSDate dateWithTimeIntervalSinceNow:+2]];
|
||||||
}
|
}
|
||||||
return [fr fetchAllRows:moc];
|
return [fr fetchAllRows:moc ? moc : [self getMainContext]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -139,24 +140,30 @@
|
|||||||
|
|
||||||
#pragma mark - Get List Of Elements
|
#pragma mark - Get List Of Elements
|
||||||
|
|
||||||
/// @return Sorted list of @c FeedGroup items where @c FeedGroup.parent @c = @c parent.
|
/**
|
||||||
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(NSManagedObjectContext*)moc {
|
@param moc If @c nil perform requests on main context (ok for reading).
|
||||||
return [[[[FeedGroup fetchRequest] where:@"parent = %@", parent] sortASC:@"sortIndex"] fetchAllRows:moc];
|
@return Sorted list of @c FeedGroup items where @c FeedGroup.parent @c = @c parent.
|
||||||
|
*/
|
||||||
|
+ (NSArray<FeedGroup*>*)sortedFeedGroupsWithParent:(id)parent inContext:(nullable NSManagedObjectContext*)moc {
|
||||||
|
return [[[[FeedGroup fetchRequest] where:@"parent = %@", parent] sortASC:@"sortIndex"] fetchAllRows:moc ? moc : [self getMainContext]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return Sorted list of @c FeedArticle items where @c FeedArticle.feed @c = @c parent.
|
/// @return Sorted list of @c FeedArticle items where @c FeedArticle.feed @c = @c parent.
|
||||||
+ (NSArray<FeedArticle*>*)sortedArticlesWithParent:(id)parent inContext:(NSManagedObjectContext*)moc {
|
//+ (NSArray<FeedArticle*>*)sortedArticlesWithParent:(id)parent inContext:(NSManagedObjectContext*)moc {
|
||||||
return [[[[FeedArticle fetchRequest] where:@"feed = %@", parent] sortDESC:@"sortIndex"] fetchAllRows:moc];
|
// return [[[[FeedArticle fetchRequest] where:@"feed = %@", parent] sortDESC:@"sortIndex"] fetchAllRows:moc];
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// @return Unsorted list of @c Feed items where @c articles.count @c == @c 0.
|
/// @return Unsorted list of @c Feed items where @c articles.count @c == @c 0.
|
||||||
+ (NSArray<Feed*>*)listOfFeedsMissingArticlesInContext:(NSManagedObjectContext*)moc {
|
//+ (NSArray<Feed*>*)listOfFeedsMissingArticlesInContext:(NSManagedObjectContext*)moc {
|
||||||
return [[[Feed fetchRequest] where:@"articles.@count == 0"] fetchAllRows:moc];
|
// return [[[Feed fetchRequest] where:@"articles.@count == 0"] fetchAllRows:moc];
|
||||||
}
|
//}
|
||||||
|
|
||||||
/// @return Single @c Feed item where @c Feed.indexPath @c = @c path.
|
/**
|
||||||
+ (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(NSManagedObjectContext*)moc {
|
@param moc If @c nil perform requests on main context (ok for reading).
|
||||||
return [[[Feed fetchRequest] where:@"indexPath = %@", path] fetchFirst:moc];
|
@return Single @c Feed item where @c Feed.indexPath @c = @c path.
|
||||||
|
*/
|
||||||
|
+ (Feed*)feedWithIndexPath:(nonnull NSString*)path inContext:(nullable NSManagedObjectContext*)moc {
|
||||||
|
return [[[Feed fetchRequest] where:@"indexPath = %@", path] fetchFirst:moc ? moc : [self getMainContext]];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return URL of @c Feed item where @c Feed.indexPath @c = @c path.
|
/// @return URL of @c Feed item where @c Feed.indexPath @c = @c path.
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#import "FaviconDownload.h"
|
#import "FaviconDownload.h"
|
||||||
#import "Feed+Ext.h"
|
#import "Feed+Ext.h"
|
||||||
#import "FeedMeta+Ext.h"
|
#import "FeedMeta+Ext.h"
|
||||||
|
#import "NSURL+Ext.h"
|
||||||
#import "NSURLRequest+Ext.h"
|
#import "NSURLRequest+Ext.h"
|
||||||
|
|
||||||
@interface FaviconDownload()
|
@interface FaviconDownload()
|
||||||
@@ -148,12 +149,10 @@
|
|||||||
if (error) path = nil; // will also nullify img
|
if (error) path = nil; // will also nullify img
|
||||||
NSImage *img = path ? [[NSImage alloc] initByReferencingURL:path] : nil;
|
NSImage *img = path ? [[NSImage alloc] initByReferencingURL:path] : nil;
|
||||||
if (img.valid) {
|
if (img.valid) {
|
||||||
NSString *tmp = NSProcessInfo.processInfo.globallyUniqueString;
|
|
||||||
NSURL *dest = [path URLByDeletingLastPathComponent];
|
|
||||||
dest = [dest URLByAppendingPathComponent:tmp isDirectory:NO];
|
|
||||||
// move image to temporary destination, otherwise dataTask: will delete it.
|
// move image to temporary destination, otherwise dataTask: will delete it.
|
||||||
[[NSFileManager defaultManager] moveItemAtURL:path toURL:dest error:nil];
|
NSString *tmpFile = NSProcessInfo.processInfo.globallyUniqueString;
|
||||||
self.fileURL = dest;
|
self.fileURL = [[path URLByDeletingLastPathComponent] file:tmpFile ext:nil];
|
||||||
|
[path moveTo:self.fileURL];
|
||||||
} else if (self.hostURL) {
|
} else if (self.hostURL) {
|
||||||
[self loadImageFromDefaultLocation]; // starts a new request
|
[self loadImageFromDefaultLocation]; // starts a new request
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ static NSInteger RadioGroupSelection(NSView *view) {
|
|||||||
NSXMLElement *head = [NSXMLElement elementWithName:@"head"];
|
NSXMLElement *head = [NSXMLElement elementWithName:@"head"];
|
||||||
head.children = @[[NSXMLElement elementWithName:@"title" stringValue:@"baRSS feeds"],
|
head.children = @[[NSXMLElement elementWithName:@"title" stringValue:@"baRSS feeds"],
|
||||||
[NSXMLElement elementWithName:@"ownerName" stringValue:@"baRSS"],
|
[NSXMLElement elementWithName:@"ownerName" stringValue:@"baRSS"],
|
||||||
[NSXMLElement elementWithName:@"dateCreated" stringValue:[NSDate dayStringISO8601]] ];
|
[NSXMLElement elementWithName:@"dateCreated" stringValue:[NSDate timeStringISO8601]] ];
|
||||||
|
|
||||||
NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
|
NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
|
||||||
for (FeedGroup *item in list) {
|
for (FeedGroup *item in list) {
|
||||||
|
|||||||
@@ -164,10 +164,8 @@ static _Atomic(NSUInteger) _queueSize = 0;
|
|||||||
|
|
||||||
/// Perform @c FaviconDownload on all core data @c Feed entries.
|
/// Perform @c FaviconDownload on all core data @c Feed entries.
|
||||||
+ (void)updateAllFavicons {
|
+ (void)updateAllFavicons {
|
||||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
for (Feed *f in [StoreCoordinator listOfFeedsThatNeedUpdate:YES inContext:nil])
|
||||||
for (Feed *f in [StoreCoordinator listOfFeedsThatNeedUpdate:YES inContext:moc])
|
|
||||||
[FaviconDownload updateFeed:f finally:nil];
|
[FaviconDownload updateFeed:f finally:nil];
|
||||||
[moc reset];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download list of feeds. Either silently in background or with alerts in foreground.
|
/// Download list of feeds. Either silently in background or with alerts in foreground.
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>13460</string>
|
<string>13607</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
<string>public.app-category.news</string>
|
<string>public.app-category.news</string>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ typedef NS_ENUM(int32_t, TimeUnitType) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@interface NSDate (Ext)
|
@interface NSDate (Ext)
|
||||||
|
+ (NSString*)timeStringISO8601;
|
||||||
+ (NSString*)dayStringISO8601;
|
+ (NSString*)dayStringISO8601;
|
||||||
+ (NSString*)dayStringLocalized;
|
+ (NSString*)dayStringLocalized;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -35,11 +35,15 @@ static TimeUnitType const _values[] = {
|
|||||||
|
|
||||||
@implementation NSDate (Ext)
|
@implementation NSDate (Ext)
|
||||||
|
|
||||||
/// @return Day as string in iso format: @c YYYY-MM-DD'T'hh:mm:ss'Z'
|
/// @return Time as string in iso format: @c YYYY-MM-DD'T'hh:mm:ss'Z'
|
||||||
+ (NSString*)dayStringISO8601 {
|
+ (NSString*)timeStringISO8601 {
|
||||||
return [[[NSISO8601DateFormatter alloc] init] stringFromDate:[NSDate date]];
|
return [[[NSISO8601DateFormatter alloc] init] stringFromDate:[NSDate date]];
|
||||||
// NSDateComponents *now = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
|
}
|
||||||
// return [NSString stringWithFormat:@"%04ld-%02ld-%02ld", now.year, now.month, now.day];
|
|
||||||
|
/// @return Day as string in iso format: @c YYYY-MM-DD
|
||||||
|
+ (NSString*)dayStringISO8601 {
|
||||||
|
NSDateComponents *now = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];
|
||||||
|
return [NSString stringWithFormat:@"%04ld-%02ld-%02ld", now.year, now.month, now.day];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @return Day as string in localized short format, e.g., @c DD.MM.YY
|
/// @return Day as string in localized short format, e.g., @c DD.MM.YY
|
||||||
|
|||||||
@@ -23,8 +23,15 @@
|
|||||||
@import Cocoa;
|
@import Cocoa;
|
||||||
|
|
||||||
@interface NSURL (Ext)
|
@interface NSURL (Ext)
|
||||||
|
// Generators
|
||||||
|
+ (NSURL*)applicationSupportURL;
|
||||||
+ (NSURL*)faviconsCacheURL;
|
+ (NSURL*)faviconsCacheURL;
|
||||||
|
+ (NSURL*)backupPathURL;
|
||||||
|
// File Traversal
|
||||||
- (BOOL)existsAndIsDir:(BOOL)dir;
|
- (BOOL)existsAndIsDir:(BOOL)dir;
|
||||||
|
- (NSURL*)subdir:(NSString*)dirname;
|
||||||
|
- (NSURL*)file:(NSString*)filename ext:(nullable NSString*)ext;
|
||||||
|
// File Manipulation
|
||||||
- (BOOL)mkdir;
|
- (BOOL)mkdir;
|
||||||
- (void)remove;
|
- (void)remove;
|
||||||
- (void)moveTo:(NSURL*)destination;
|
- (void)moveTo:(NSURL*)destination;
|
||||||
|
|||||||
@@ -26,24 +26,56 @@
|
|||||||
|
|
||||||
@implementation NSURL (Ext)
|
@implementation NSURL (Ext)
|
||||||
|
|
||||||
/// @return Directory URL pointing to "Application Support/baRSS/favicons". Does @b not create directory!
|
// ---------------------------------------------------------------
|
||||||
+ (NSURL*)faviconsCacheURL {
|
// | MARK: - Generators
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
|
/// @return Directory URL pointing to "Application Support/baRSS". Does @b not create directory!
|
||||||
|
+ (NSURL*)applicationSupportURL {
|
||||||
static NSURL *path = nil;
|
static NSURL *path = nil;
|
||||||
static dispatch_once_t onceToken;
|
static dispatch_once_t onceToken;
|
||||||
dispatch_once(&onceToken, ^{
|
dispatch_once(&onceToken, ^{
|
||||||
path = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
|
path = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil];
|
||||||
path = [path URLByAppendingPathComponent:[UserPrefs appName] isDirectory:YES];
|
path = [path URLByAppendingPathComponent:[UserPrefs appName] isDirectory:YES];
|
||||||
path = [path URLByAppendingPathComponent:@"favicons" isDirectory:YES];
|
|
||||||
});
|
});
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return Directory URL pointing to "Application Support/baRSS/favicons". Does @b not create directory!
|
||||||
|
+ (NSURL*)faviconsCacheURL {
|
||||||
|
return [[self applicationSupportURL] URLByAppendingPathComponent:@"favicons" isDirectory:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return Directory URL pointing to "Application Support/baRSS/backup". Does @b not create directory!
|
||||||
|
+ (NSURL*)backupPathURL {
|
||||||
|
return [[self applicationSupportURL] URLByAppendingPathComponent:@"backup" isDirectory:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | MARK: - File Traversal
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
/// @return @c YES if and only if item exists at URL and item matches @c dir flag
|
/// @return @c YES if and only if item exists at URL and item matches @c dir flag
|
||||||
- (BOOL)existsAndIsDir:(BOOL)dir {
|
- (BOOL)existsAndIsDir:(BOOL)dir {
|
||||||
BOOL d;
|
BOOL d;
|
||||||
return self.path && [[NSFileManager defaultManager] fileExistsAtPath:self.path isDirectory:&d] && d == dir;
|
return self.path && [[NSFileManager defaultManager] fileExistsAtPath:self.path isDirectory:&d] && d == dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @return @c NSURL copy with appended directory path
|
||||||
|
- (NSURL*)subdir:(NSString*)dirname {
|
||||||
|
return [self URLByAppendingPathComponent:dirname isDirectory:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return @c NSURL copy with appended file path and extension
|
||||||
|
- (NSURL*)file:(NSString*)filename ext:(nullable NSString*)ext {
|
||||||
|
NSURL *u = [self URLByAppendingPathComponent:filename isDirectory:NO];
|
||||||
|
return ext.length > 0 ? [u URLByAppendingPathExtension:ext] : u;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// | MARK: - File Manipulation
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create directory at URL. If directory exists, this method does nothing.
|
Create directory at URL. If directory exists, this method does nothing.
|
||||||
@return @c YES if dir created successfully. @c NO if dir already exists or an error occured.
|
@return @c YES if dir created successfully. @c NO if dir already exists or an error occured.
|
||||||
|
|||||||
@@ -69,7 +69,8 @@
|
|||||||
[self str:mas add:@"RSXML2" link:@"https://github.com/relikd/RSXML2"];
|
[self str:mas add:@"RSXML2" link:@"https://github.com/relikd/RSXML2"];
|
||||||
[self str:mas add:@" (MIT License)" bold:NO];
|
[self str:mas add:@" (MIT License)" bold:NO];
|
||||||
[self str:mas add:@"\n\n\n\nOptions\n" bold:YES];
|
[self str:mas add:@"\n\n\n\nOptions\n" bold:YES];
|
||||||
[self str:mas add:@"Fix Cache" link:@"barss:config/fixcache"];
|
[self str:mas add:@"Fix Cache\n" link:@"barss:config/fixcache"];
|
||||||
|
[self str:mas add:@"Backup now\n" link:@"barss:backup/show"];
|
||||||
[mas endEditing];
|
[mas endEditing];
|
||||||
return mas;
|
return mas;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,19 +69,17 @@
|
|||||||
|
|
||||||
/// Populate menu with items.
|
/// Populate menu with items.
|
||||||
- (void)menuNeedsUpdate:(NSMenu*)menu {
|
- (void)menuNeedsUpdate:(NSMenu*)menu {
|
||||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
|
||||||
if (menu.isFeedMenu) {
|
if (menu.isFeedMenu) {
|
||||||
Feed *feed = [StoreCoordinator feedWithIndexPath:menu.titleIndexPath inContext:moc];
|
Feed *feed = [StoreCoordinator feedWithIndexPath:menu.titleIndexPath inContext:nil];
|
||||||
[self setArticles:[feed sortedArticles] forMenu:menu];
|
[self setArticles:[feed sortedArticles] forMenu:menu];
|
||||||
} else {
|
} else {
|
||||||
NSArray<FeedGroup*> *groups = [StoreCoordinator sortedFeedGroupsWithParent:menu.parentItem.representedObject inContext:moc];
|
NSArray<FeedGroup*> *groups = [StoreCoordinator sortedFeedGroupsWithParent:menu.parentItem.representedObject inContext:nil];
|
||||||
if (groups.count == 0) {
|
if (groups.count == 0) {
|
||||||
[menu addItemWithTitle:NSLocalizedString(@"~~~ no entries ~~~", nil) action:nil keyEquivalent:@""].enabled = NO;
|
[menu addItemWithTitle:NSLocalizedString(@"~~~ no entries ~~~", nil) action:nil keyEquivalent:@""].enabled = NO;
|
||||||
} else {
|
} else {
|
||||||
[self setFeedGroups:groups forMenu:menu];
|
[self setFeedGroups:groups forMenu:menu];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
[moc reset];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get rid of everything that is not needed.
|
/// Get rid of everything that is not needed.
|
||||||
@@ -128,13 +126,11 @@
|
|||||||
@warning @c item and @c feed will often mismatch.
|
@warning @c item and @c feed will often mismatch.
|
||||||
*/
|
*/
|
||||||
- (void)updateFeedMenuItem:(NSManagedObjectID*)oid withBlock:(void(^)(Feed *feed, NSMenuItem *item))block {
|
- (void)updateFeedMenuItem:(NSManagedObjectID*)oid withBlock:(void(^)(Feed *feed, NSMenuItem *item))block {
|
||||||
NSManagedObjectContext *moc = [StoreCoordinator createChildContext];
|
Feed *feed = [[StoreCoordinator getMainContext] objectWithID:oid];
|
||||||
Feed *feed = [moc objectWithID:oid];
|
|
||||||
if ([feed isKindOfClass:[Feed class]]) {
|
if ([feed isKindOfClass:[Feed class]]) {
|
||||||
NSMenuItem *item = [self.statusItem.mainMenu deepestItemWithPath:feed.indexPath];
|
NSMenuItem *item = [self.statusItem.mainMenu deepestItemWithPath:feed.indexPath];
|
||||||
if (item) block(feed, item);
|
if (item) block(feed, item);
|
||||||
}
|
}
|
||||||
[moc reset];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Callback method fired when feed has been updated in the background.
|
/// Callback method fired when feed has been updated in the background.
|
||||||
|
|||||||
Reference in New Issue
Block a user