[Sandbox] Add Playlist Path Suggester

Add a dialog for suggesting sandbox paths to the Preferences general
page, in the context menu.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
swiftingly
Christopher Snowhill 2022-06-21 02:45:45 -07:00
parent 5cb08291bf
commit e8dacdabda
13 changed files with 600 additions and 52 deletions

View File

@ -413,8 +413,8 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/>
<binding destination="-2" name="value" keyPath="valueToDisplay.playCount" id="Mpe-FA-gRG"/>
<binding destination="-2" name="toolTip" keyPath="valueToDisplay.playCountInfo" id="ydF-ec-fBX"/>
</connections>
</textField>
</subviews>

View File

@ -21,21 +21,21 @@
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<value key="minSize" type="size" width="750" height="200"/>
<stackView key="contentView" distribution="equalSpacing" orientation="vertical" alignment="centerX" spacing="0.0" detachesHiddenViews="YES" id="2">
<rect key="frame" x="0.0" y="0.0" width="1000" height="400"/>
<rect key="frame" x="0.0" y="0.0" width="1015" height="400"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<splitView dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2123">
<rect key="frame" x="0.0" y="345" width="1000" height="55"/>
<rect key="frame" x="0.0" y="350" width="1015" height="50"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="0.0" verticalLineScroll="24" verticalPageScroll="0.0" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="206" userLabel="Scroll View - Playlist View">
<rect key="frame" x="0.0" y="0.0" width="1000" height="55"/>
<rect key="frame" x="0.0" y="0.0" width="1015" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KWC-Ti-8KY">
<rect key="frame" x="0.0" y="0.0" width="1000" height="55"/>
<rect key="frame" x="0.0" y="0.0" width="1015" height="50"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="Playlist" rowHeight="18" headerView="1517" viewBased="YES" id="207" customClass="PlaylistView">
<rect key="frame" x="0.0" y="0.0" width="1000" height="38"/>
<rect key="frame" x="0.0" y="0.0" width="1015" height="33"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="6"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -229,11 +229,11 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="gpC-Oe-Rog">
<rect key="frame" x="235" y="3" width="150" height="18"/>
<rect key="frame" x="234.5" y="3" width="149" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="1WK-qN-Mgj">
<rect key="frame" x="0.0" y="1" width="150" height="16"/>
<rect key="frame" x="0.0" y="1" width="149" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="71l-3L-S3g">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -273,7 +273,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="1ed-gX-bct">
<rect key="frame" x="388" y="3" width="151" height="18"/>
<rect key="frame" x="387" y="3" width="151" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="nEt-s5-vRX">
@ -316,11 +316,11 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="hhB-nv-e78">
<rect key="frame" x="542" y="3" width="96" height="18"/>
<rect key="frame" x="540.5" y="3" width="95" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tHy-sM-HDB">
<rect key="frame" x="0.0" y="1" width="96" height="16"/>
<rect key="frame" x="0.0" y="1" width="95" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Igo-5f-yim">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -360,7 +360,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="q93-oh-i5T">
<rect key="frame" x="641" y="3" width="94" height="18"/>
<rect key="frame" x="639" y="3" width="94" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="bOi-LI-TDx">
@ -401,7 +401,7 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="rRl-p9-Awr">
<rect key="frame" x="738" y="3" width="144" height="18"/>
<rect key="frame" x="735.5" y="3" width="144" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="yW6-2w-6mN">
@ -441,11 +441,11 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="hgh-VE-5kl">
<rect key="frame" x="885" y="3" width="39" height="18"/>
<rect key="frame" x="882.5" y="3" width="38" height="18"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="yEY-MI-d3o">
<rect key="frame" x="0.0" y="1" width="39" height="16"/>
<rect key="frame" x="0.0" y="1" width="38" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="tus-lr-RhS">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -628,7 +628,7 @@
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="1517">
<rect key="frame" x="0.0" y="0.0" width="1000" height="17"/>
<rect key="frame" x="0.0" y="0.0" width="1015" height="17"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
@ -641,7 +641,7 @@
</connections>
</splitView>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="778">
<rect key="frame" x="370" y="4" width="261" height="14"/>
<rect key="frame" x="377" y="4" width="261" height="14"/>
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" alignment="center" title="Total Duration: 00 hours 00 minutes 00 seconds" bezelStyle="round" id="1473">
<font key="font" metaFont="controlContent" size="11"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>

