diff --git a/Application/AppController.m b/Application/AppController.m index 24f82a0aa..2f781769a 100644 --- a/Application/AppController.m +++ b/Application/AppController.m @@ -12,6 +12,7 @@ #import "PlaylistLoader.h" #import "PlaylistView.h" #import "SQLiteStore.h" +#import "SandboxBroker.h" #import "SpotlightWindowController.h" #import "StringToURLTransformer.h" #import @@ -189,6 +190,11 @@ BOOL kAppControllerShuttingDown = NO; } } + SandboxBroker *sandboxBroker = [SandboxBroker sharedSandboxBroker]; + if(!sandboxBroker) { + ALog(@"Sandbox broker init failed."); + } + [[playlistController undoManager] enableUndoRegistration]; int lastStatus = (int)[[NSUserDefaults standardUserDefaults] integerForKey:@"lastPlaybackStatus"]; @@ -434,6 +440,9 @@ BOOL kAppControllerShuttingDown = NO; [[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error]; + DLog(@"Shutting down sandbox broker"); + [[SandboxBroker sharedSandboxBroker] shutdown]; + DLog(@"Saving expanded nodes: %@", [expandedNodes description]); [[NSUserDefaults standardUserDefaults] setValue:[expandedNodes allObjects] forKey:@"fileTreeViewExpandedNodes"]; diff --git a/Application/PlaybackController.h b/Application/PlaybackController.h index 12e0e3f5e..56ad8a66c 100644 --- a/Application/PlaybackController.h +++ b/Application/PlaybackController.h @@ -3,7 +3,6 @@ #import #import "AppController.h" -#import "AudioScrobbler.h" #import "CogAudio/AudioPlayer.h" #import "CogAudio/Status.h" #import "TrackingSlider.h" @@ -15,6 +14,8 @@ #import "EqualizerWindowController.h" +#import "PlaylistEntry.h" + #define DEFAULT_VOLUME_DOWN 5 #define DEFAULT_VOLUME_UP DEFAULT_VOLUME_DOWN diff --git a/Application/PlaybackEventController.h b/Application/PlaybackEventController.h index 012c9ef0a..7998ed9ae 100644 --- a/Application/PlaybackEventController.h +++ b/Application/PlaybackEventController.h @@ -11,8 +11,6 @@ #import "PlaybackController.h" -@class AudioScrobbler; - @interface PlaybackEventController : NSObject { IBOutlet PlaybackController *playbackController; diff --git a/Application/PlaybackEventController.m b/Application/PlaybackEventController.m index 2cdef275c..a222792c9 100644 --- a/Application/PlaybackEventController.m +++ b/Application/PlaybackEventController.m @@ -7,8 +7,6 @@ #import "PlaybackEventController.h" -#import "AudioScrobbler.h" - #import "PlaylistEntry.h" NSString *TrackNotification = @"com.apple.iTunes.playerInfo"; @@ -27,8 +25,6 @@ typedef NS_ENUM(NSInteger, TrackStatus) { TrackPlaying, TrackStopped }; @implementation PlaybackEventController { - AudioScrobbler *scrobbler; - NSOperationQueue *queue; PlaylistEntry *entry; @@ -38,8 +34,6 @@ typedef NS_ENUM(NSInteger, TrackStatus) { TrackPlaying, - (void)initDefaults { NSDictionary *defaultsDictionary = @{ - @"enableAudioScrobbler": @YES, - @"automaticallyLaunchLastFM": @NO, @"notifications.enable": @YES, @"notifications.itunes-style": @YES, @"notifications.show-album-art": @YES @@ -85,7 +79,6 @@ typedef NS_ENUM(NSInteger, TrackStatus) { TrackPlaying, queue = [[NSOperationQueue alloc] init]; [queue setMaxConcurrentOperationCount:1]; - scrobbler = [[AudioScrobbler alloc] init]; [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; entry = nil; @@ -161,11 +154,6 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; if([defaults boolForKey:@"notifications.enable"]) { - if([defaults boolForKey:@"enableAudioScrobbler"]) { - [scrobbler start:pe]; - if([AudioScrobbler isRunning]) return; - } - if(@available(macOS 10.14, *)) { if(didGainUN) { UNUserNotificationCenter *center = @@ -300,9 +288,6 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response object:nil userInfo:[self fillNotificationDictionary:entry status:TrackPaused] deliverImmediately:YES]; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { - [scrobbler pause]; - } } - (void)performPlaybackDidResumeActions { @@ -311,9 +296,6 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response object:nil userInfo:[self fillNotificationDictionary:entry status:TrackPlaying] deliverImmediately:YES]; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { - [scrobbler resume]; - } } - (void)performPlaybackDidStopActions { @@ -323,9 +305,6 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response userInfo:[self fillNotificationDictionary:entry status:TrackStopped] deliverImmediately:YES]; entry = nil; - if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { - [scrobbler stop]; - } } - (void)awakeFromNib { diff --git a/AudioScrobbler/AudioScrobbler.h b/AudioScrobbler/AudioScrobbler.h deleted file mode 100644 index 3fb9928cc..000000000 --- a/AudioScrobbler/AudioScrobbler.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * $Id: AudioScrobbler.h 238 2007-01-26 22:55:20Z stephen_booth $ - * - * Copyright (C) 2006 - 2007 Stephen F. Booth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#import - -#import - -@class PlaylistEntry; - -@interface AudioScrobbler : NSObject { - NSString *_pluginID; - NSMutableArray *_queue; - - BOOL _audioScrobblerThreadCompleted; - BOOL _keepProcessingAudioScrobblerCommands; - semaphore_t _semaphore; -} - -+ (BOOL)isRunning; - -- (void)start:(PlaylistEntry *)pe; -- (void)stop; -- (void)pause; -- (void)resume; - -- (void)shutdown; - -@end diff --git a/AudioScrobbler/AudioScrobbler.m b/AudioScrobbler/AudioScrobbler.m deleted file mode 100644 index 96e10f884..000000000 --- a/AudioScrobbler/AudioScrobbler.m +++ /dev/null @@ -1,259 +0,0 @@ -/* - * $Id: AudioScrobbler.m 238 2007-01-26 22:55:20Z stephen_booth $ - * - * Copyright (C) 2006 - 2007 Stephen F. Booth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#import "AudioScrobbler.h" - -#import "AudioScrobblerClient.h" -#import "PlaylistEntry.h" - -#import "Logging.h" - -// ======================================== -// Symbolic Constants -// ======================================== -NSString *const AudioScrobblerRunLoopMode = @"org.cogx.Cog.AudioScrobbler.RunLoopMode"; - -// ======================================== -// Helpers -// ======================================== -static NSString * -escapeForLastFM(NSString *string) { - NSMutableString *result = [string mutableCopy]; - - [result replaceOccurrencesOfString:@"&" - withString:@"&&" - options:NSLiteralSearch - range:NSMakeRange(0, [result length])]; - - return (nil == result ? @"" : result); -} - -@interface AudioScrobbler (Private) - -- (NSMutableArray *)queue; -- (NSString *)pluginID; - -- (void)sendCommand:(NSString *)command; - -- (BOOL)keepProcessingAudioScrobblerCommands; -- (void)setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands; - -- (BOOL)audioScrobblerThreadCompleted; -- (void)setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted; - -- (semaphore_t)semaphore; - -- (void)processAudioScrobblerCommands:(id)unused; - -@end - -@implementation AudioScrobbler - -+ (BOOL)isRunning { - NSArray *launchedApps = [[NSWorkspace sharedWorkspace] runningApplications]; - BOOL running = NO; - for(NSRunningApplication *app in launchedApps) { - if([[app bundleIdentifier] isEqualToString:@"fm.last.Last.fm"] || - [[app bundleIdentifier] isEqualToString:@"fm.last.Scrobbler"]) { - running = YES; - break; - } - } - return running; -} - -- (id)init { - if((self = [super init])) { - _pluginID = @"cog"; - - if([[NSUserDefaults standardUserDefaults] boolForKey:@"automaticallyLaunchLastFM"]) { - if(![AudioScrobbler isRunning]) { - [[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"]; - } - } - - _keepProcessingAudioScrobblerCommands = YES; - - kern_return_t result = semaphore_create(mach_task_self(), &_semaphore, SYNC_POLICY_FIFO, 0); - - if(KERN_SUCCESS != result) { - ALog(@"Couldn't create semaphore (%s).", mach_error_type(result)); - - self = nil; - return nil; - } - - [NSThread detachNewThreadSelector:@selector(processAudioScrobblerCommands:) toTarget:self withObject:nil]; - } - return self; -} - -- (void)dealloc { - if([self keepProcessingAudioScrobblerCommands] || NO == [self audioScrobblerThreadCompleted]) - [self shutdown]; - - _queue = nil; - - semaphore_destroy(mach_task_self(), _semaphore); - _semaphore = 0; -} - -- (void)start:(PlaylistEntry *)pe { - [self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n", - [self pluginID], - escapeForLastFM([pe artist]), - escapeForLastFM([pe title]), - escapeForLastFM([pe album]), - @"", // TODO: MusicBrainz support - [[pe length] intValue], - escapeForLastFM([pe.url path])]]; -} - -- (void)stop { - [self sendCommand:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]]; -} - -- (void)pause { - [self sendCommand:[NSString stringWithFormat:@"PAUSE c=%@\n", [self pluginID]]]; -} - -- (void)resume { - [self sendCommand:[NSString stringWithFormat:@"RESUME c=%@\n", [self pluginID]]]; -} - -- (void)shutdown { - [self setKeepProcessingAudioScrobblerCommands:NO]; - semaphore_signal([self semaphore]); - - // Wait for the thread to terminate - while(NO == [self audioScrobblerThreadCompleted]) - [[NSRunLoop currentRunLoop] runMode:AudioScrobblerRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]]; -} - -@end - -@implementation AudioScrobbler (Private) - -- (NSMutableArray *)queue { - if(nil == _queue) - _queue = [[NSMutableArray alloc] init]; - - return _queue; -} - -- (NSString *)pluginID { - return _pluginID; -} - -- (void)sendCommand:(NSString *)command { - @synchronized([self queue]) { - [[self queue] addObject:command]; - } - semaphore_signal([self semaphore]); -} - -- (BOOL)keepProcessingAudioScrobblerCommands { - return _keepProcessingAudioScrobblerCommands; -} - -- (void)setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands { - _keepProcessingAudioScrobblerCommands = keepProcessingAudioScrobblerCommands; -} - -- (BOOL)audioScrobblerThreadCompleted { - return _audioScrobblerThreadCompleted; -} - -- (void)setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted { - _audioScrobblerThreadCompleted = audioScrobblerThreadCompleted; -} - -- (semaphore_t)semaphore { - return _semaphore; -} - -- (void)processAudioScrobblerCommands:(id)unused { - @autoreleasepool { - AudioScrobblerClient *client = [[AudioScrobblerClient alloc] init]; - mach_timespec_t timeout = { 5, 0 }; - NSString *command = nil; - NSString *response = nil; - in_port_t port = 33367; - - while([self keepProcessingAudioScrobblerCommands]) { - @autoreleasepool { - // Get the first command to be sent - @synchronized([self queue]) { - if([[self queue] count]) { - command = [[self queue] objectAtIndex:0]; - [[self queue] removeObjectAtIndex:0]; - } - } - - if(nil != command) { - @try { - if([client connectToHost:@"localhost" port:port]) { - port = [client connectedPort]; - [client send:command]; - command = nil; - - response = [client receive]; - if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0, 2)]) - ALog(@"AudioScrobbler error: %@", response); - - [client shutdown]; - } - } - - @catch(NSException *exception) { - command = nil; - - [client shutdown]; - // ALog(@"Exception: %@",exception); - continue; - } - } - - semaphore_timedwait([self semaphore], timeout); - } - } - - // Send a final stop command to cleanup - @try { - if([client connectToHost:@"localhost" port:port]) { - [client send:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]]; - - response = [client receive]; - if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0, 2)]) - ALog(@"AudioScrobbler error: %@", response); - - [client shutdown]; - } - } - - @catch(NSException *exception) { - [client shutdown]; - } - - [self setAudioScrobblerThreadCompleted:YES]; - } -} - -@end diff --git a/AudioScrobbler/AudioScrobblerClient.h b/AudioScrobbler/AudioScrobblerClient.h deleted file mode 100644 index f4b8beb95..000000000 --- a/AudioScrobbler/AudioScrobblerClient.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - * $Id: AudioScrobblerClient.h 666 2007-04-26 16:35:18Z stephen_booth $ - * - * Copyright (C) 2006 - 2007 Stephen F. Booth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#import - -#include - -@interface AudioScrobblerClient : NSObject { - int _socket; - BOOL _doPortStepping; - in_port_t _port; -} - -- (BOOL)connectToHost:(NSString *)hostname port:(in_port_t)port; - -- (BOOL)isConnected; -- (in_port_t)connectedPort; - -- (void)send:(NSString *)data; -- (NSString *)receive; - -- (void)shutdown; - -@end diff --git a/AudioScrobbler/AudioScrobblerClient.m b/AudioScrobbler/AudioScrobblerClient.m deleted file mode 100644 index 73e8cda2b..000000000 --- a/AudioScrobbler/AudioScrobblerClient.m +++ /dev/null @@ -1,212 +0,0 @@ -/* - * $Id: AudioScrobblerClient.m 869 2007-06-18 15:51:33Z stephen_booth $ - * - * Copyright (C) 2006 - 2007 Stephen F. Booth - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -/* - * This is a port of the BlockingClient client class from - * the Last.fm ScrobSub library by sharevari - */ - -#import "AudioScrobblerClient.h" - -#include - -#import "Logging.h" - -#define kBufferSize 1024 -#define kPortsToStep 5 - -static in_addr_t -addressForHost(NSString *hostname) { - NSCParameterAssert(nil != hostname); - - in_addr_t address; - struct hostent *hostinfo; - - address = inet_addr([hostname cStringUsingEncoding:NSASCIIStringEncoding]); - - if(INADDR_NONE == address) { - hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]); - if(NULL == hostinfo) { - ALog(@"AudioScrobblerClient error: Unable to resolve address for \"%@\".", hostname); - return INADDR_NONE; - } - - address = *((in_addr_t *)hostinfo->h_addr_list[0]); - } - - return address; -} - -@interface AudioScrobblerClient (Private) -- (BOOL)connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port; -@end - -@implementation AudioScrobblerClient - -- (id)init { - if((self = [super init])) { - _socket = -1; - _doPortStepping = YES; - } - return self; -} - -- (BOOL)connectToHost:(NSString *)hostname port:(in_port_t)port { - NSParameterAssert(nil != hostname); - - in_addr_t remoteAddress = addressForHost(hostname); - - if(INADDR_NONE != remoteAddress) - return [self connectToSocket:remoteAddress port:port]; - - return NO; -} - -- (BOOL)isConnected { - return (-1 != _socket); -} - -- (in_port_t)connectedPort { - return _port; -} - -- (void)send:(NSString *)data { - const char *utf8data = [data UTF8String]; - unsigned len = (unsigned int)strlen(utf8data); - unsigned bytesToSend = len; - unsigned totalBytesSent = 0; - ssize_t bytesSent = 0; - - if(NO == [self isConnected]) { - ALog(@"AudioScrobblerClient error: Can't send data, client not connected"); - return; - } - - while(totalBytesSent < bytesToSend && -1 != bytesSent) { - bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0); - - if(-1 == bytesSent || 0 == bytesSent) - ALog(@"AudioScrobblerClient error: Unable to send data through socket: %s", strerror(errno)); - - totalBytesSent += bytesSent; - } -} - -- (NSString *)receive { - char buffer[kBufferSize]; - int readSize = kBufferSize - 1; - ssize_t bytesRead = 0; - BOOL keepGoing = YES; - NSString *result = nil; - - if(NO == [self isConnected]) { - ALog(@"AudioScrobblerClient error: Can't receive data, client not connected"); - return nil; - } - - do { - bytesRead = recv(_socket, buffer, readSize, 0); - if(-1 == bytesRead || 0 == bytesRead) { - ALog(@"AudioScrobblerClient error: Unable to receive data through socket: %s", strerror(errno)); - break; - } - - if('\n' == buffer[bytesRead - 1]) { - --bytesRead; - keepGoing = NO; - } - - buffer[bytesRead] = '\0'; - result = [[NSString alloc] initWithUTF8String:buffer]; - - } while(keepGoing); - - return result; -} - -- (void)shutdown { - int result; - char buffer[kBufferSize]; - ssize_t bytesRead; - - if(NO == [self isConnected]) { - return; - } - - result = shutdown(_socket, SHUT_WR); - if(-1 == result) - ALog(@"AudioScrobblerClient error: Socket shutdown failed: %s", strerror(errno)); - - for(;;) { - bytesRead = recv(_socket, buffer, kBufferSize, 0); - if(-1 == bytesRead) - ALog(@"AudioScrobblerClient error: Waiting for shutdown confirmation failed: %s", strerror(errno)); - - if(0 != bytesRead) { - NSString *received = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding]; - ALog(@"Received unexpected bytes during shutdown: %@", received); - } else - break; - } - - result = close(_socket); - if(-1 == result) - ALog(@"Couldn't close socket (%s)", strerror(errno)); - - _socket = -1; - _port = 0; -} - -@end - -@implementation AudioScrobblerClient (Private) - -- (BOOL)connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port { - NSParameterAssert(INADDR_NONE != remoteAddress); - - _port = port; - - int result; - do { - struct sockaddr_in socketAddress; - - _socket = socket(AF_INET, SOCK_STREAM, 0); - if(-1 == _socket) { - ALog(@"Unable to create socket (%s)", strerror(errno)); - return NO; - } - - socketAddress.sin_family = AF_INET; - socketAddress.sin_addr.s_addr = remoteAddress; - socketAddress.sin_port = htons(_port); - - result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in)); - if(-1 == result) { - close(_socket); - _socket = -1; - - _port++; - } - } while(YES == _doPortStepping && -1 == result && _port < (port + kPortsToStep)); - - return (-1 != result); -} - -@end diff --git a/Base.lproj/MainMenu.xib b/Base.lproj/MainMenu.xib index ab25069f6..7595c945c 100644 --- a/Base.lproj/MainMenu.xib +++ b/Base.lproj/MainMenu.xib @@ -25,17 +25,17 @@ - + - + - + - + diff --git a/Cog.entitlements b/Cog.entitlements index d35e43ae5..56e8557bd 100644 --- a/Cog.entitlements +++ b/Cog.entitlements @@ -2,7 +2,19 @@ + com.apple.security.app-sandbox + + com.apple.security.assets.movies.read-only + + com.apple.security.assets.music.read-only + com.apple.security.cs.allow-jit + com.apple.security.files.downloads.read-only + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 9bbc98bb3..f04d2e11f 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -23,8 +23,6 @@ 173855FF0E0CC81F00488CD4 /* FileTreeOutlineView.m in Sources */ = {isa = PBXBuildFile; fileRef = 173855FE0E0CC81F00488CD4 /* FileTreeOutlineView.m */; }; 1752C36C0F59E00100F85F28 /* PlaybackButtons.m in Sources */ = {isa = PBXBuildFile; fileRef = 1752C36B0F59E00100F85F28 /* PlaybackButtons.m */; }; 1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 1755E1F70BA0D2B600CA3560 /* PlaylistLoader.m */; }; - 1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */; }; - 1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */; }; 1770429C0B8BC53600B86321 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 177042980B8BC53600B86321 /* AppController.m */; }; 1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1770429A0B8BC53600B86321 /* PlaybackController.m */; }; 1778D3B00F645A190037E7A0 /* missingArt.png in Resources */ = {isa = PBXBuildFile; fileRef = 1778D3AF0F645A190037E7A0 /* missingArt.png */; }; @@ -93,6 +91,7 @@ 56DB084C0D6717DC00453B6A /* NSNumber+CogSort.m in Sources */ = {isa = PBXBuildFile; fileRef = 56DB084B0D6717DC00453B6A /* NSNumber+CogSort.m */; }; 56DB08550D67185300453B6A /* NSArray+CogSort.m in Sources */ = {isa = PBXBuildFile; fileRef = 56DB08540D67185300453B6A /* NSArray+CogSort.m */; }; 8305963C277F013200EBFAAE /* File_Extractor.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83059639277F011100EBFAAE /* File_Extractor.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 8307D30E28606148000FF8EB /* SandboxBroker.m in Sources */ = {isa = PBXBuildFile; fileRef = 8307D30D28606148000FF8EB /* SandboxBroker.m */; }; 830C37A127B95E3000E02BB0 /* Equalizer.xib in Resources */ = {isa = PBXBuildFile; fileRef = 830C379F27B95E3000E02BB0 /* Equalizer.xib */; }; 830C37A527B95EB300E02BB0 /* EqualizerWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 830C37A427B95EB300E02BB0 /* EqualizerWindowController.m */; }; 830C37FC27B9956C00E02BB0 /* analyzer.c in Sources */ = {isa = PBXBuildFile; fileRef = 830C37F227B9956C00E02BB0 /* analyzer.c */; }; @@ -784,10 +783,6 @@ 1752C36B0F59E00100F85F28 /* PlaybackButtons.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PlaybackButtons.m; path = Window/PlaybackButtons.m; sourceTree = ""; }; 1755E1F60BA0D2B600CA3560 /* PlaylistLoader.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlaylistLoader.h; sourceTree = ""; }; 1755E1F70BA0D2B600CA3560 /* PlaylistLoader.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PlaylistLoader.m; sourceTree = ""; }; - 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobbler.h; sourceTree = ""; }; - 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobbler.m; sourceTree = ""; }; - 1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobblerClient.h; sourceTree = ""; }; - 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobblerClient.m; sourceTree = ""; }; 1770424E0B8BC41800B86321 /* Cog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 177042970B8BC53600B86321 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; 177042980B8BC53600B86321 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; @@ -909,6 +904,8 @@ 56DB08530D67185300453B6A /* NSArray+CogSort.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSArray+CogSort.h"; path = "Spotlight/NSArray+CogSort.h"; sourceTree = ""; }; 56DB08540D67185300453B6A /* NSArray+CogSort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+CogSort.m"; path = "Spotlight/NSArray+CogSort.m"; sourceTree = ""; }; 83059634277F011100EBFAAE /* File_Extractor.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = File_Extractor.xcodeproj; path = Frameworks/File_Extractor/File_Extractor.xcodeproj; sourceTree = ""; }; + 8307D30C28606148000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SandboxBroker.h; sourceTree = ""; }; + 8307D30D28606148000FF8EB /* SandboxBroker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SandboxBroker.m; sourceTree = ""; }; 830C37A027B95E3000E02BB0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Equalizer.xib; sourceTree = ""; }; 830C37A327B95EB300E02BB0 /* EqualizerWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EqualizerWindowController.h; path = Equalizer/EqualizerWindowController.h; sourceTree = ""; }; 830C37A427B95EB300E02BB0 /* EqualizerWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = EqualizerWindowController.m; path = Equalizer/EqualizerWindowController.m; sourceTree = ""; }; @@ -1136,7 +1133,6 @@ 830C37A227B95E6000E02BB0 /* Equalizer */, 17D1B0FE0F63252900694C57 /* InfoInspector */, 569C52C50D5F2BD500BDBDC9 /* Spotlight */, - 1766C68D0B911DF1004A7AE4 /* AudioScrobbler */, 17E0D5F60F520F42005B6FED /* Transformers */, 177EC0110B8BC2CF0000BC8C /* Utils */, 177EBF770B8BC2A70000BC8C /* ThirdParty */, @@ -1184,17 +1180,6 @@ name = "Other Frameworks"; sourceTree = ""; }; - 1766C68D0B911DF1004A7AE4 /* AudioScrobbler */ = { - isa = PBXGroup; - children = ( - 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */, - 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */, - 1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */, - 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */, - ); - path = AudioScrobbler; - sourceTree = ""; - }; 177042960B8BC53600B86321 /* Application */ = { isa = PBXGroup; children = ( @@ -1266,6 +1251,8 @@ 177EC01B0B8BC2CF0000BC8C /* TrackingCell.m */, 177EC01C0B8BC2CF0000BC8C /* TrackingSlider.h */, 177EC01D0B8BC2CF0000BC8C /* TrackingSlider.m */, + 8307D30C28606148000FF8EB /* SandboxBroker.h */, + 8307D30D28606148000FF8EB /* SandboxBroker.m */, ); path = Utils; sourceTree = ""; @@ -2610,10 +2597,8 @@ 177EC0290B8BC2CF0000BC8C /* TrackingSlider.m in Sources */, 1770429C0B8BC53600B86321 /* AppController.m in Sources */, 1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */, - 1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */, 8355D6B6180612F300D05687 /* NSData+MD5.m in Sources */, 8377C66327B8CF6300E8BC0F /* SpectrumViewSK.m in Sources */, - 1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */, 1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */, 8E9A30160BA792DC0091081B /* NSFileHandle+CreateFile.m in Sources */, 179790E10C087AB7001D6996 /* OpenURLPanel.m in Sources */, @@ -2668,6 +2653,7 @@ 171EFE8C0F59FEAE000ADC42 /* DockIconController.m in Sources */, 17F6C8070F603701000D9DA9 /* PlaybackEventController.m in Sources */, 83BC5AB220E4C87100631CD4 /* DualWindow.m in Sources */, + 8307D30E28606148000FF8EB /* SandboxBroker.m in Sources */, 83229C9F283B0095004626A8 /* SpectrumWindowController.m in Sources */, 835F00BB279BD1CD00055FCF /* SecondsFormatter.m in Sources */, 1784560F0F631E24007E8021 /* FileTreeViewController.m in Sources */, diff --git a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents index 5b7b5ee15..0ba1e36ce 100644 --- a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents +++ b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents @@ -60,9 +60,14 @@ + + + + - + + \ No newline at end of file diff --git a/Playlist/PlaylistController.h b/Playlist/PlaylistController.h index 9ecb104f4..8eb47be49 100644 --- a/Playlist/PlaylistController.h +++ b/Playlist/PlaylistController.h @@ -19,6 +19,10 @@ @class SpotlightWindowController; @class PlaybackController; +@interface NSApplication (CoreDataStorageExtension) +- (NSPersistentContainer *_Nonnull)sharedPersistentContainer; +@end + typedef NS_ENUM(NSInteger, RepeatMode) { RepeatModeNoRepeat = 0, RepeatModeRepeatOne, @@ -139,7 +143,7 @@ typedef NS_ENUM(NSInteger, URLOrigin) { - (IBAction)reloadTags:(id _Nullable)sender; // Play statistics -- (void)updatePlayCountForTrack:(PlaylistEntry *)pe; +- (void)updatePlayCountForTrack:(PlaylistEntry *_Nonnull)pe; - (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *_Nullable)indexSet toIndex:(NSUInteger)insertIndex; diff --git a/Playlist/PlaylistController.m b/Playlist/PlaylistController.m index 39d5687d4..68d7576e3 100644 --- a/Playlist/PlaylistController.m +++ b/Playlist/PlaylistController.m @@ -27,6 +27,14 @@ extern BOOL kAppControllerShuttingDown; +NSPersistentContainer *__persistentContainer = nil; + +@implementation NSApplication (CoreDataStorageExtension) +- (NSPersistentContainer *_Nonnull)sharedPersistentContainer { + return __persistentContainer; +} +@end + @implementation PlaylistController @synthesize currentEntry; @@ -35,7 +43,6 @@ extern BOOL kAppControllerShuttingDown; static NSArray *cellIdentifiers = nil; -NSPersistentContainer *__persistentContainer = nil; NSMutableDictionary *__artworkDictionary = nil; static void *playlistControllerContext = &playlistControllerContext; diff --git a/Plugins/ArchiveSource/ArchiveSource.xcodeproj/project.pbxproj b/Plugins/ArchiveSource/ArchiveSource.xcodeproj/project.pbxproj index 37ba1db6d..1eaa39a14 100644 --- a/Plugins/ArchiveSource/ArchiveSource.xcodeproj/project.pbxproj +++ b/Plugins/ArchiveSource/ArchiveSource.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8307D31A286070EA000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../../Utils/SandboxBroker.h; sourceTree = ""; }; 8359009717FEF6490060F3ED /* ArchiveSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveSource.h; sourceTree = ""; }; 8359009817FEF6490060F3ED /* ArchiveSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArchiveSource.m; sourceTree = ""; }; 8359009A17FEFDA80060F3ED /* ArchiveContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveContainer.h; sourceTree = ""; }; @@ -102,6 +103,7 @@ 8359FF2017FEF35C0060F3ED /* ArchiveSource */ = { isa = PBXGroup; children = ( + 8307D31A286070EA000FF8EB /* SandboxBroker.h */, 8384913518081BA000E7332D /* Logging.h */, 835900A017FF079C0060F3ED /* Plugin.h */, 8359009A17FEFDA80060F3ED /* ArchiveContainer.h */, diff --git a/Plugins/ArchiveSource/ArchiveSource/ArchiveContainer.m b/Plugins/ArchiveSource/ArchiveSource/ArchiveContainer.m index 181becc5a..9e1544c64 100644 --- a/Plugins/ArchiveSource/ArchiveSource/ArchiveContainer.m +++ b/Plugins/ArchiveSource/ArchiveSource/ArchiveContainer.m @@ -12,6 +12,8 @@ #import "Logging.h" +#import "SandboxBroker.h" + static NSString *path_pack_string(NSString *src) { return [NSString stringWithFormat:@"|%lu|%@|", [src length], src]; } @@ -43,6 +45,11 @@ static NSString *g_make_unpack_path(NSString *archive, NSString *file, NSString return @[]; } + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker beginFolderAccess:url]; + fex_t *fex; fex_err_t error = fex_open(&fex, [[url path] UTF8String]); if(error) { @@ -61,6 +68,8 @@ static NSString *g_make_unpack_path(NSString *archive, NSString *file, NSString fex_close(fex); + [sandboxBroker endFolderAccess:url]; + return files; } diff --git a/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.h b/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.h index 255e0f676..853ddfbfe 100644 --- a/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.h +++ b/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.h @@ -20,6 +20,7 @@ NSUInteger size; NSURL *_url; + NSURL *fileURL; } @end diff --git a/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.m b/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.m index 5573300f9..2b146afe0 100644 --- a/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.m +++ b/Plugins/ArchiveSource/ArchiveSource/ArchiveSource.m @@ -8,6 +8,8 @@ #import "ArchiveSource.h" +#import "SandboxBroker.h" + #import "Logging.h" static NSString *path_unpack_string(NSString *src, NSRange *remainder) { @@ -83,6 +85,13 @@ static BOOL g_parse_unpack_path(NSString *src, NSString **archive, NSString **fi if(![type isEqualToString:@"fex"]) return NO; + fileURL = [NSURL fileURLWithPath:archive]; + + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker beginFolderAccess:fileURL]; + fex_err_t error; error = fex_open(&fex, [archive UTF8String]); @@ -151,6 +160,13 @@ static BOOL g_parse_unpack_path(NSString *src, NSString **archive, NSString **fi fex_close(fex); fex = NULL; } + + if(fileURL) { + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker endFolderAccess:fileURL]; + } } - (NSURL *)url { diff --git a/Plugins/FileSource/FileSource.m b/Plugins/FileSource/FileSource.m index 5265e9604..bb350fe52 100644 --- a/Plugins/FileSource/FileSource.m +++ b/Plugins/FileSource/FileSource.m @@ -8,6 +8,8 @@ #import "FileSource.h" +#import "SandboxBroker.h" + @implementation FileSource + (void)initialize { @@ -26,6 +28,11 @@ - (BOOL)open:(NSURL *)url { [self setURL:url]; + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker beginFolderAccess:url]; + NSString *path = [url path]; fex_type_t type; @@ -123,6 +130,11 @@ fex_close(fex); fex = NULL; } + + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker endFolderAccess:_url]; } - (NSURL *)url { diff --git a/Plugins/FileSource/FileSource.xcodeproj/project.pbxproj b/Plugins/FileSource/FileSource.xcodeproj/project.pbxproj index 173d75d6b..d3e5150f2 100644 --- a/Plugins/FileSource/FileSource.xcodeproj/project.pbxproj +++ b/Plugins/FileSource/FileSource.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 17ADB4180B979AEB00257CA2 /* FileSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileSource.h; sourceTree = ""; }; 17ADB4190B979AEB00257CA2 /* FileSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileSource.m; sourceTree = ""; }; 32DBCF630370AF2F00C91783 /* FileSource_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileSource_Prefix.pch; sourceTree = ""; }; + 8307D31B2860722C000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../Utils/SandboxBroker.h; sourceTree = ""; }; 8335FF6817FF765A002D8DD2 /* File_Extractor.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = File_Extractor.xcodeproj; path = ../../Frameworks/File_Extractor/File_Extractor.xcodeproj; sourceTree = ""; }; 8D5B49B6048680CD000E48DA /* FileSource.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FileSource.bundle; sourceTree = BUILT_PRODUCTS_DIR; }; 8D5B49B7048680CD000E48DA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -88,6 +89,7 @@ 08FB77AFFE84173DC02AAC07 /* Classes */ = { isa = PBXGroup; children = ( + 8307D31B2860722C000FF8EB /* SandboxBroker.h */, 17ADB4080B979A8A00257CA2 /* Plugin.h */, 17ADB4180B979AEB00257CA2 /* FileSource.h */, 17ADB4190B979AEB00257CA2 /* FileSource.m */, diff --git a/Plugins/HTTPSource/HTTPSource.m b/Plugins/HTTPSource/HTTPSource.m index 850ea9b54..1ba58ab9b 100644 --- a/Plugins/HTTPSource/HTTPSource.m +++ b/Plugins/HTTPSource/HTTPSource.m @@ -463,6 +463,8 @@ static void http_stream_reset(HTTPSource *fp) { struct curl_slist *headers = NULL; struct curl_slist *ok_aliases = curl_slist_append(NULL, "ICY 200 OK"); + BOOL sslVerify = ![[[NSUserDefaultsController sharedUserDefaultsController] defaults] boolForKey:@"allowInsecureSSL"]; + curl_easy_reset(curl); curl_easy_setopt(curl, CURLOPT_URL, [[URL absoluteString] UTF8String]); NSString *ua = [NSString stringWithFormat:@"Cog/%@", [[[NSBundle mainBundle] infoDictionary] valueForKey:(__bridge NSString *)kCFBundleVersionKey]]; @@ -491,6 +493,7 @@ static void http_stream_reset(HTTPSource *fp) { if(pos > 0 && length >= 0) { curl_easy_setopt(curl, CURLOPT_RESUME_FROM, (long)pos); } + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long)sslVerify); // fp->status = STATUS_INITIAL; DLog(@"curl: calling curl_easy_perform (status=%d)...\n", self->status); gettimeofday(&last_read_time, NULL); diff --git a/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj b/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj index f962a1dd5..5c228d22d 100644 --- a/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj +++ b/Plugins/MIDI/MIDI.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 8307D31E28607377000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../../Utils/SandboxBroker.h; sourceTree = ""; }; 831E2A7F27B4B2B2006F1C86 /* bassmidi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bassmidi.h; sourceTree = ""; }; 831E2A8027B4B2B2006F1C86 /* sflist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sflist.c; sourceTree = ""; }; 831E2A8227B4B2B2006F1C86 /* sflist_rewrite.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sflist_rewrite.c; sourceTree = ""; }; @@ -295,6 +296,7 @@ 83B06690180D5668008E3612 /* MIDI */ = { isa = PBXGroup; children = ( + 8307D31E28607377000FF8EB /* SandboxBroker.h */, 831E2A9127B4B2FA006F1C86 /* json */, 831E2A7D27B4B2B2006F1C86 /* BASS */, 8356BCC427B352620074E50C /* BMPlayer.cpp */, diff --git a/Plugins/MIDI/MIDI/MIDIDecoder.h b/Plugins/MIDI/MIDI/MIDIDecoder.h index be54cd5bc..4f4ec49a1 100644 --- a/Plugins/MIDI/MIDI/MIDIDecoder.h +++ b/Plugins/MIDI/MIDI/MIDIDecoder.h @@ -24,6 +24,8 @@ class BMPlayer; MIDIPlayer* player; midi_container midi_file; + NSURL* sandboxURL; + NSString* globalSoundFontPath; BOOL soundFontsAssigned; BOOL isLooped; diff --git a/Plugins/MIDI/MIDI/MIDIDecoder.mm b/Plugins/MIDI/MIDI/MIDIDecoder.mm index 08a286bcb..deb844ad4 100644 --- a/Plugins/MIDI/MIDI/MIDIDecoder.mm +++ b/Plugins/MIDI/MIDI/MIDIDecoder.mm @@ -18,6 +18,8 @@ #import "PlaylistController.h" +#import "SandboxBroker.h" + #import static OSType getOSType(const char *in_) { @@ -165,6 +167,15 @@ static OSType getOSType(const char *in_) { globalSoundFontPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"soundFontPath"]; + if(globalSoundFontPath && [globalSoundFontPath length] > 0) { + sandboxURL = [NSURL fileURLWithPath:[globalSoundFontPath stringByDeletingLastPathComponent]]; + + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker beginFolderAccess:sandboxURL]; + } + // First detect if soundfont has gone AWOL if(![[NSFileManager defaultManager] fileExistsAtPath:globalSoundFontPath]) { globalSoundFontPath = nil; @@ -333,6 +344,15 @@ static OSType getOSType(const char *in_) { - (void)close { delete player; player = NULL; + + if(sandboxURL) { + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker endFolderAccess:sandboxURL]; + + sandboxURL = nil; + } } - (void)dealloc { diff --git a/Plugins/TagLib/TagLib.xcodeproj/project.pbxproj b/Plugins/TagLib/TagLib.xcodeproj/project.pbxproj index 3640e2593..5005dfd00 100644 --- a/Plugins/TagLib/TagLib.xcodeproj/project.pbxproj +++ b/Plugins/TagLib/TagLib.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 17C93FC30B90056C008627D6 /* TagLibMetadataReader.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C93FC20B90056C008627D6 /* TagLibMetadataReader.m */; }; 17F563B40C3BDBB30019975C /* TagLib.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17F563A60C3BDB8F0019975C /* TagLib.framework */; }; 17F563B60C3BDBB50019975C /* TagLib.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 17F563A60C3BDB8F0019975C /* TagLib.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 8307D31D286072BF000FF8EB /* SandboxBroker.h in Headers */ = {isa = PBXBuildFile; fileRef = 8307D31C286072BF000FF8EB /* SandboxBroker.h */; }; 8356BCE527B377C20074E50C /* TagLibID3v2Reader.h in Headers */ = {isa = PBXBuildFile; fileRef = 8356BCE327B377C20074E50C /* TagLibID3v2Reader.h */; }; 8356BCE627B377C20074E50C /* TagLibID3v2Reader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8356BCE427B377C20074E50C /* TagLibID3v2Reader.mm */; }; 8384913A18081FFC00E7332D /* Logging.h in Headers */ = {isa = PBXBuildFile; fileRef = 8384913918081FFC00E7332D /* Logging.h */; }; @@ -58,6 +59,7 @@ 17C93FC20B90056C008627D6 /* TagLibMetadataReader.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = TagLibMetadataReader.m; sourceTree = ""; }; 17F563A00C3BDB8F0019975C /* TagLib.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = TagLib.xcodeproj; path = ../../Frameworks/TagLib/TagLib.xcodeproj; sourceTree = SOURCE_ROOT; }; 32DBCF630370AF2F00C91783 /* TagLib_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TagLib_Prefix.pch; sourceTree = ""; }; + 8307D31C286072BF000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../Utils/SandboxBroker.h; sourceTree = ""; }; 8356BCE327B377C20074E50C /* TagLibID3v2Reader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TagLibID3v2Reader.h; sourceTree = ""; }; 8356BCE427B377C20074E50C /* TagLibID3v2Reader.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TagLibID3v2Reader.mm; sourceTree = ""; }; 8384913918081FFC00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; @@ -111,6 +113,7 @@ 08FB77AFFE84173DC02AAC07 /* Classes */ = { isa = PBXGroup; children = ( + 8307D31C286072BF000FF8EB /* SandboxBroker.h */, 8384913918081FFC00E7332D /* Logging.h */, 07CACE890ED1AD1000C0F1E8 /* TagLibMetadataWriter.h */, 07CACE8A0ED1AD1000C0F1E8 /* TagLibMetadataWriter.m */, @@ -174,6 +177,7 @@ buildActionMask = 2147483647; files = ( 8384913A18081FFC00E7332D /* Logging.h in Headers */, + 8307D31D286072BF000FF8EB /* SandboxBroker.h in Headers */, 8356BCE527B377C20074E50C /* TagLibID3v2Reader.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Plugins/TagLib/TagLibMetadataReader.m b/Plugins/TagLib/TagLibMetadataReader.m index 20f59a324..4fe014002 100644 --- a/Plugins/TagLib/TagLibMetadataReader.m +++ b/Plugins/TagLib/TagLibMetadataReader.m @@ -19,6 +19,8 @@ #import #import +#import "SandboxBroker.h" + @implementation TagLibMetadataReader + (NSDictionary *)metadataForURL:(NSURL *)url { @@ -26,6 +28,11 @@ return [NSDictionary dictionary]; } + id sandboxBrokerClass = NSClassFromString(@"SandboxBroker"); + id sandboxBroker = [sandboxBrokerClass sharedSandboxBroker]; + + [sandboxBroker beginFolderAccess:url]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; // if ( !*TagLib::ascii_encoding ) { @@ -230,6 +237,8 @@ } } + [sandboxBroker endFolderAccess:url]; + return dict; } diff --git a/Preferences/Preferences/Base.lproj/Preferences.xib b/Preferences/Preferences/Base.lproj/Preferences.xib index b8b80e12c..74a5da364 100644 --- a/Preferences/Preferences/Base.lproj/Preferences.xib +++ b/Preferences/Preferences/Base.lproj/Preferences.xib @@ -9,13 +9,13 @@ + - @@ -368,6 +368,12 @@ + + + + + + name @@ -389,45 +395,96 @@ - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -709,5 +766,28 @@ + + + path + valid + + + + + + + + + + + + + + + + + + + diff --git a/Preferences/Preferences/GeneralPane.h b/Preferences/Preferences/GeneralPane.h new file mode 100644 index 000000000..7337f01b6 --- /dev/null +++ b/Preferences/Preferences/GeneralPane.h @@ -0,0 +1,23 @@ +// +// GeneralPane.h +// Preferences +// +// Created by Christopher Snowhill on 6/20/22. +// + +#import "GeneralPreferencePane.h" + +#import "SandboxPathBehaviorController.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface GeneralPane : GeneralPreferencePane { + IBOutlet SandboxPathBehaviorController *sandboxBehaviorController; +} + +- (IBAction)addPath:(id)sender; +- (IBAction)deleteSelectedPaths:(id)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Preferences/Preferences/GeneralPane.m b/Preferences/Preferences/GeneralPane.m new file mode 100644 index 000000000..6bd9a2e8c --- /dev/null +++ b/Preferences/Preferences/GeneralPane.m @@ -0,0 +1,44 @@ +// +// GeneralPane.m +// Preferences +// +// Created by Christopher Snowhill on 6/20/22. +// + +#import "GeneralPane.h" + +@implementation GeneralPane + +- (NSString *)title { + return NSLocalizedPrefString(@"General"); +} + +- (NSImage *)icon { + if(@available(macOS 11.0, *)) + return [NSImage imageWithSystemSymbolName:@"gearshape.fill" accessibilityDescription:nil]; + return [[NSImage alloc] initWithContentsOfFile:[[NSBundle bundleForClass:[self class]] pathForImageResource:@"general"]]; +} + +- (IBAction)addPath:(id)sender { + NSOpenPanel *panel = [NSOpenPanel openPanel]; + [panel setAllowsMultipleSelection:NO]; + [panel setCanChooseDirectories:YES]; + [panel setCanChooseFiles:NO]; + [panel setFloatingPanel:YES]; + NSInteger result = [panel runModal]; + if(result == NSModalResponseOK) { + [sandboxBehaviorController addUrl:[panel URL]]; + } +} + +- (IBAction)deleteSelectedPaths:(id)sender { + NSArray *selectedObjects = [sandboxBehaviorController selectedObjects]; + if(selectedObjects && [selectedObjects count]) { + NSArray *paths = [selectedObjects valueForKey:@"path"]; + for(NSString *path in paths) { + [sandboxBehaviorController removePath:path]; + } + } +} + +@end diff --git a/Preferences/Preferences/GeneralPreferencesPlugin.h b/Preferences/Preferences/GeneralPreferencesPlugin.h index 67013dfb3..41e47441a 100644 --- a/Preferences/Preferences/GeneralPreferencesPlugin.h +++ b/Preferences/Preferences/GeneralPreferencesPlugin.h @@ -10,6 +10,7 @@ #import "PreferencePanePlugin.h" +#import "GeneralPane.h" #import "HotKeyPane.h" #import "MIDIPane.h" #import "OutputPane.h" @@ -18,9 +19,9 @@ IBOutlet HotKeyPane *hotKeyPane; IBOutlet OutputPane *outputPane; IBOutlet MIDIPane *midiPane; + IBOutlet GeneralPane *generalPane; IBOutlet NSView *playlistView; - IBOutlet NSView *scrobblerView; IBOutlet NSView *updatesView; IBOutlet NSView *notificationsView; IBOutlet NSView *appearanceView; @@ -31,9 +32,9 @@ - (HotKeyPane *)hotKeyPane; - (OutputPane *)outputPane; - (MIDIPane *)midiPane; +- (GeneralPane *)generalPane; - (GeneralPreferencePane *)updatesPane; -- (GeneralPreferencePane *)scrobblerPane; - (GeneralPreferencePane *)playlistPane; - (GeneralPreferencePane *)notificationsPane; - (GeneralPreferencePane *)appearancePane; diff --git a/Preferences/Preferences/GeneralPreferencesPlugin.m b/Preferences/Preferences/GeneralPreferencesPlugin.m index 1590aad4c..f9a55f5b7 100644 --- a/Preferences/Preferences/GeneralPreferencesPlugin.m +++ b/Preferences/Preferences/GeneralPreferencesPlugin.m @@ -32,7 +32,7 @@ [plugin hotKeyPane], [plugin updatesPane], [plugin outputPane], - [plugin scrobblerPane], + [plugin generalPane], [plugin notificationsPane], [plugin appearancePane], [plugin midiPane]]; @@ -50,6 +50,10 @@ return midiPane; } +- (GeneralPane *)generalPane { + return generalPane; +} + - (GeneralPreferencePane *)updatesPane { return [GeneralPreferencePane preferencePaneWithView:updatesView title:NSLocalizedPrefString(@"Updates") @@ -57,13 +61,6 @@ orOldIconNamed:@"updates"]; } -- (GeneralPreferencePane *)scrobblerPane { - return [GeneralPreferencePane preferencePaneWithView:scrobblerView - title:NSLocalizedPrefString(@"Scrobble") - systemIconName:@"dot.radiowaves.left.and.right" - orOldIconNamed:@"lastfm"]; -} - - (GeneralPreferencePane *)playlistPane { return [GeneralPreferencePane preferencePaneWithView:playlistView title:NSLocalizedPrefString(@"Playlist") diff --git a/Preferences/Preferences/Icons/appearance.png b/Preferences/Preferences/Icons/appearance.png index 1b4eba6af..5d59a5d1a 100644 Binary files a/Preferences/Preferences/Icons/appearance.png and b/Preferences/Preferences/Icons/appearance.png differ diff --git a/Preferences/Preferences/Icons/appearance@2x.png b/Preferences/Preferences/Icons/appearance@2x.png index 21fffb45b..de44f2684 100644 Binary files a/Preferences/Preferences/Icons/appearance@2x.png and b/Preferences/Preferences/Icons/appearance@2x.png differ diff --git a/Preferences/Preferences/Icons/file_tree.png b/Preferences/Preferences/Icons/file_tree.png index 86f57ad33..f8ebcfea7 100644 Binary files a/Preferences/Preferences/Icons/file_tree.png and b/Preferences/Preferences/Icons/file_tree.png differ diff --git a/Preferences/Preferences/Icons/general.png b/Preferences/Preferences/Icons/general.png new file mode 100644 index 000000000..92c491745 Binary files /dev/null and b/Preferences/Preferences/Icons/general.png differ diff --git a/Preferences/Preferences/Icons/general@2x.png b/Preferences/Preferences/Icons/general@2x.png new file mode 100644 index 000000000..3fbae3d5f Binary files /dev/null and b/Preferences/Preferences/Icons/general@2x.png differ diff --git a/Preferences/Preferences/Icons/growl.png b/Preferences/Preferences/Icons/growl.png index c8c0c972f..304e6b19a 100644 Binary files a/Preferences/Preferences/Icons/growl.png and b/Preferences/Preferences/Icons/growl.png differ diff --git a/Preferences/Preferences/Icons/hot_keys.png b/Preferences/Preferences/Icons/hot_keys.png index 419fd26e6..87485b7ea 100644 Binary files a/Preferences/Preferences/Icons/hot_keys.png and b/Preferences/Preferences/Icons/hot_keys.png differ diff --git a/Preferences/Preferences/Icons/lastfm.png b/Preferences/Preferences/Icons/lastfm.png deleted file mode 100644 index 6a76a1666..000000000 Binary files a/Preferences/Preferences/Icons/lastfm.png and /dev/null differ diff --git a/Preferences/Preferences/Icons/lastfm@2x.png b/Preferences/Preferences/Icons/lastfm@2x.png deleted file mode 100644 index 30df84b82..000000000 Binary files a/Preferences/Preferences/Icons/lastfm@2x.png and /dev/null differ diff --git a/Preferences/Preferences/Icons/midi.png b/Preferences/Preferences/Icons/midi.png index 1aba844b9..affb0377d 100644 Binary files a/Preferences/Preferences/Icons/midi.png and b/Preferences/Preferences/Icons/midi.png differ diff --git a/Preferences/Preferences/Icons/midi@2x.png b/Preferences/Preferences/Icons/midi@2x.png index 1a4032eb8..1832bf3af 100644 Binary files a/Preferences/Preferences/Icons/midi@2x.png and b/Preferences/Preferences/Icons/midi@2x.png differ diff --git a/Preferences/Preferences/Icons/output.png b/Preferences/Preferences/Icons/output.png index a0b1e8d63..33b12865a 100644 Binary files a/Preferences/Preferences/Icons/output.png and b/Preferences/Preferences/Icons/output.png differ diff --git a/Preferences/Preferences/Icons/playlist.png b/Preferences/Preferences/Icons/playlist.png index 1bb7afb0d..11fea3970 100644 Binary files a/Preferences/Preferences/Icons/playlist.png and b/Preferences/Preferences/Icons/playlist.png differ diff --git a/Preferences/Preferences/Icons/playlist@2x.png b/Preferences/Preferences/Icons/playlist@2x.png index 59108fc0a..a86eb1fb0 100644 Binary files a/Preferences/Preferences/Icons/playlist@2x.png and b/Preferences/Preferences/Icons/playlist@2x.png differ diff --git a/Preferences/Preferences/Icons/updates.png b/Preferences/Preferences/Icons/updates.png index 75de34724..6e94ba7c9 100644 Binary files a/Preferences/Preferences/Icons/updates.png and b/Preferences/Preferences/Icons/updates.png differ diff --git a/Preferences/Preferences/Icons/updates@2x.png b/Preferences/Preferences/Icons/updates@2x.png index 3bb63dc0d..e6160e1df 100644 Binary files a/Preferences/Preferences/Icons/updates@2x.png and b/Preferences/Preferences/Icons/updates@2x.png differ diff --git a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj index 35832c241..7c9a98180 100644 --- a/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj +++ b/Preferences/Preferences/Preferences.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 1766C7A80B912A71004A7AE4 /* lastfm.png in Resources */ = {isa = PBXBuildFile; fileRef = 1766C7A70B912A71004A7AE4 /* lastfm.png */; }; 178E386E0C3DA64500EE6711 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 178E386D0C3DA64500EE6711 /* InfoPlist.strings */; }; 17C643380B8A77CC00C53518 /* OutputsArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C643360B8A77CC00C53518 /* OutputsArrayController.m */; }; 17C6433F0B8A783F00C53518 /* OutputPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C6433E0B8A783F00C53518 /* OutputPane.m */; }; @@ -17,18 +16,21 @@ 17E41DB80C130AA500AC744D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 17E41DB70C130AA500AC744D /* Localizable.strings */; }; 17E78A7E0D68BE3C005C5A59 /* file_tree.png in Resources */ = {isa = PBXBuildFile; fileRef = 17E78A7D0D68BE3C005C5A59 /* file_tree.png */; }; 17E78B6A0D68C1E3005C5A59 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17E78B680D68C1E3005C5A59 /* Preferences.xib */; }; + 8307D30528604D8B000FF8EB /* SandboxPathBehaviorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */; }; + 8307D30B286057B5000FF8EB /* GeneralPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 8307D30A286057B5000FF8EB /* GeneralPane.m */; }; + 8307D31628606EAF000FF8EB /* general@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31228606EAF000FF8EB /* general@2x.png */; }; + 8307D31728606EAF000FF8EB /* growl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31328606EAF000FF8EB /* growl@2x.png */; }; + 8307D31828606EAF000FF8EB /* general.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31428606EAF000FF8EB /* general.png */; }; + 8307D31928606EAF000FF8EB /* growl.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31528606EAF000FF8EB /* growl.png */; }; 83651DA527322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83651DA327322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m */; }; 8372053718E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8372053618E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.m */; }; 837C0D401C50954000CAE18F /* MIDIPluginBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837C0D3F1C50954000CAE18F /* MIDIPluginBehaviorArrayController.m */; }; 8384917718084D9F00E7332D /* appearance.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384917518084D9F00E7332D /* appearance.png */; }; - 8384917818084D9F00E7332D /* growl.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384917618084D9F00E7332D /* growl.png */; }; 83A3B72C283AE04800CC6593 /* ColorToValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A3B72B283AE04800CC6593 /* ColorToValueTransformer.m */; }; 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 */; }; 83F27E6B1810DD3A00CEF538 /* appearance@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E651810DD3A00CEF538 /* appearance@2x.png */; }; - 83F27E6C1810DD3A00CEF538 /* growl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E661810DD3A00CEF538 /* growl@2x.png */; }; - 83F27E6D1810DD3A00CEF538 /* lastfm@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E671810DD3A00CEF538 /* lastfm@2x.png */; }; 83F27E6E1810DD3A00CEF538 /* midi@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E681810DD3A00CEF538 /* midi@2x.png */; }; 83F27E6F1810DD3A00CEF538 /* playlist@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E691810DD3A00CEF538 /* playlist@2x.png */; }; 83F27E701810DD3A00CEF538 /* updates@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E6A1810DD3A00CEF538 /* updates@2x.png */; }; @@ -78,7 +80,6 @@ 089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 089C167FFE841241C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; - 1766C7A70B912A71004A7AE4 /* lastfm.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = lastfm.png; path = Icons/lastfm.png; sourceTree = ""; }; 17C643360B8A77CC00C53518 /* OutputsArrayController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OutputsArrayController.m; sourceTree = ""; }; 17C643370B8A77CC00C53518 /* OutputsArrayController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputsArrayController.h; sourceTree = ""; }; 17C6433D0B8A783F00C53518 /* OutputPane.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputPane.h; sourceTree = ""; }; @@ -91,6 +92,15 @@ 32DBCF630370AF2F00C91783 /* Preferences_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences_Prefix.pch; sourceTree = ""; }; 3DFAC48E235B6B8100A29416 /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = ""; }; 3DFAC48F235B6B8100A29416 /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; + 8307D30328604D8B000FF8EB /* SandboxPathBehaviorController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SandboxPathBehaviorController.h; sourceTree = ""; }; + 8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SandboxPathBehaviorController.m; sourceTree = ""; }; + 8307D30828604ECF000FF8EB /* PlaylistController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlaylistController.h; path = ../../Playlist/PlaylistController.h; sourceTree = ""; }; + 8307D309286057B5000FF8EB /* GeneralPane.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneralPane.h; sourceTree = ""; }; + 8307D30A286057B5000FF8EB /* GeneralPane.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneralPane.m; sourceTree = ""; }; + 8307D31228606EAF000FF8EB /* general@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "general@2x.png"; path = "Icons/general@2x.png"; sourceTree = ""; }; + 8307D31328606EAF000FF8EB /* growl@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "growl@2x.png"; path = "Icons/growl@2x.png"; sourceTree = ""; }; + 8307D31428606EAF000FF8EB /* general.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = general.png; path = Icons/general.png; sourceTree = ""; }; + 8307D31528606EAF000FF8EB /* growl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = growl.png; path = Icons/growl.png; sourceTree = ""; }; 833F681B1CDBCAA700AFB9F0 /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; 833F681C1CDBCAA700AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; 8347435D20E6D58800063D45 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Preferences.strings; sourceTree = ""; }; @@ -106,7 +116,6 @@ 837C0D3F1C50954000CAE18F /* MIDIPluginBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIPluginBehaviorArrayController.m; sourceTree = ""; }; 8384913618081ECB00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = ""; }; 8384917518084D9F00E7332D /* appearance.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = appearance.png; path = Icons/appearance.png; sourceTree = ""; }; - 8384917618084D9F00E7332D /* growl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = growl.png; path = Icons/growl.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 = ""; }; 83B06727180D85B8008E3612 /* MIDIPane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIPane.h; sourceTree = ""; }; @@ -117,8 +126,6 @@ 83EF495D17FBC96A00642E3C /* VolumeBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VolumeBehaviorArrayController.h; sourceTree = ""; }; 83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VolumeBehaviorArrayController.m; sourceTree = ""; }; 83F27E651810DD3A00CEF538 /* appearance@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "appearance@2x.png"; path = "Icons/appearance@2x.png"; sourceTree = ""; }; - 83F27E661810DD3A00CEF538 /* growl@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "growl@2x.png"; path = "Icons/growl@2x.png"; sourceTree = ""; }; - 83F27E671810DD3A00CEF538 /* lastfm@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "lastfm@2x.png"; path = "Icons/lastfm@2x.png"; sourceTree = ""; }; 83F27E681810DD3A00CEF538 /* midi@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "midi@2x.png"; path = "Icons/midi@2x.png"; sourceTree = ""; }; 83F27E691810DD3A00CEF538 /* playlist@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "playlist@2x.png"; path = "Icons/playlist@2x.png"; sourceTree = ""; }; 83F27E6A1810DD3A00CEF538 /* updates@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "updates@2x.png"; path = "Icons/updates@2x.png"; sourceTree = ""; }; @@ -192,6 +199,7 @@ 08FB77AFFE84173DC02AAC07 /* Classes */ = { isa = PBXGroup; children = ( + 8307D30828604ECF000FF8EB /* PlaylistController.h */, 83F27E711810E41A00CEF538 /* Transformers */, 8384913618081ECB00E7332D /* Logging.h */, 83D34B7D27A10FA700784D34 /* HeadphoneFilter.h */, @@ -235,6 +243,8 @@ 17C6433E0B8A783F00C53518 /* OutputPane.m */, 83B06727180D85B8008E3612 /* MIDIPane.h */, 83B06728180D85B8008E3612 /* MIDIPane.m */, + 8307D309286057B5000FF8EB /* GeneralPane.h */, + 8307D30A286057B5000FF8EB /* GeneralPane.m */, ); name = Panes; sourceTree = ""; @@ -254,6 +264,8 @@ 8372053618E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.m */, 83EF495D17FBC96A00642E3C /* VolumeBehaviorArrayController.h */, 83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */, + 8307D30328604D8B000FF8EB /* SandboxPathBehaviorController.h */, + 8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */, ); name = Custom; sourceTree = ""; @@ -300,21 +312,21 @@ 8E07ABD90AAC95AF00A4B32F /* Icons */ = { isa = PBXGroup; children = ( - 83F27E651810DD3A00CEF538 /* appearance@2x.png */, - 83F27E661810DD3A00CEF538 /* growl@2x.png */, - 83F27E671810DD3A00CEF538 /* lastfm@2x.png */, - 83F27E681810DD3A00CEF538 /* midi@2x.png */, - 83F27E691810DD3A00CEF538 /* playlist@2x.png */, - 83F27E6A1810DD3A00CEF538 /* updates@2x.png */, - 83B0672A180D8B39008E3612 /* midi.png */, 8384917518084D9F00E7332D /* appearance.png */, - 8384917618084D9F00E7332D /* growl.png */, - 17C7E5AF0DCCC30A003CBCF7 /* playlist.png */, + 83F27E651810DD3A00CEF538 /* appearance@2x.png */, 17E78A7D0D68BE3C005C5A59 /* file_tree.png */, - 1766C7A70B912A71004A7AE4 /* lastfm.png */, - 17C643680B8A788000C53518 /* output.png */, - 8E15A86B0B894768006DC802 /* updates.png */, + 8307D31428606EAF000FF8EB /* general.png */, + 8307D31228606EAF000FF8EB /* general@2x.png */, + 8307D31528606EAF000FF8EB /* growl.png */, + 8307D31328606EAF000FF8EB /* growl@2x.png */, 8E07ABDB0AAC95BC00A4B32F /* hot_keys.png */, + 83B0672A180D8B39008E3612 /* midi.png */, + 83F27E681810DD3A00CEF538 /* midi@2x.png */, + 17C643680B8A788000C53518 /* output.png */, + 17C7E5AF0DCCC30A003CBCF7 /* playlist.png */, + 83F27E691810DD3A00CEF538 /* playlist@2x.png */, + 8E15A86B0B894768006DC802 /* updates.png */, + 83F27E6A1810DD3A00CEF538 /* updates@2x.png */, ); name = Icons; sourceTree = ""; @@ -425,21 +437,21 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 8307D31628606EAF000FF8EB /* general@2x.png in Resources */, 83B0672B180D8B39008E3612 /* midi.png in Resources */, - 83F27E6D1810DD3A00CEF538 /* lastfm@2x.png in Resources */, 178E386E0C3DA64500EE6711 /* InfoPlist.strings in Resources */, + 8307D31928606EAF000FF8EB /* growl.png in Resources */, 8E07ABDD0AAC95BC00A4B32F /* hot_keys.png in Resources */, - 8384917818084D9F00E7332D /* growl.png in Resources */, + 8307D31728606EAF000FF8EB /* growl@2x.png in Resources */, 8E15A86C0B894768006DC802 /* updates.png in Resources */, 8384917718084D9F00E7332D /* appearance.png in Resources */, 17C643690B8A788000C53518 /* output.png in Resources */, - 1766C7A80B912A71004A7AE4 /* lastfm.png in Resources */, 83F27E6F1810DD3A00CEF538 /* playlist@2x.png in Resources */, 17E41DB80C130AA500AC744D /* Localizable.strings in Resources */, + 8307D31828606EAF000FF8EB /* general.png in Resources */, 17E78A7E0D68BE3C005C5A59 /* file_tree.png in Resources */, 83F27E701810DD3A00CEF538 /* updates@2x.png in Resources */, 17E78B6A0D68C1E3005C5A59 /* Preferences.xib in Resources */, - 83F27E6C1810DD3A00CEF538 /* growl@2x.png in Resources */, 83F27E6B1810DD3A00CEF538 /* appearance@2x.png in Resources */, 17C7E5B00DCCC30A003CBCF7 /* playlist.png in Resources */, 83F27E6E1810DD3A00CEF538 /* midi@2x.png in Resources */, @@ -461,6 +473,8 @@ 8E07AA8A0AAC8EA200A4B32F /* GeneralPreferencesPlugin.m in Sources */, 83EF495F17FBC96A00642E3C /* VolumeBehaviorArrayController.m in Sources */, 17C643380B8A77CC00C53518 /* OutputsArrayController.m in Sources */, + 8307D30528604D8B000FF8EB /* SandboxPathBehaviorController.m in Sources */, + 8307D30B286057B5000FF8EB /* GeneralPane.m in Sources */, 83A3B72C283AE04800CC6593 /* ColorToValueTransformer.m in Sources */, 837C0D401C50954000CAE18F /* MIDIPluginBehaviorArrayController.m in Sources */, 17C6433F0B8A783F00C53518 /* OutputPane.m in Sources */, diff --git a/Preferences/Preferences/SandboxPathBehaviorController.h b/Preferences/Preferences/SandboxPathBehaviorController.h new file mode 100644 index 000000000..bae40f2df --- /dev/null +++ b/Preferences/Preferences/SandboxPathBehaviorController.h @@ -0,0 +1,19 @@ +// +// SandboxPathBehaviorController.h +// Preferences +// +// Created by Christopher Snowhill on 6/19/22. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SandboxPathBehaviorController : NSArrayController + +- (void)addUrl:(NSURL *)url; +- (void)removePath:(NSString *)path; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Preferences/Preferences/SandboxPathBehaviorController.m b/Preferences/Preferences/SandboxPathBehaviorController.m new file mode 100644 index 000000000..6eed712ad --- /dev/null +++ b/Preferences/Preferences/SandboxPathBehaviorController.m @@ -0,0 +1,113 @@ +// +// SandboxPathBehaviorController.m +// Preferences +// +// Created by Christopher Snowhill on 6/19/22. +// + +#import "PreferencePanePlugin.h" + +#import "SandboxPathBehaviorController.h" + +#import "PlaylistController.h" + +#import + +#import "Logging.h" + +#import "PlaylistController.h" + +@interface SandboxToken : NSManagedObject +@property(nonatomic, strong) NSString *path; +@property(nonatomic, strong) NSData *bookmark; +@end + +@implementation SandboxPathBehaviorController +- (void)awakeFromNib { + [self removeObjects:[self arrangedObjects]]; + + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path" ascending:YES]; + [self setSortDescriptors:@[sortDescriptor]]; + + if([NSApp respondsToSelector:@selector(sharedPersistentContainer)]) { + NSPersistentContainer *pc = [NSApp sharedPersistentContainer]; + + NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"]; + + NSError *error = nil; + NSArray *results = [pc.viewContext executeFetchRequest:request error:&error]; + + if(results && [results count] > 0) { + for(SandboxToken *token in results) { + BOOL isStale = YES; + NSError *err = nil; + NSURL *bookmarkUrl = [NSURL URLByResolvingBookmarkData:token.bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&err]; + if(!bookmarkUrl) { + ALog(@"Stale bookmark for path: %@, with error: %@", token.path, [err localizedDescription]); + continue; + } + [self addObject:@{ @"path": token.path, @"valid": (isStale ? NSLocalizedPrefString(@"ValidNo") : NSLocalizedPrefString(@"ValidYes")) }]; + } + } + } +} + +- (void)addUrl:(NSURL *)url { + NSError *err = nil; + NSData *bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&err]; + if(!bookmark && err) { + ALog(@"Failed to add bookmark for URL: %@, with error: %@", url, [err localizedDescription]); + return; + } + + if(![NSApp respondsToSelector:@selector(sharedPersistentContainer)]) + return; + + NSPersistentContainer *pc = [NSApp sharedPersistentContainer]; + + SandboxToken *token = [NSEntityDescription insertNewObjectForEntityForName:@"SandboxToken" inManagedObjectContext:pc.viewContext]; + + if(token) { + token.path = [url path]; + token.bookmark = bookmark; + [pc.viewContext save:&err]; + if(err) { + ALog(@"Error saving bookmark: %@", [err localizedDescription]); + [pc.viewContext deleteObject:token]; + } else { + [self addObject:@{ @"path": [url path], @"valid": NSLocalizedPrefString(@"ValidYes") }]; + } + } +} + +- (void)removePath:(NSString *)path { + if(![NSApp respondsToSelector:@selector(sharedPersistentContainer)]) + return; + + NSPersistentContainer *pc = [NSApp sharedPersistentContainer]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"path == %@", path]; + + NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"]; + request.predicate = predicate; + + NSError *error = nil; + NSArray *results = [pc.viewContext executeFetchRequest:request error:&error]; + + if(results && [results count] > 0) { + for(SandboxToken *token in results) { + [pc.viewContext deleteObject:token]; + } + } + + NSArray *objects = [self arrangedObjects]; + + for(NSDictionary *obj in objects) { + if([[obj objectForKey:@"path"] isEqualToString:path]) { + [self removeObject:obj]; + break; + } + } +} + +@end diff --git a/Preferences/Preferences/en.lproj/Localizable.strings b/Preferences/Preferences/en.lproj/Localizable.strings index 967314319..f452b06bd 100644 --- a/Preferences/Preferences/en.lproj/Localizable.strings +++ b/Preferences/Preferences/en.lproj/Localizable.strings @@ -8,13 +8,18 @@ "Updates" = "Updates"; "Last.fm" = "Last.fm"; +"Scrobble" = "Scrobble"; "Playlist" = "Playlist"; "Growl" = "Growl"; "Appearance" = "Appearance"; "MIDI" = "MIDI"; +"General" = "General"; "Press Key..." = "Press Key..."; "Clear playlist and play" = "Clear playlist and play"; "Enqueue" = "Enqueue"; -"Enqueue and play" = "Enqueue and play"; \ No newline at end of file +"Enqueue and play" = "Enqueue and play"; + +"ValidNo" = "No"; +"ValidYes" = "Yes"; diff --git a/Preferences/Preferences/es.lproj/Localizable.strings b/Preferences/Preferences/es.lproj/Localizable.strings index 967314319..f452b06bd 100644 --- a/Preferences/Preferences/es.lproj/Localizable.strings +++ b/Preferences/Preferences/es.lproj/Localizable.strings @@ -8,13 +8,18 @@ "Updates" = "Updates"; "Last.fm" = "Last.fm"; +"Scrobble" = "Scrobble"; "Playlist" = "Playlist"; "Growl" = "Growl"; "Appearance" = "Appearance"; "MIDI" = "MIDI"; +"General" = "General"; "Press Key..." = "Press Key..."; "Clear playlist and play" = "Clear playlist and play"; "Enqueue" = "Enqueue"; -"Enqueue and play" = "Enqueue and play"; \ No newline at end of file +"Enqueue and play" = "Enqueue and play"; + +"ValidNo" = "No"; +"ValidYes" = "Yes"; diff --git a/Preferences/PreferencesWindow.m b/Preferences/PreferencesWindow.m index 4d407f325..e3b76ae2d 100644 --- a/Preferences/PreferencesWindow.m +++ b/Preferences/PreferencesWindow.m @@ -134,8 +134,9 @@ if([lastPane isEqualToString:NSLocalizedPrefString(@"Growl")]) { lastPane = NSLocalizedPrefString(@"Notifications"); } - if([lastPane isEqualToString:NSLocalizedPrefString(@"Last.fm")]) { - lastPane = NSLocalizedPrefString(@"Scrobble"); + if([lastPane isEqualToString:NSLocalizedPrefString(@"Last.fm")] || + [lastPane isEqualToString:NSLocalizedPrefString(@"Scrobble")]) { + lastPane = NSLocalizedPrefString(@"General"); } if(nil == lastPane) { if(0 >= [preferencePaneOrder count]) { diff --git a/Utils/SandboxBroker.h b/Utils/SandboxBroker.h new file mode 100644 index 000000000..cc11e4ea3 --- /dev/null +++ b/Utils/SandboxBroker.h @@ -0,0 +1,26 @@ +// +// SandboxBroker.h +// Cog +// +// Created by Christopher Snowhill on 6/20/22. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface SandboxBroker : NSObject { + NSMutableArray *storage; +} + ++ (SandboxBroker *)sharedSandboxBroker; + +- (id)init; +- (void)shutdown; + +- (void)beginFolderAccess:(NSURL *)fileUrl; +- (void)endFolderAccess:(NSURL *)fileUrl; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Utils/SandboxBroker.m b/Utils/SandboxBroker.m new file mode 100644 index 000000000..750e66dd7 --- /dev/null +++ b/Utils/SandboxBroker.m @@ -0,0 +1,237 @@ +// +// SandboxBroker.m +// Cog +// +// Created by Christopher Snowhill on 6/20/22. +// + +#import + +#import + +#import "SandboxBroker.h" + +#import "Logging.h" + +#import "Cog-Swift.h" + +#import "PlaylistController.h" + +static SandboxBroker *__sharedSandboxBroker = nil; + +@interface NSApplication (SandboxBrokerExtension) +- (SandboxBroker *)sharedSandboxBroker; +@end + +@implementation NSApplication (SandboxBrokerExtension) +- (SandboxBroker *)sharedSandboxBroker { + return __sharedSandboxBroker; +} +@end + +static NSURL *urlWithoutFragment(NSURL *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; + NSURL *_secureUrl; +}; + +@property(readonly) SandboxToken *token; + +@property NSURL *secureUrl; + +@property(readonly) NSString *path; + +@property NSInteger refCount; + +- (id)initWithToken:(SandboxToken *)token; +@end + +@implementation SandboxEntry +- (id)initWithToken:(SandboxToken *)token { + SandboxEntry *obj = [super init]; + if(obj) { + obj->_refCount = 1; + obj->_secureUrl = nil; + obj->_token = token; + } + return obj; +} + +- (NSInteger)refCount { + return _refCount; +} + +- (void)setRefCount:(NSInteger)refCount { + _refCount = refCount; +} + +- (NSURL *)secureUrl { + return _secureUrl; +} + +- (void)setSecureUrl:(NSURL *)url { + _secureUrl = url; +} + +- (SandboxToken *)token { + return _token; +} + +- (NSString *)path { + return _token.path; +} +@end + +@implementation SandboxBroker + ++ (id)sharedSandboxBroker { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __sharedSandboxBroker = [[self alloc] init]; + }); + return [NSApp sharedSandboxBroker]; +} + +- (id)init { + id _self = [super init]; + if(_self) { + storage = [[NSMutableArray alloc] init]; + } + + return _self; +} + +- (void)shutdown { + for(SandboxEntry *obj in storage) { + if([obj secureUrl]) { + [[obj secureUrl] stopAccessingSecurityScopedResource]; + } + } +} + +- (void)recursivePathTest:(NSURL *)url removing:(BOOL)removing { + NSArray *pathComponents = [url pathComponents]; + + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path" ascending:NO]; + + for(size_t i = 1; i <= [pathComponents count]; ++i) { + NSArray *partialComponents = [pathComponents subarrayWithRange:NSMakeRange(0, i)]; + NSURL *partialUrl = [NSURL fileURLWithPathComponents:partialComponents]; + NSString *matchString = [[partialUrl path] stringByAppendingString:@"*"]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"path like %@", matchString]; + + NSArray *matchingObjects = [storage filteredArrayUsingPredicate:predicate]; + + if(matchingObjects && [matchingObjects count] > 0) { + if([matchingObjects count] > 1) { + matchingObjects = [matchingObjects sortedArrayUsingDescriptors:@[sortDescriptor]]; + } + + for(SandboxEntry *entry in matchingObjects) { + if([entry.path isEqualToString:[partialUrl path]]) { + if(!removing) { + entry.refCount += 1; + return; + } else { + if(entry.refCount > 1) { + entry.refCount -= 1; + return; + } else { + if(entry.secureUrl) { + [entry.secureUrl stopAccessingSecurityScopedResource]; + entry.secureUrl = nil; + } + entry.refCount = 0; + + [storage removeObject:entry]; + return; + } + } + } + } + } + } + + if(removing) return; + + NSPersistentContainer *pc = [NSApp sharedPersistentContainer]; + + for(size_t i = 1; i <= [pathComponents count]; ++i) { + NSArray *partialComponents = [pathComponents subarrayWithRange:NSMakeRange(0, i)]; + NSURL *partialUrl = [NSURL fileURLWithPathComponents:partialComponents]; + NSString *matchString = [[partialUrl path] stringByAppendingString:@"*"]; + + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"path like %@", matchString]; + + NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"]; + request.predicate = predicate; + request.sortDescriptors = @[sortDescriptor]; + + NSError *error = nil; + NSArray *results = [pc.viewContext executeFetchRequest:request error:&error]; + + if(results && [results count] > 0) { + for(SandboxToken *token in results) { + if([token.path isEqualToString:[partialUrl path]]) { + SandboxEntry *entry = [[SandboxEntry alloc] initWithToken:token]; + + [storage addObject:entry]; + + BOOL isStale; + NSError *err = nil; + NSURL *secureUrl = [NSURL URLByResolvingBookmarkData:token.bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&err]; + if(!secureUrl && err) { + ALog(@"Failed to access bookmark for URL: %@, error: %@", token.path, [err localizedDescription]); + return; + } + + entry.secureUrl = secureUrl; + + [secureUrl startAccessingSecurityScopedResource]; + + return; + } + } + } + } + + return; +} + +- (void)beginFolderAccess:(NSURL *)fileUrl { + NSURL *folderUrl = [urlWithoutFragment(fileUrl) URLByDeletingLastPathComponent]; + if(![folderUrl isFileURL]) return; + if(![NSApp respondsToSelector:@selector(sharedPersistentContainer)]) return; + + @synchronized(self) { + [self recursivePathTest:folderUrl removing:NO]; + } +} + +- (void)endFolderAccess:(NSURL *)fileUrl { + NSURL *folderUrl = [urlWithoutFragment(fileUrl) URLByDeletingLastPathComponent]; + if(![folderUrl isFileURL]) return; + + @synchronized(self) { + [self recursivePathTest:folderUrl removing:YES]; + } +} + +@end