diff --git a/Base.lproj/InfoInspector.xib b/Base.lproj/InfoInspector.xib index 0a19f3a1c..6482fa058 100644 --- a/Base.lproj/InfoInspector.xib +++ b/Base.lproj/InfoInspector.xib @@ -413,8 +413,8 @@ - + diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index e6f35502c..8c2051ed0 100644 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -21,21 +21,21 @@ - + - + - + - + - + @@ -229,11 +229,11 @@ - + - + @@ -273,7 +273,7 @@ - + @@ -316,11 +316,11 @@ - + - + @@ -360,7 +360,7 @@ - + @@ -401,7 +401,7 @@ - + @@ -441,11 +441,11 @@ - + - + @@ -628,7 +628,7 @@ - + @@ -641,7 +641,7 @@ - + diff --git a/Preferences/Preferences/Base.lproj/Preferences.xib b/Preferences/Preferences/Base.lproj/Preferences.xib index 74a5da364..1c4326df1 100644 --- a/Preferences/Preferences/Base.lproj/Preferences.xib +++ b/Preferences/Preferences/Base.lproj/Preferences.xib @@ -370,10 +370,16 @@ - + + + + + + + name @@ -426,14 +432,14 @@ - + - + @@ -444,11 +450,31 @@ + + + + + + + + + + + + + + + + + + + + - + @@ -459,11 +485,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -780,12 +830,25 @@ - + + + + + + + + + + + + + + diff --git a/Preferences/Preferences/GeneralPane.h b/Preferences/Preferences/GeneralPane.h index 7337f01b6..bb2249f84 100644 --- a/Preferences/Preferences/GeneralPane.h +++ b/Preferences/Preferences/GeneralPane.h @@ -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 { + IBOutlet SandboxPathBehaviorController *sandboxPathBehaviorController; + IBOutlet PathSuggester *pathSuggester; } - (IBAction)addPath:(id)sender; - (IBAction)deleteSelectedPaths:(id)sender; +- (IBAction)removeStaleEntries:(id)sender; +- (IBAction)showPathSuggester:(id)sender; @end diff --git a/Preferences/Preferences/GeneralPane.m b/Preferences/Preferences/GeneralPane.m index 6bd9a2e8c..fb10fae32 100644 --- a/Preferences/Preferences/GeneralPane.m +++ b/Preferences/Preferences/GeneralPane.m @@ -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 diff --git a/Preferences/Preferences/PathSuggester.h b/Preferences/Preferences/PathSuggester.h new file mode 100644 index 000000000..a0f33b40b --- /dev/null +++ b/Preferences/Preferences/PathSuggester.h @@ -0,0 +1,23 @@ +// +// PathSuggester.h +// Preferences +// +// Created by Christopher Snowhill on 6/21/22. +// + +#import + +#import "SandboxPathBehaviorController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface PathSuggester : NSWindowController { + IBOutlet SandboxPathBehaviorController *sandboxPathBehaviorController; + IBOutlet NSArrayController *pathsList; +} + +- (IBAction)beginSuggestion:(id)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Preferences/Preferences/PathSuggester.m b/Preferences/Preferences/PathSuggester.m new file mode 100644 index 000000000..38e321865 --- /dev/null +++ b/Preferences/Preferences/PathSuggester.m @@ -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 diff --git a/Preferences/Preferences/PathSuggester.xib b/Preferences/Preferences/PathSuggester.xib new file mode 100644 index 000000000..d179b56ec --- /dev/null +++ b/Preferences/Preferences/PathSuggester.xib @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + path + enabled + + + + diff --git a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj index 7c9a98180..7cbe446c8 100644 --- a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj +++ b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj @@ -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 = ""; }; 83A3B72A283AE04800CC6593 /* ColorToValueTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ColorToValueTransformer.h; sourceTree = ""; }; 83A3B72B283AE04800CC6593 /* ColorToValueTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ColorToValueTransformer.m; sourceTree = ""; }; + 83AC57372861A54D009D6F50 /* PathSuggester.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PathSuggester.h; sourceTree = ""; }; + 83AC57382861A54D009D6F50 /* PathSuggester.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PathSuggester.m; sourceTree = ""; }; + 83AC57392861A54D009D6F50 /* PathSuggester.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = PathSuggester.xib; sourceTree = ""; }; + 83AC573E2861B77E009D6F50 /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../Utils/SandboxBroker.h; sourceTree = ""; }; 83B06727180D85B8008E3612 /* MIDIPane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIPane.h; sourceTree = ""; }; 83B06728180D85B8008E3612 /* MIDIPane.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIPane.m; sourceTree = ""; }; 83B0672A180D8B39008E3612 /* midi.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = midi.png; path = Icons/midi.png; sourceTree = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/Preferences/Preferences/SandboxPathBehaviorController.h b/Preferences/Preferences/SandboxPathBehaviorController.h index bae40f2df..3ce4d57b5 100644 --- a/Preferences/Preferences/SandboxPathBehaviorController.h +++ b/Preferences/Preferences/SandboxPathBehaviorController.h @@ -13,6 +13,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)addUrl:(NSURL *)url; - (void)removePath:(NSString *)path; +- (void)removeStaleEntries; + +- (BOOL)matchesPath:(NSURL *)url; @end diff --git a/Preferences/Preferences/SandboxPathBehaviorController.m b/Preferences/Preferences/SandboxPathBehaviorController.m index eeb6c1eff..86a2101a3 100644 --- a/Preferences/Preferences/SandboxPathBehaviorController.m +++ b/Preferences/Preferences/SandboxPathBehaviorController.m @@ -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 diff --git a/Utils/SandboxBroker.h b/Utils/SandboxBroker.h index 6ce46d4c7..5f9812c3b 100644 --- a/Utils/SandboxBroker.h +++ b/Utils/SandboxBroker.h @@ -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; diff --git a/Utils/SandboxBroker.m b/Utils/SandboxBroker.m index 094518451..030bf422e 100644 --- a/Utils/SandboxBroker.m +++ b/Utils/SandboxBroker.m @@ -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;