View File

@ -370,10 +370,16 @@
</customObject>
<customObject id="tm7-TU-wbV" customClass="GeneralPane">
<connections>
<outlet property="sandboxBehaviorController" destination="9mC-3k-IQG" id="QzA-u4-vH7"/>
<outlet property="pathSuggester" destination="Vx8-Va-nme" id="UWw-bx-h7Z"/>
<outlet property="sandboxPathBehaviorController" destination="9mC-3k-IQG" id="xNT-x0-lVA"/>
<outlet property="view" destination="0nK-XQ-5MY" id="TDK-Vn-N5G"/>
</connections>
</customObject>
<customObject id="Vx8-Va-nme" customClass="PathSuggester">
<connections>
<outlet property="sandboxPathBehaviorController" destination="9mC-3k-IQG" id="1Jd-9v-4rm"/>
</connections>
</customObject>
<arrayController id="59" userLabel="OutputDevices" customClass="OutputsArrayController">
<declaredKeys>
<string>name</string>
@ -426,14 +432,14 @@
<rect key="frame" x="1" y="1" width="488" height="113"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" headerView="9rQ-Rq-K6J" id="gHG-xw-OyR">
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" rowSizeStyle="automatic" headerView="9rQ-Rq-K6J" viewBased="YES" id="gHG-xw-OyR">
<rect key="frame" x="0.0" y="0.0" width="488" height="85"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="388" minWidth="40" maxWidth="1000" id="Yz2-Ee-bZ0" userLabel="path">
<tableColumn identifier="path" width="388" minWidth="40" maxWidth="1000" id="Yz2-Ee-bZ0" userLabel="path">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Path">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
@ -444,11 +450,31 @@
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="EEt-uJ-j6o">
<rect key="frame" x="8" y="0.0" width="386" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="7l9-R2-FVF">
<rect key="frame" x="0.0" y="4" width="386" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="7un-em-D8r">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="7l9-R2-FVF" id="Yha-df-BGK"/>
</connections>
</tableCellView>
</prototypeCellViews>
<connections>
<binding destination="9mC-3k-IQG" name="value" keyPath="arrangedObjects.path" id="eva-uV-ce2"/>
</connections>
</tableColumn>
<tableColumn width="41" minWidth="40" maxWidth="1000" id="lEg-E3-TBs" userLabel="valid">
<tableColumn identifier="valid" width="41" minWidth="40" maxWidth="1000" id="lEg-E3-TBs" userLabel="valid">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Valid">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
@ -459,11 +485,35 @@
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="EDi-gi-Vg9">
<rect key="frame" x="411" y="0.0" width="38" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="0TJ-dK-Rfk">
<rect key="frame" x="0.0" y="4" width="38" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="LwT-Qb-47r">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="0TJ-dK-Rfk" id="kDn-6g-04J"/>
</connections>
</tableCellView>
</prototypeCellViews>
<connections>
<binding destination="9mC-3k-IQG" name="value" keyPath="arrangedObjects.valid" id="Kg6-FI-Mcl"/>
</connections>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="9mC-3k-IQG" id="2X9-U5-GJP"/>
<outlet property="delegate" destination="tm7-TU-wbV" id="h2y-Ql-eEn"/>
</connections>
</tableView>
</subviews>
</clipView>
@ -780,12 +830,25 @@
<action selector="addPath:" target="tm7-TU-wbV" id="j3o-oG-PA4"/>
</connections>
</menuItem>
<menuItem title="Delete Paths" id="nva-pz-xSS">
<menuItem title="Delete Path" id="nva-pz-xSS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="deleteSelectedPaths:" target="tm7-TU-wbV" id="suU-Gb-vMn"/>
</connections>
</menuItem>
<menuItem title="Delete Invalid Paths" id="JU5-o7-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="removeStaleEntries:" target="tm7-TU-wbV" id="j5v-pf-4FC"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="QVM-CG-8Ga"/>
<menuItem title="Suggest Paths" id="qjI-dK-nfG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="showPathSuggester:" target="tm7-TU-wbV" id="TnR-Di-6Ys"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="533" y="-395"/>
</menu>

