All concurrency from other threads should pass through the viewContext's performBlock or performBlockAndWait functions, and no other way. So now, all access to Core Data is either happening on the main thread, or by using these code blocks, all of which will wait for their access to proceed. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
207 lines
5.9 KiB
Objective-C
207 lines
5.9 KiB
Objective-C
//
|
|
// PathSuggester.m
|
|
// Preferences
|
|
//
|
|
// Created by Christopher Snowhill on 6/21/22.
|
|
//
|
|
|
|
#import "PathSuggester.h"
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
#import "SandboxBroker.h"
|
|
|
|
@interface AudioContainer : NSObject {
|
|
}
|
|
|
|
+ (NSArray *)urlsForContainerURL:(NSURL *)url;
|
|
+ (NSArray *)dependencyUrlsForContainerURL:(NSURL *)url;
|
|
|
|
@end
|
|
|
|
// Sync, only declare items we need
|
|
@interface PlaylistEntry
|
|
@property(nonatomic) NSURL *_Nullable url;
|
|
@end
|
|
|
|
@interface PathItem : NSObject
|
|
@property(nonatomic, strong) NSString *path;
|
|
@property(nonatomic) BOOL enabled;
|
|
|
|
- (IBAction)clickEnable:(id)sender;
|
|
@end
|
|
|
|
@implementation PathItem
|
|
@synthesize path;
|
|
@synthesize enabled;
|
|
|
|
- (IBAction)clickEnable:(id)sender {
|
|
self.enabled = !self.enabled;
|
|
}
|
|
@end
|
|
|
|
@interface PathSuggester ()
|
|
|
|
@end
|
|
|
|
@implementation PathSuggester
|
|
|
|
- (id)init {
|
|
return [super initWithWindowNibName:@"PathSuggester"];
|
|
}
|
|
|
|
- (IBAction)beginSuggestion:(id)sender {
|
|
[self showWindow:self];
|
|
|
|
[pathsList removeObjects:[pathsList arrangedObjects]];
|
|
|
|
NSPersistentContainer *pc = [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
|
|
|
|
NSPredicate *hasUrlPredicate = [NSPredicate predicateWithFormat:@"urlString != nil && urlString != %@", @""];
|
|
NSPredicate *deletedPredicate = [NSPredicate predicateWithFormat:@"deLeted == NO || deLeted == nil"];
|
|
|
|
NSCompoundPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:@[deletedPredicate, hasUrlPredicate]];
|
|
|
|
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"PlaylistEntry"];
|
|
request.predicate = predicate;
|
|
|
|
NSError *error = nil;
|
|
NSArray *results = [pc.viewContext executeFetchRequest:request error:&error];
|
|
|
|
if(!results || [results count] < 1) return;
|
|
|
|
id sandboxBrokerClass = NSClassFromString(@"SandboxBroker");
|
|
|
|
NSMutableArray *items = [[NSMutableArray alloc] init];
|
|
NSMutableArray *itemPaths = [[NSMutableArray alloc] init];
|
|
|
|
NSArray *originalArray = [results valueForKey:@"url"];
|
|
NSMutableArray *array = [originalArray mutableCopy];
|
|
|
|
for(NSURL *url in originalArray) {
|
|
if(![url isFileURL]) {
|
|
[array removeObject:url];
|
|
}
|
|
}
|
|
|
|
originalArray = [array copy];
|
|
|
|
id audioContainerClass = NSClassFromString(@"AudioContainer");
|
|
|
|
for(NSURL *url in originalArray) {
|
|
NSArray *containedUrls = [audioContainerClass dependencyUrlsForContainerURL:url];
|
|
if(containedUrls && [containedUrls count] > 0) {
|
|
[array addObjectsFromArray:containedUrls];
|
|
}
|
|
}
|
|
|
|
// Add other system paths to this setting
|
|
NSString *fileTreePath = [[NSUserDefaults standardUserDefaults] stringForKey:@"fileTreeRootURL"];
|
|
if(fileTreePath && [fileTreePath length]) {
|
|
// Append false name to dodge the directory/fragment trimmer
|
|
[array addObject:[NSURL URLWithString:[fileTreePath stringByAppendingPathComponent:@"moo.mp3"]]];
|
|
}
|
|
|
|
NSString *soundFontPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"soundFontPath"];
|
|
if(soundFontPath && [soundFontPath length]) {
|
|
[array addObject:[NSURL fileURLWithPath:soundFontPath]];
|
|
}
|
|
|
|
for(NSURL *fileUrl in array) {
|
|
NSURL *url = [sandboxBrokerClass urlWithoutFragment:fileUrl];
|
|
if([sandboxPathBehaviorController matchesPath:url])
|
|
continue;
|
|
|
|
NSArray *pathComponents = [url pathComponents];
|
|
for(size_t i = 2; i < [pathComponents count]; ++i) {
|
|
NSURL *subUrl = [NSURL fileURLWithPathComponents:[pathComponents subarrayWithRange:NSMakeRange(0, i)]];
|
|
if(![itemPaths containsObject:subUrl]) {
|
|
[itemPaths addObject:subUrl];
|
|
PathItem *item = [[PathItem alloc] init];
|
|
item.path = [subUrl path];
|
|
item.enabled = NO;
|
|
[items addObject:item];
|
|
}
|
|
}
|
|
}
|
|
|
|
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path.length" ascending:YES];
|
|
[pathsList setSortDescriptors:@[sortDescriptor]];
|
|
|
|
[items sortUsingDescriptors:@[sortDescriptor]];
|
|
|
|
[pathsList addObjects:items];
|
|
|
|
[pathsList setSelectedObjects:@[]];
|
|
}
|
|
|
|
- (void)windowDidLoad {
|
|
[super windowDidLoad];
|
|
}
|
|
|
|
- (NSView *_Nullable)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *_Nullable)tableColumn row:(NSInteger)row {
|
|
NSString *cellIdentifier = @"";
|
|
NSTextAlignment cellTextAlignment = NSTextAlignmentLeft;
|
|
|
|
PathItem *pi = [[pathsList arrangedObjects] objectAtIndex:row];
|
|
|
|
float fontSize = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] floatForKey:@"fontSize"];
|
|
|
|
NSButton *cellButton = nil;
|
|
NSString *cellText = @"";
|
|
|
|
if(pi) {
|
|
cellIdentifier = [tableColumn identifier];
|
|
if([cellIdentifier isEqualToString:@"enabled"]) {
|
|
cellButton = [NSButton checkboxWithTitle:@"" target:pi action:@selector(clickEnable:)];
|
|
cellButton.state = pi.enabled ? NSControlStateValueOn : NSControlStateValueOff;
|
|
} else if([cellIdentifier isEqualToString:@"path"]) {
|
|
cellText = pi.path;
|
|
}
|
|
}
|
|
|
|
NSView *view = [tableView makeViewWithIdentifier:cellIdentifier owner:nil];
|
|
if(view && [view isKindOfClass:[NSTableCellView class]]) {
|
|
NSTableCellView *cellView = (NSTableCellView *)view;
|
|
|
|
if(cellView.textField) {
|
|
cellView.textField.allowsDefaultTighteningForTruncation = YES;
|
|
|
|
NSFont *font = [NSFont monospacedDigitSystemFontOfSize:fontSize weight:NSFontWeightRegular];
|
|
|
|
cellView.textField.font = font;
|
|
cellView.textField.stringValue = cellText;
|
|
cellView.textField.alignment = cellTextAlignment;
|
|
|
|
if(cellView.textField.intrinsicContentSize.width > cellView.textField.frame.size.width - 4) {
|
|
cellView.textField.toolTip = cellText;
|
|
}
|
|
}
|
|
if(cellButton) {
|
|
[cellView setSubviews:@[cellButton]];
|
|
}
|
|
}
|
|
|
|
return view;
|
|
}
|
|
|
|
- (IBAction)applyPaths:(id)sender {
|
|
for(PathItem *pi in [pathsList arrangedObjects]) {
|
|
if(pi.enabled) {
|
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|
[panel setAllowsMultipleSelection:NO];
|
|
[panel setCanChooseDirectories:YES];
|
|
[panel setCanChooseFiles:NO];
|
|
[panel setFloatingPanel:YES];
|
|
[panel setDirectoryURL:[NSURL fileURLWithPath:pi.path]];
|
|
[panel setTitle:@"Open to add path"];
|
|
NSInteger result = [panel runModal];
|
|
if(result == NSModalResponseOK) {
|
|
[sandboxPathBehaviorController addUrl:[panel URL]];
|
|
}
|
|
}
|
|
}
|
|
[[self window] orderOut:self];
|
|
}
|
|
|
|
@end
|