Skip to content

Commit

Permalink
Modify code for download folder selection to work with sandboxing
Browse files Browse the repository at this point in the history
NSOpenSavePanelDelegate methods do not work with sandboxing. The URLs returned by the delegate callbacks are inaccessible until NSOpenPanel calls the completion handler. A workaround is to validate the URL after it was selected. When the URL is inaccessible (i.e. not writable) then an error is shown and the open panel reopens, giving the user the opportunity to choose a different directory or cancel.
  • Loading branch information
Eitot committed Jul 9, 2023
1 parent 929b494 commit c7f5ff6
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

@import Cocoa;

@interface GeneralPreferencesViewController : NSViewController <NSOpenSavePanelDelegate>
@interface GeneralPreferencesViewController : NSViewController

// Action functions
-(IBAction)changeCheckFrequency:(id)sender;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,20 +278,43 @@ -(IBAction)changeOpenLinksInExternalBrowser:(id)sender
* Bring up the folder browser to pick a new download folder.
*/
-(IBAction)changeDownloadFolder:(id)sender
{
NSFileManager *fileManager = NSFileManager.defaultManager;
[self chooseDirectoryWithRootDirectory:fileManager.vna_downloadsDirectory];
}

- (void)chooseDirectoryWithRootDirectory:(NSURL *)rootDirectoryURL
{
NSOpenPanel *openPanel = [NSOpenPanel openPanel];
openPanel.delegate = self;
openPanel.canChooseFiles = NO;
openPanel.canChooseDirectories = YES;
openPanel.canCreateDirectories = YES;
openPanel.allowsMultipleSelection = NO;
openPanel.prompt = NSLocalizedString(@"Select",
@"Label of a button on an open panel");

openPanel.directoryURL = NSFileManager.defaultManager.vna_downloadsDirectory;
openPanel.directoryURL = rootDirectoryURL;
[openPanel beginSheetModalForWindow:self.view.window
completionHandler:^(NSInteger returnCode) {
if (returnCode == NSModalResponseOK) {
completionHandler:^(NSModalResponse result) {
if (result == NSModalResponseOK && openPanel.URL) {
NSFileManager *fileManager = NSFileManager.defaultManager;
if (![fileManager isWritableFileAtPath:openPanel.URL.path]) {
NSString *str =
NSLocalizedString(@"This folder cannot be chosen because "
"you don’t have permission.",
@"Message text of a modal alert");
NSDictionary *userInfoDict = @{NSLocalizedDescriptionKey: str};
NSError *error =
[NSError errorWithDomain:NSCocoaErrorDomain
code:NSFileWriteNoPermissionError
userInfo:userInfoDict];
[self presentError:error
modalForWindow:self.view.window
delegate:self
didPresentSelector:@selector(didPresentErrorWithRecovery:contextInfo:)
contextInfo:(__bridge_retained void *)openPanel.URL];
return;
}

NSError *error = nil;
NSData *data = [VNASecurityScopedBookmark bookmarkDataFromFileURL:openPanel.URL
error:&error];
Expand All @@ -300,12 +323,22 @@ -(IBAction)changeDownloadFolder:(id)sender
[userDefaults setObject:data forKey:MAPref_DownloadsFolderBookmark];
[self updateDownloadsPopUp:openPanel.URL.path];
}
} else if (returnCode == NSModalResponseCancel) {
} else if (result == NSModalResponseCancel) {
[self->downloadFolder selectItemAtIndex:0];
}
}];
}

// Do not change this method signature without consulting the documentation of
// `-presentError:modalForWindow:delegate:didPresentSelector:contextInfo:`. If
// the signature does not match, the contextInfo parameter might not work.
- (void)didPresentErrorWithRecovery:(BOOL)didRecover
contextInfo:(nullable void *)contextInfo
{
NSURL *previousDirectoryURL = (__bridge_transfer NSURL *)contextInfo;
[self chooseDirectoryWithRootDirectory:previousDirectoryURL];
}

/* updateDownloadsPopUp
* Update the Downloads folder popup with the specified download folder path and image.
*/
Expand Down Expand Up @@ -445,28 +478,4 @@ -(void)dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

// MARK: - NSOpenSavePanelDelegate

- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url {
NSFileManager *fileManager = NSFileManager.defaultManager;
return [fileManager isWritableFileAtPath:url.path];
}

- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError {
NSFileManager *fileManager = NSFileManager.defaultManager;
BOOL isWritable = [fileManager isWritableFileAtPath:url.path];
if (!isWritable) {
NSString *str = NSLocalizedString(@"This folder cannot be chosen "
"because you don’t have permission.",
@"Message text of a modal alert");
NSDictionary *userInfoDict = @{NSLocalizedDescriptionKey: str};
if (outError) {
*outError = [NSError errorWithDomain:NSCocoaErrorDomain
code:NSFileWriteNoPermissionError
userInfo:userInfoDict];
}
}
return isWritable;
}

@end

0 comments on commit c7f5ff6

Please sign in to comment.