View File

@ -7,16 +7,20 @@
#import "GeneralPreferencePane.h"
#import "PathSuggester.h"
#import "SandboxPathBehaviorController.h"
NS_ASSUME_NONNULL_BEGIN
@interface GeneralPane : GeneralPreferencePane {
IBOutlet SandboxPathBehaviorController *sandboxBehaviorController;
@interface GeneralPane : GeneralPreferencePane <NSTableViewDelegate> {
IBOutlet SandboxPathBehaviorController *sandboxPathBehaviorController;
IBOutlet PathSuggester *pathSuggester;
}
- (IBAction)addPath:(id)sender;
- (IBAction)deleteSelectedPaths:(id)sender;
- (IBAction)removeStaleEntries:(id)sender;
- (IBAction)showPathSuggester:(id)sender;
@end

View File

@ -7,6 +7,8 @@
#import "GeneralPane.h"
#import "PathSuggester.h"
@implementation GeneralPane
- (NSString *)title {
@ -27,18 +29,80 @@
[panel setFloatingPanel:YES];
NSInteger result = [panel runModal];
if(result == NSModalResponseOK) {
[sandboxBehaviorController addUrl:[panel URL]];
[sandboxPathBehaviorController addUrl:[panel URL]];
}
}
- (IBAction)deleteSelectedPaths:(id)sender {
NSArray *selectedObjects = [sandboxBehaviorController selectedObjects];
NSArray *selectedObjects = [sandboxPathBehaviorController selectedObjects];
if(selectedObjects && [selectedObjects count]) {
NSArray *paths = [selectedObjects valueForKey:@"path"];
for(NSString *path in paths) {
[sandboxBehaviorController removePath:path];
[sandboxPathBehaviorController removePath:path];
}
}
}
- (IBAction)removeStaleEntries:(id)sender {
[sandboxPathBehaviorController removeStaleEntries];
}
- (NSView *_Nullable)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *_Nullable)tableColumn row:(NSInteger)row {
NSString *cellIdentifier = @"";
NSTextAlignment cellTextAlignment = NSTextAlignmentLeft;
NSDictionary *item = [[sandboxPathBehaviorController arrangedObjects] objectAtIndex:row];
float fontSize = [[[NSUserDefaultsController sharedUserDefaultsController] defaults] floatForKey:@"fontSize"];
NSString *cellText = @"";
if(item) {
cellIdentifier = [tableColumn identifier];
if([cellIdentifier isEqualToString:@"path"]) {
cellText = [item objectForKey:@"path"];
} else if([cellIdentifier isEqualToString:@"valid"]) {
cellText = [item objectForKey:@"valid"];
}
}
NSView *view = [tableView makeViewWithIdentifier:cellIdentifier owner:nil];
if(view) {
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;
}
}
}
return view;
}
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
SEL action = [menuItem action];
if(action == @selector(addPath:) ||
action == @selector(deleteSelectedPaths:) ||
action == @selector(removeStaleEntries:) ||
action == @selector(showPathSuggester:)) {
return YES;
}
return NO;
}
- (IBAction)showPathSuggester:(id)sender {
[pathSuggester beginSuggestion:sender];
}
@end

View File

@ -0,0 +1,23 @@
//
// PathSuggester.h
// Preferences
//
// Created by Christopher Snowhill on 6/21/22.
//
#import <Cocoa/Cocoa.h>
#import "SandboxPathBehaviorController.h"
NS_ASSUME_NONNULL_BEGIN
@interface PathSuggester : NSWindowController <NSTableViewDelegate> {
IBOutlet SandboxPathBehaviorController *sandboxPathBehaviorController;
IBOutlet NSArrayController *pathsList;
}
- (IBAction)beginSuggestion:(id)sender;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,200 @@
//
// PathSuggester.m
// Preferences
//
// Created by Christopher Snowhill on 6/21/22.
//
#import "PathSuggester.h"
#import "PlaylistController.h"
#import "SandboxBroker.h"
// Sync, only declare items we need
@interface PlaylistEntry
@property(nonatomic) NSURL *_Nullable url;
@end
static NSURL *defaultMusicDirectory(void) {
return [[NSFileManager defaultManager] URLForDirectory:NSMusicDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:nil];
}
static NSURL *defaultDownloadsDirectory(void) {
return [[NSFileManager defaultManager] URLForDirectory:NSDownloadsDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:nil];
}
static NSURL *defaultMoviesDirectory(void) {
return [[NSFileManager defaultManager] URLForDirectory:NSMoviesDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:nil];
}
@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;
NSURL *defaultMusic = defaultMusicDirectory();
NSURL *defaultDownloads = defaultDownloadsDirectory();
NSURL *defaultMovies = defaultMoviesDirectory();
id sandboxBrokerClass = NSClassFromString(@"SandboxBroker");
NSMutableArray *items = [[NSMutableArray alloc] init];
NSMutableArray *itemPaths = [[NSMutableArray alloc] init];
for(PlaylistEntry *pe in results) {
NSURL *url = [sandboxBrokerClass urlWithoutFragment:pe.url];
if([sandboxBrokerClass isPath:url aSubdirectoryOf:defaultMusic] ||
[sandboxBrokerClass isPath:url
aSubdirectoryOf:defaultDownloads] ||
[sandboxBrokerClass isPath:url
aSubdirectoryOf:defaultMovies] ||
[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) {
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

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="PathSuggester">
<connections>
<outlet property="pathsList" destination="co7-fb-ZfR" id="Gfj-wX-lfj"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<view key="contentView" id="se5-gp-TjO">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="uJf-BX-nGb">
<rect key="frame" x="395" y="13" width="69" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Apply" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Jne-2G-yP9">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="applyPaths:" target="-2" id="aP1-hj-jCO"/>
</connections>
</button>
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qDP-bP-gt0">
<rect key="frame" x="20" y="53" width="440" height="197"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="o8s-wX-Hz7">
<rect key="frame" x="1" y="1" width="438" height="195"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" rowSizeStyle="automatic" headerView="0f4-Mi-48L" viewBased="YES" id="12l-iE-HGP">
<rect key="frame" x="0.0" y="0.0" width="438" height="167"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="enabled" width="51" minWidth="40" maxWidth="1000" id="QWk-Qb-l0y">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Enabled">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="w2T-DH-7x9">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="IEa-52-JSd">
<rect key="frame" x="8" y="0.0" width="49" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JEu-SS-Ht2">
<rect key="frame" x="0.0" y="4" width="49" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="RLN-Pa-Mhl">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="JEu-SS-Ht2" id="nHb-KP-VcE"/>
</connections>
</tableCellView>
</prototypeCellViews>
<connections>
<binding destination="co7-fb-ZfR" name="value" keyPath="arrangedObjects.enabled" id="AYu-Hb-5Oz"/>
</connections>
</tableColumn>
<tableColumn identifier="path" width="336.5" minWidth="40" maxWidth="1000" id="23r-ai-XoX">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Path">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="2vN-j2-JYP">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView id="Myn-dz-D1X">
<rect key="frame" x="74" y="0.0" width="334" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3sg-yr-7pE">
<rect key="frame" x="0.0" y="4" width="334" height="16"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="xFm-ed-yBb">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<connections>
<outlet property="textField" destination="3sg-yr-7pE" id="QRi-Ud-o8O"/>
</connections>
</tableCellView>
</prototypeCellViews>
<connections>
<binding destination="co7-fb-ZfR" name="value" keyPath="arrangedObjects.path" id="6z5-hZ-eM6"/>
</connections>
</tableColumn>
</tableColumns>
<connections>
<outlet property="dataSource" destination="co7-fb-ZfR" id="fsu-oW-sXa"/>
<outlet property="delegate" destination="-2" id="e4J-bb-n4Z"/>
</connections>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Uts-zz-KoA">
<rect key="frame" x="1" y="181" width="438" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="UOh-zJ-0eC">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="0f4-Mi-48L">
<rect key="frame" x="0.0" y="0.0" width="438" height="28"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
</scrollView>
</subviews>
</view>
<connections>
<outlet property="delegate" destination="-2" id="0bl-1N-AYu"/>
</connections>
<point key="canvasLocation" x="139" y="90"/>
</window>
<arrayController id="co7-fb-ZfR" userLabel="Paths Array Controller">
<declaredKeys>
<string>path</string>
<string>enabled</string>
</declaredKeys>
</arrayController>
</objects>
</document>

View File

@ -27,6 +27,8 @@
837C0D401C50954000CAE18F /* MIDIPluginBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837C0D3F1C50954000CAE18F /* MIDIPluginBehaviorArrayController.m */; };
8384917718084D9F00E7332D /* appearance.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384917518084D9F00E7332D /* appearance.png */; };
83A3B72C283AE04800CC6593 /* ColorToValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A3B72B283AE04800CC6593 /* ColorToValueTransformer.m */; };
83AC573A2861A54D009D6F50 /* PathSuggester.m in Sources */ = {isa = PBXBuildFile; fileRef = 83AC57382861A54D009D6F50 /* PathSuggester.m */; };
83AC573B2861A54D009D6F50 /* PathSuggester.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83AC57392861A54D009D6F50 /* PathSuggester.xib */; };
83B06729180D85B8008E3612 /* MIDIPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 83B06728180D85B8008E3612 /* MIDIPane.m */; };
83B0672B180D8B39008E3612 /* midi.png in Resources */ = {isa = PBXBuildFile; fileRef = 83B0672A180D8B39008E3612 /* midi.png */; };
83EF495F17FBC96A00642E3C /* VolumeBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */; };
@ -118,6 +120,10 @@
8384917518084D9F00E7332D /* appearance.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = appearance.png; path = Icons/appearance.png; sourceTree = "<group>"; };
83A3B72A283AE04800CC6593 /* ColorToValueTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ColorToValueTransformer.h; sourceTree = "<group>"; };
83A3B72B283AE04800CC6593 /* ColorToValueTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ColorToValueTransformer.m; sourceTree = "<group>"; };
83AC57372861A54D009D6F50 /* PathSuggester.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PathSuggester.h; sourceTree = "<group>"; };
83AC57382861A54D009D6F50 /* PathSuggester.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PathSuggester.m; sourceTree = "<group>"; };
83AC57392861A54D009D6F50 /* PathSuggester.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PathSuggester.xib; sourceTree = "<group>"; };
83AC573E2861B77E009D6F50 /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../Utils/SandboxBroker.h; sourceTree = "<group>"; };
83B06727180D85B8008E3612 /* MIDIPane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIPane.h; sourceTree = "<group>"; };
83B06728180D85B8008E3612 /* MIDIPane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIPane.m; sourceTree = "<group>"; };
83B0672A180D8B39008E3612 /* midi.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = midi.png; path = Icons/midi.png; sourceTree = "<group>"; };
@ -199,6 +205,7 @@
08FB77AFFE84173DC02AAC07 /* Classes */ = {
isa = PBXGroup;
children = (
83AC573E2861B77E009D6F50 /* SandboxBroker.h */,
8307D30828604ECF000FF8EB /* PlaylistController.h */,
83F27E711810E41A00CEF538 /* Transformers */,
8384913618081ECB00E7332D /* Logging.h */,
@ -266,6 +273,9 @@
83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */,
8307D30328604D8B000FF8EB /* SandboxPathBehaviorController.h */,
8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */,
83AC57372861A54D009D6F50 /* PathSuggester.h */,
83AC57382861A54D009D6F50 /* PathSuggester.m */,
83AC57392861A54D009D6F50 /* PathSuggester.xib */,
);
name = Custom;
sourceTree = "<group>";
@ -452,6 +462,7 @@
17E78A7E0D68BE3C005C5A59 /* file_tree.png in Resources */,
83F27E701810DD3A00CEF538 /* updates@2x.png in Resources */,
17E78B6A0D68C1E3005C5A59 /* Preferences.xib in Resources */,
83AC573B2861A54D009D6F50 /* PathSuggester.xib in Resources */,
83F27E6B1810DD3A00CEF538 /* appearance@2x.png in Resources */,
17C7E5B00DCCC30A003CBCF7 /* playlist.png in Resources */,
83F27E6E1810DD3A00CEF538 /* midi@2x.png in Resources */,
@ -465,6 +476,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
83AC573A2861A54D009D6F50 /* PathSuggester.m in Sources */,
83651DA527322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m in Sources */,
83B06729180D85B8008E3612 /* MIDIPane.m in Sources */,
8E07AA880AAC8EA200A4B32F /* HotKeyPane.m in Sources */,

View File

@ -13,6 +13,9 @@ NS_ASSUME_NONNULL_BEGIN
- (void)addUrl:(NSURL *)url;
- (void)removePath:(NSString *)path;
- (void)removeStaleEntries;
- (BOOL)matchesPath:(NSURL *)url;
@end

View File

@ -17,6 +17,8 @@
#import "PlaylistController.h"
#import "SandboxBroker.h"
@interface SandboxToken : NSManagedObject
@property(nonatomic, strong) NSString *path;
@property(nonatomic, strong) NSData *bookmark;
@ -102,4 +104,22 @@
}
}
- (void)removeStaleEntries {
for(NSDictionary *entry in [[self arrangedObjects] copy]) {
if([[entry objectForKey:@"valid"] isEqualToString:NSLocalizedPrefString(@"ValidNo")]) {
[self removeObject:entry];
}
}
}
- (BOOL)matchesPath:(NSURL *)url {
id sandboxBrokerClass = NSClassFromString(@"SandboxBroker");
for(NSDictionary *entry in [self arrangedObjects]) {
NSURL *testPath = [NSURL fileURLWithPath:[entry objectForKey:@"path"]];
if([sandboxBrokerClass isPath:url aSubdirectoryOf:testPath])
return YES;
}
return NO;
}
@end

View File

@ -15,6 +15,9 @@ NS_ASSUME_NONNULL_BEGIN
+ (SandboxBroker *)sharedSandboxBroker;
+ (NSURL *)urlWithoutFragment:(NSURL *)url;
+ (BOOL)isPath:(NSURL *)path aSubdirectoryOf:(NSURL *)directory;
- (id)init;
- (void)shutdown;

View File

@ -19,30 +19,6 @@
static SandboxBroker *kSharedSandboxBroker = nil;
static NSURL *urlWithoutFragment(NSURL *u) {
if(![u isFileURL]) return u;
NSNumber *isDirectory;
BOOL success = [u getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
if(success && [isDirectory boolValue]) return u;
NSString *s = [u path];
NSString *lastComponent = [u lastPathComponent];
// Find that last component in the string from the end to make sure
// to get the last one
NSRange fragmentRange = [s rangeOfString:lastComponent
options:NSBackwardsSearch];
// Chop the fragment.
NSString *newURLString = [s substringToIndex:fragmentRange.location + fragmentRange.length];
return [NSURL fileURLWithPath:newURLString];
}
@interface SandboxEntry : NSObject {
SandboxToken *_token;
NSInteger _refCount;
@ -110,6 +86,30 @@ static NSURL *urlWithoutFragment(NSURL *u) {
return [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
}
+ (NSURL *)urlWithoutFragment:(NSURL *)url {
if(![url isFileURL]) return url;
NSNumber *isDirectory;
BOOL success = [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
if(success && [isDirectory boolValue]) return url;
NSString *s = [url path];
NSString *lastComponent = [url lastPathComponent];
// Find that last component in the string from the end to make sure
// to get the last one
NSRange fragmentRange = [s rangeOfString:lastComponent
options:NSBackwardsSearch];
// Chop the fragment.
NSString *newURLString = [s substringToIndex:fragmentRange.location + fragmentRange.length];
return [NSURL fileURLWithPath:newURLString];
}
- (id)init {
id _self = [super init];
if(_self) {
@ -188,7 +188,7 @@ static NSURL *urlWithoutFragment(NSURL *u) {
}
- (const void *)beginFolderAccess:(NSURL *)fileUrl {
NSURL *folderUrl = [urlWithoutFragment(fileUrl) URLByDeletingLastPathComponent];
NSURL *folderUrl = [[SandboxBroker urlWithoutFragment:fileUrl] URLByDeletingLastPathComponent];
if(![folderUrl isFileURL]) return NULL;
SandboxEntry *entry;