From c23bece62c537fa7abd970f75fb7dcf2bf5fbaaa Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Mon, 20 Jun 2022 03:35:29 -0700 Subject: [PATCH] Reintroducing App Sandbox, and more - Implemented App Sandboxing in a more friendly manner. - All sandboxed paths will need to be set in Preferences. Set as loose a path as you want. The shortest path will be preferred. - Removed Last.fm client support, as it was non-functional by now, unfortunately. Maybe something better can come in the future. - Added support for insecure SSL to the HTTP/S reader, in case anyone needs streams which are "protected" by self-signed or expired certificates, without having to futz around by adding certificates to the system settings, especially for expired certificates that can't otherwise be dodged this way. If you want to import your old playlists to the new version, copy the contents of `~/Library/Application Support/Cog` to the alternate sandbox path: `~/Library/Containers/org.cogx.cog/Data/Library/Application `... ...continued...`Support/Cog`. The preferences file will migrate to the new version automatically. Signed-off-by: Christopher Snowhill --- Application/AppController.m | 9 + Application/PlaybackController.h | 3 +- Application/PlaybackEventController.h | 2 - Application/PlaybackEventController.m | 21 -- AudioScrobbler/AudioScrobbler.h | 45 --- AudioScrobbler/AudioScrobbler.m | 259 ------------------ AudioScrobbler/AudioScrobblerClient.h | 41 --- AudioScrobbler/AudioScrobblerClient.m | 212 -------------- Base.lproj/MainMenu.xib | 8 +- Cog.entitlements | 12 + Cog.xcodeproj/project.pbxproj | 26 +- .../DataModel.xcdatamodel/contents | 7 +- Playlist/PlaylistController.h | 6 +- Playlist/PlaylistController.m | 9 +- .../ArchiveSource.xcodeproj/project.pbxproj | 2 + .../ArchiveSource/ArchiveContainer.m | 9 + .../ArchiveSource/ArchiveSource.h | 1 + .../ArchiveSource/ArchiveSource.m | 16 ++ Plugins/FileSource/FileSource.m | 12 + .../FileSource.xcodeproj/project.pbxproj | 2 + Plugins/HTTPSource/HTTPSource.m | 3 + Plugins/MIDI/MIDI.xcodeproj/project.pbxproj | 2 + Plugins/MIDI/MIDI/MIDIDecoder.h | 2 + Plugins/MIDI/MIDI/MIDIDecoder.mm | 20 ++ .../TagLib/TagLib.xcodeproj/project.pbxproj | 4 + Plugins/TagLib/TagLibMetadataReader.m | 9 + .../Preferences/Base.lproj/Preferences.xib | 140 ++++++++-- Preferences/Preferences/GeneralPane.h | 23 ++ Preferences/Preferences/GeneralPane.m | 44 +++ .../Preferences/GeneralPreferencesPlugin.h | 5 +- .../Preferences/GeneralPreferencesPlugin.m | 13 +- Preferences/Preferences/Icons/appearance.png | Bin 2028 -> 1969 bytes .../Preferences/Icons/appearance@2x.png | Bin 5741 -> 5734 bytes Preferences/Preferences/Icons/file_tree.png | Bin 3614 -> 830 bytes Preferences/Preferences/Icons/general.png | Bin 0 -> 509 bytes Preferences/Preferences/Icons/general@2x.png | Bin 0 -> 963 bytes Preferences/Preferences/Icons/growl.png | Bin 2217 -> 2160 bytes Preferences/Preferences/Icons/hot_keys.png | Bin 1433 -> 541 bytes Preferences/Preferences/Icons/lastfm.png | Bin 1768 -> 0 bytes Preferences/Preferences/Icons/lastfm@2x.png | Bin 3875 -> 0 bytes Preferences/Preferences/Icons/midi.png | Bin 1767 -> 1752 bytes Preferences/Preferences/Icons/midi@2x.png | Bin 3612 -> 3602 bytes Preferences/Preferences/Icons/output.png | Bin 5181 -> 2443 bytes Preferences/Preferences/Icons/playlist.png | Bin 1709 -> 1689 bytes Preferences/Preferences/Icons/playlist@2x.png | Bin 4353 -> 4325 bytes Preferences/Preferences/Icons/updates.png | Bin 2458 -> 2422 bytes Preferences/Preferences/Icons/updates@2x.png | Bin 6701 -> 6682 bytes .../Preferences.xcodeproj/project.pbxproj | 62 +++-- .../SandboxPathBehaviorController.h | 19 ++ .../SandboxPathBehaviorController.m | 113 ++++++++ .../Preferences/en.lproj/Localizable.strings | 7 +- .../Preferences/es.lproj/Localizable.strings | 7 +- Preferences/PreferencesWindow.m | 5 +- Utils/SandboxBroker.h | 26 ++ Utils/SandboxBroker.m | 237 ++++++++++++++++ 55 files changed, 767 insertions(+), 676 deletions(-) delete mode 100644 AudioScrobbler/AudioScrobbler.h delete mode 100644 AudioScrobbler/AudioScrobbler.m delete mode 100644 AudioScrobbler/AudioScrobblerClient.h delete mode 100644 AudioScrobbler/AudioScrobblerClient.m create mode 100644 Preferences/Preferences/GeneralPane.h create mode 100644 Preferences/Preferences/GeneralPane.m create mode 100644 Preferences/Preferences/Icons/general.png create mode 100644 Preferences/Preferences/Icons/general@2x.png delete mode 100644 Preferences/Preferences/Icons/lastfm.png delete mode 100644 Preferences/Preferences/Icons/lastfm@2x.png create mode 100644 Preferences/Preferences/SandboxPathBehaviorController.h create mode 100644 Preferences/Preferences/SandboxPathBehaviorController.m create mode 100644 Utils/SandboxBroker.h create mode 100644 Utils/SandboxBroker.m 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 1b4eba6af017a55f43bec03ba29c07d392b55f89..5d59a5d1ab97846370bf559b686ffae176e53db6 100644 GIT binary patch delta 1934 zcmV;92XXl953vuBIe%kGL_t(|+QpRvfFoPJhrffWxG}cp+O};Q@2zdywr$(??%1~3 zJ+6*PO}TQ=0y@E`J_R0W04lN+YJ9^mXsq zu<#pS{tW+42;nhbdKO*%@of8x%8QdXsAvrMl1Q6Ec#pR!KGIIa`JrI#erJ-k zHtGLU`*?S2T&Yamv+T#G&%AMPGr)ruu;eFK6(@E~x>*GERJ=baUo5B~B3uAls}pv0 zL9K}$=%U%P3nK-^!;hdidj<;7aW|DQG2Ppfb=G%2Yk$%OpZ~*y7O?9#PrEo5=ia8Z zI?nq*<|If3fQ%Him8+@#c?nr#5St}<4=#mxbdf3lcMC$+7(H=_TCc>K@dTTB>+M&) z&t5j|vTyANxc4%AqmbKw{ggLq9sQ*4-Eo7BjD`_t6xs*8HDp6I)VNWkZ5{NP99_N8 zI~3t2On;*Ntg}ElI>*$9&qo~S?V#2)q(RHhw&I<+Tknn1FoR~AF-33Us)=hU`vcp0_Vr$L+<%jQvH3p)tp3K0FX
fOO9ewjA- zN?K!Mt&9EjKD^7&QgVI!c-}SPaICXSGlwq?JV^RVEAvFj^@ENHZN5`z|7~DOVL*n^FJvEl?9s@duRDrg*#(2Gz zT6KVqiavYRM12?VDGg9ASDsZU=F5$y!2p+(JDD;2Sbn}|H_HYF7*i+{wUv1K$Z?pY zi6bC&1VXhuk@-CBhu%)s=)vy&C1Wd1-hY3^e15Y+vvhlbQRN1*JeE|HDCQL^c$D|g zxa_joUlcF7Xl&`WC!N2nTq)1aj3rn@@D`;sIi15JfWX#zKO?0guQgId2q_7{lV+aM zAx~!1)Dzk9r?;SoR<+J0&4gCyl%!D53}$7j4b6n|AWBIxFB-r8%l_kZ`>fGZe18a& z(q@9mGC~kYr3q4FZGd3$CSkn1=bQu?;RE>Wo^UkC&A5f}v(IAN^6yjIGY?}SGoHb^ zp;U^QIJ(#pS{8N6MNbeBNQE=Dka*LnU7A&fKc2%{g!HtJ@N|xBqQU_NdNih?7_<`z zQH&I_6#|ns5W=!|Zy#yTpU|br?0;PR72Hr0Ayt^OV$Tm(ZtDJWF;>xeCr^9U@duAS z%Lh+Y)$6*ddfk=-jnj@E%5ozr`5Du?>F5~3Rdd5!K^Q+tc4Dj7IpIAbQvwwt;SnCMR zk&hMYwjEgX!v%d?#Aqmg_$_BHIdt}wCZ7a{cd? zHRiPXz#E=?@O!T~_ox^7q<iEG2lB081`DFN`#lQlCkYbaJqL|P)H zq^oa;p8iI|S?_c%E))y*Tp@VpeP+BFY^Gh?cK7}9SM&G1cm1xg@}G_9$SGpxb8k6v z&OsB}#u_h>vBtZA%N$C9iX;difHMy715vDyN)ke79nV>@`%m*$?tl5j=H1!KYClPw zLl!F(i<%M|nl_`Yks~c{WA{{$=~_@ocfo4I%n%kH|^-RG6+%X#L*Fb_)$RLPv z3LgS#Vo8z=5lMmooB7r@&g&dbJe`M#=3dxS4eNnB51S-DdVk{Ko&Ps$@`y_&kMEp1 zx}!i2PpOy(K`Rs}K!B8zJqN0XiDAMp4_&~$uy%Kt56l-rj6QBQC(oErId$x)%2Dm* z!nAx&b!sW|fXs|Z`m4?DYqs^gGYs?4bKoH&geU@Kpa=+{3G{{#%p**~$Dh9f64Qzu U0ELH300000Ne4wvM6N<$f+UI5#{d8T delta 1993 zcmV;)2R8Vz59|++Ie(r>L_t(og~gY9j9pa~#((>9&VA3^>Ac(NyF(vPEX6{!p(r3! zLNJ18kw6+zF-?dG0b`;GHWCvQ)Pxv~AtA~$R3s>-Qb43IP$)1kRN6X}89I;lPVc<# zow@hC_TK(6K!8-FCX_EZ|D2V*zAxX-T5B)ioa57h5a68rG=Heg;ALAF+}a3m(goD} zFTdW2hSOMb*A#$0ndFsMzI;)txZwMtt!;dl_@p^7v~%rpzp(f|6Gyk@mflx-_y0)| z(07~2&0Bib{nm2u#Ru-b^8-QW+$a8(_kQKK(clg7@UC_1J{+#fkQT(toata-{z&V$r+oU z3#wCnum0sv=YQd57koJ1{}ixehfwFVZC$UBUlP*ah{~)&d2L{GBCHExEj9_Uo;Jcy z&$=WzYU1FHB$(XY*lDix3x&lay?@=f{EF!}kC*?y3V(R{(Mz)nie29jPJIV!&q`-A z$_WqSAgRvahDuPalT4MUmxqX@AbaX*WV@H40PT-aX!98x4E86sX#cP}MuJoTNKaAi+ehV@mx*fABvF91U_$U)vn<-W1tF`nE|{e{E=l4} zToT#DCV%%@8~tF(#Sb2RKi3D#aJ!I$kAHi+*50Et)~uhdMbu(}Mxm|4CNa@$71hx~ z*id95PEi_%@mUWuzl+9An?Me0}Y49oW8I z$Sc=fbw^`kVOyLC?w=TEBCUBlmnK~u=WAXgYk!mv0+1={r3pj|Y7I}CW~}hA z4Qpu4y~K`ZI~cXAnaV7q9!4zE{am`>4f6KbK>5h{h3l{V`a0&60)lw97}$l|o7nXJf+LXUQ+UlwD6g#vk`B zr+;oWwb&3k!Ho4;RT$ydXFf=McKYc9hl9;qZ+)S?{FLF6jC`F{3@OL zEvl6%iUob{iUs-_;P)CJpD%37WYhUtJ%1(!Hs_mI)_n#$M~3O0nxZX}$7{%OU2_L< zP{-hq+5@4mHYT!Z8dqOMR4bE=K1O??&aW=n!xOJ*UKz~LlCL52?SvvnHmy+3qOASi z#TR$~LELy@d+wXxI=?qx$ahC^f=gnYO;Adc(kUzgaJ)4$NwZX>wMHrrAtlaP!hgt; zTXiihi#PE0?px5auMUJFgiLxSl*4!E#&Y^_h4r02&r7pYa@^J^^e`1^_6$t*-N&axw3VW zb)Y;Cfxuczr4oeu_LnMp|Dj{G&wpk%O3f>zmn|8iSe(UFh6!fM6r3f|S${(9GZPev z>oaq-2BU1?;U&w;Gv&x$RWihAtXs+aK@1K z75fK{?S15hi9@0l@^{>F&daO2yH06VFW`j(I6GIG&OzcNM`~-R1Wjlyke)&*MY%M= z&_J1DOOawro@~Y^ol-mHIrDQQ+_6!@w5bIjkQs18W^5VJDOkxrnzx#?$&eaG^c zBsK^wvDT7A3Bhbck{B{sPn@>2c+H8Dzjp=i#|3AkuDamt#lKj$vb|FYh+~6EEJH=1U9xvK^O{m&SgJ*aqs`-z`J;&H@x?b`+xe+d+5mn_Z%FW3Xlrv z1YSyEog)kqf*?Y85+@*uEKY!*)+vm*?nE6p?qV`haR-2_*K~>BY&f-P>xzZ*HZSaK zTHIR9kiwG7ra^$U78D>rO3BEv${Hbra3}E4{K(DdjbXP3*dv5!UEj^w%jOq8*WOY% zy)mCzl1`~6Ekb1)kWm~5la>15zXr;;o(MU7j7RBl4+)V4@<5ik0<8n%&bj!6bA5X2 b`5gWO64QzuxxQmU00000NkvXXu0mjf8`j;d diff --git a/Preferences/Preferences/Icons/appearance@2x.png b/Preferences/Preferences/Icons/appearance@2x.png index 21fffb45b14e69841f4b45452b031d1d408749e8..de44f2684969622ef253e172987e932bbbec2cab 100644 GIT binary patch literal 5734 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 literal 5741 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 diff --git a/Preferences/Preferences/Icons/file_tree.png b/Preferences/Preferences/Icons/file_tree.png index 86f57ad3325c402380ad4ed0b12e3bd897650d1a..f8ebcfea7e10ae4d558ea0b299f2dd4afcacd1ea 100644 GIT binary patch delta 808 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 diff --git a/Preferences/Preferences/Icons/general.png b/Preferences/Preferences/Icons/general.png new file mode 100644 index 0000000000000000000000000000000000000000..92c49174585bcf9f3bec4bb00d471a86b7abf472 GIT binary patch literal 509 zcmV2TB7>9KMDxfT@&;=OqAi$6=K)DA2 zs&oOaV%c(l>ux(@zQy7BZo{|Xo1oDY>Tw_UP=_f&-4AS!9=Zm1(F!Hdhof=AA4R{g zN1H$R8Yj*t)|rYoJSxPLLBDUlqE3T?8o`;g)}UXfW4IID5d^qv($h4+Z9#xLMga`Z ze$|2iH3t3qeFYf?1rs=+8GZm02K~}~MO*L*lft4d97r&{+u&&Wf-T_<*5W)e1r5ef zikrCM|9ebOmx&8ltJ{XZpe*_6wga8QqQwAlRg_9Lm668gk%2oGlBqT!!FH*{vPExk*r6v zQI`|c8r#o@Z!`hkdhv9;5&y6<^zQi-a=PaL7@D!wKUg;Z00000NkvXXu0mjf!I|ie literal 0 HcmV?d00001 diff --git a/Preferences/Preferences/Icons/general@2x.png b/Preferences/Preferences/Icons/general@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3fbae3d5f45982b84df89d4267ffb1522c96e5be GIT binary patch literal 963 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 Z_q+f};woFUJ@Nnm002ovPDHLkV1hQQE$09L delta 2205 zcmV;O2x9l}5UCN6B!6&8L_t(o!`+r?Y+Th9$A9;|H}B2rSv3liwp7;OVdyaJf@PGfdW@mEx?Ch*@sr$80 zQ~b!$-jx&C!n3nQ=U4rM!;3GaM*pPgqH{ad6 zU;fW;pl>_Zi`;uJT^LVq3p~LLivwxQ}$M((ZjP*a*{~X~@s{hzwna<`+PqNl7JKZM^@9a1{@-#Ak@<=h? zw7=vf&foInF%O{Ux%IkFJNbYR?v<6Q&cL}rcIVN)tk+<5<%;qjTyvx6f{q(>+%wehqCXuo$ ztx#~OxQb-l;pTNi*rJohrWOK$1hQhFWX62-tN4n7~gM3iV5%8rZcNruLC4h_dB=}m08wwa|1Y%mH4J#Py??wkMs5;6P8 zx~|?zBIGxlk`8sDdisWS+I%K;@hHL+1QI#?Q(5x451>%8OT|hHovOhf_t_j6nBck%^?Y`t$&v+Nm=<+013Ryn%vQ*k zN)+-r`UWfts|)z-0#d4;t&a|Qa|RfjXdapuD((&XoQ1BVF*1~A*WocH^FD?z*nIs9 zg3jg~eqV`5xJXSok6QZThs;X)$z*i!+Hf?v-e1=`&mT$Q4<)dyAi8o%XJ!bAX});%6zz?Z zc&>x=ARdOx7fbHErp%336z~a~Lcs-9X{w7j-BMq;_`ukTWY@}#+QNkkaVnLjvTctW z0I%(MeWP2-Jyr}~xk)!qSq;7uXn)~G%s?Ba-UyzeJ32vAQgO$oGPoJqo2$%Z1(k}1 zri0I?SlXsAErpVh&Vc877?vU!fwQCYIM|nE!Z3mz*L__=9So|P>nlq)eTi$Gln&0mjZ#u0iDln0e5D379;Mkwg{YTiNi9zwep&Rk48cGd#J*b$w#8pfi*PA%EbyH4dpp@I_mp zZYj#wNv>GW`Ltkocml6Dz-6h^B>jVAGLng`;Mq4M+yAWC_Bp}V?$QtvbaaZBzqG=pAk2-Qy8P(hkF8X@-Xe zxu)YB4{Xg4@kQd4_B+*b zVVlyFW%vZ)pd=O(#1cuyaxFaia*~m(N!2NlnVh8W+yrf*Xh7dw@^2TRlH+Pv~Om2~R8CQ&GyY3`oQk$$xqcCBnS2JIs!K8YSBy z;xDjzk;ApCJ=QJ-UBP4)?6RO-g`%Snn)!avj6HnW{h8MT^&+O>j|J%3s z$V&zQ_{p|q{(tVa=}m@jcAFWPd|kZ@yxQ*0QLX=B5MnGoaEy8khj`Kn0L<1rRj|KM)4$YA_KXP=nI}RXd#nlxl)G zV77L0rzWoE3h+S)qbBIDg;mR4bl6_4#|}`gg;UFYNPMmU7x#;r*hLt<78m~|YvI;l fybt6)vKIah07>F1!z~au00000NkvXXu0mjfwl!R| diff --git a/Preferences/Preferences/Icons/hot_keys.png b/Preferences/Preferences/Icons/hot_keys.png index 419fd26e6147c228f4258bdcc060f1f3b07bf5c0..87485b7ea70670a5ff45fea443d04f7f0906c3e2 100644 GIT binary patch delta 516 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 KMNUMnLSTZ2dU-7X diff --git a/Preferences/Preferences/Icons/lastfm@2x.png b/Preferences/Preferences/Icons/lastfm@2x.png deleted file mode 100644 index 30df84b8274be732a5285b03b454b4a878291602..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3875 zcmV+;58UvHP)Ow#g@%;)k;SfiFK%q$hJ>p^Hzy`X0C0JpEY!!)< zrfyH1^w^GsNwc@dPkgs3c0y`pY;tzRsN{SA?{N)d$U!b@P&?lduLswNh-EQJG|LjB z%a5NBSDi5Fq3XDa%gUYO%eGm+c04ySu{+%q%V~izoNBQ1TD^@^bPDhu*OW2jAQv^L z1qZmm`MhwW$FK`md@?O}Bn6blPn;@(7nH?}-|mh|>ev+I&=b9(fggH_ zeju7IOS~H$PtS+j^hM6A?kKa5%g+vt?@3WHekmscJ<%H)pkZ$q4{e~X$FTPXw4M@dP4)WKohh<<0Xk5&SXllBwEXC;}++IC$z8CS|MUs09xFl z3EH3$S}$tsibpz-G($T){8PNZlV35%XeQE-(PfICpJj0Nf^5SG1+>Ehyuj03 zcspOP{W@9~q;0+!J zjUQ_Q@e+`sjai(bu@6CD7_TSd)cCQScK>wFm^q8HUw(yiX3ycQ-~EBNJo_u&aL;|5 z>P&)gzMSoesZKqE2mkF$jlE3~h8{Xg% zULT+UIu~0s4w_HliRomoPM1V=fQZ=r;!AwCrN#Rb?(F1~pMJ^*b6s4Yp23Im^7+*M zdgZ!jw4FZ9`SMA_w8D+m;2gsTic7q5&mL*whOBJf^WiGqm$H_dD=WFHz1?diY?tvq zK2^XYyu$OjU`;ZuAoF)J$#ybBlGI`5^sA?EV|BInr+WMZ*DQO7bEe)XO*8_Md?V+4 z^A@gI`VO}rJML9;bnjly_|EN;7cS7T>xrki&0}IuA86pMPyG~p=aA`0F(yr74N~6l2bfq9(Q*PG__}4D#krH!#L;WTe)T5KJGeumfM<-DHGL@ zlgrtYZ;g4;YcxK61r ze0jDq0i}OS&V2%p1$-w!zM^By{c#k$Vvir6>WjqpjYUmRe#)sT_>=}DEZB{0r;K4`u z?9pbgaXj<)$%a}gPY&12=Q;hmck`AgCu)Qkgtm%(%rY1;gqtU}Jz$-k%J1wF}dWr($){~_yR|eIPgL_mqvThIYhf7Ok z%#0jA5XJ@f-p8$n4}0AP#c~^<$5t6XR$1k-SEx_ODS1Rze((>@yk?5#*z{=_R3f13FF4ReDB*}<3`~j84u=d zx8KF}Yu9mG(^2llrg)y{7EOCIvp6UDYUvT}yT1Tl;Thg(E``y3 zFvw%7sj7-y5%H2^^QckhsagCMCeq_J*w5~pZjl@_yD_20FfRV#!@TS5CEO^7pi^>Y z=#c%Vw`=eT>5`RMxpWymDBt}B@CwiHj^$6^B%O5)MQ39$U$vOHOg5m-wzh%0bVCE@ zP5ZXgnUUuQOkDKf!`ycC=l~w<{-yI?!vqPJqc1lUKg(+XZ~gidE}ii-*DhMjO$A%I ztF_hpV6}56yRVuo_0~aq!y7!pD?GzHO(HExM64q7CoUz)p@L8m{)pFO44YEPi@%j| zMfiCT-3J>V*}Bd9JW*84uJ|!9Fi0(~1#uqwn5Q`eE1b@D)fBFm`s;K0eYiF4FZRU4!a=|ce`Blkt*){Dpc8Ngj=B}g(oIm3!uK9R1pYFTokIK2BVBCKQ7xB*d zuPbMH&%j};Hf61H@N$iwD3cK=AZ1cq!7=Q*^>(g)ZzXql)V9i@toV+5MQfz02Y7_n z0sku&(|CHC0w&7lP>WeAq!={_iJt3}Z(?2S88{=HIM5(pX^Xjj+crM3vld^Q@w^_e zTCkaJ!zu`Ma>JSwJP`){MF(n|YHN9aegW^3$K0p6EzDs(%m{(HNM|qM|EXhTxuyV=7YxuA<>-84y*WqQae0qgM!e!x-30L!> zb?doX?*9vkGj(-bDH~uBnFiGjPw)nh@CwhKVfZtJJ(oe&E+qSgnM>v5)gZr*>yV}I zPhj8Zl%;)G&ixhl-mh>dT$qH|(Y#l7^)vhSDJyFz(IXq_iHZuYmOZAJ0)6U+H+Y0s z8YDE&P{7nrDEiYPa&QU7_y$%&U?lH&=9k=Bt$9bmNUa=$B*-=+Fg7| zmO64vBJENe<-uhafm$xV`8H)Dn%#Nay1!odTEGFWew^(}pVs<%KI(Gwo;Mb7+2pBG zAL3IlJi!}0!Yg^kU7t@%)KZFAfoEkYIR!C3fN_wU1V%2O{0-jz_>)}w!f$!E{OWqQ zob7kYy2Q7*gc8p+WLLcXZ==96)YK*J@*C@#N#8dv{#hQhI1z9#RUJy%|ZbJqVV zeadgR8F_uYHq`sn3Qx<#8$8|(uPUK?k%AwPqvDAY5Dp1;fk_|+hyZ!76GR9c$QdGF zDpUZ2)Q?k(>lpW&3tr#}-r$jZg_$%lGE<-6$XG_suLR%)p5TrA2*Yn=rsQ$ajC-eo zoFf|W059+aZ{%lK-=wg=%Be{qsT`qzc6fjnc%mVM?ol#+L!OycI6aNj34nHZfERco zewJ6)tOevkAwyh-7kw`ix&;)rdcDTd zSuRVh#yE;=0tCh@gudR;0!`2cjnGP$C=63bJK+^Fz5Suad~%!K!8;%#@X7$ap#fT; z3EH5ME^+8Wh+_YX0)H*X%bIkpwYNf+-%b&9If0(&4Gqu&P0)5Z{|YetUebSi4w>Hn zfTE7)YVBNUyz~=+9_WRh=nW0fqG-ATVKfoN%YSE^@dkx%{3n@DW$SEQZj4cuXl?Mt z0l2^kZuCGe^h9rH7!H3E8XQNOu_Efpxn%nIU5ac-)mXao^mZ;2;oAdcWd!+e7`doH zEjYjhPH>|KdZDM%dl&`81j#>fcai?i=P2ma*C}-KI}};BN@G5oCig2o!2&Th) zA!Yz@4P(ebE^1H<4sd}J+~}e78s?!Fga;6f{ub$O`3VL5=l@X9>#|e-;|&VSTtwj| zf1!vSODJL&fcLnDG2|c@HK+v#xD?J28ahE>p9zOxw7i)#hBVhE%Sju+dtAd9a*&G} lrS>Ztnm%ZOb3b2!_&18iJsuvj%y$3)002ovPDHLkV1loldP@KR diff --git a/Preferences/Preferences/Icons/midi.png b/Preferences/Preferences/Icons/midi.png index 1aba844b9d88931a34bc2eb699f5c8902cc703b6..affb0377dbd79562dcb9a0ea8afd522f0ce4ecce 100644 GIT binary patch delta 1551 zcmV+q2Jrdk4cHBka)0khL_t(|+QnCej%zm){=%>j=4|d#mg$EwKR{7bZeO6HGPh4s zdWwowX6DNn+zVkm>3r5YI@L*;Msn=f1D|KcV;}t6WW!HsYHx4vYOmLu(rbSqjoR&Y z@$=6=e;Yv40$^ojWhax#ygWNQ`yadAj=zHp!$7@W|LpYX(|->hK79D|F@RVsHod&O z{8l27*zNUtPz$y{?tsx~M0v}Y< z^ZB-e!Qj;b;D2=mKw_SBI2;%bhX_Z*Xx5vMMUV)9#Pxvyh=s#2ltJYt9Y;0Cz94e_ zejkBAfG$^K4W!v@DjVF4)9Flc-l@GtW*Rwm_!t*qx7iU2hY(N1@bwqpC?NuGz&JND1AiDVxZQ5$`i<>%96x#j-+c8g zmR1&F3`h9%gAY*@*S5s4ON@2^MILGG1FxjKUf9HtB%9J1iA1PZd6XgGoCanAz(y~y zKX&A}$^`-{7D{kBoaoA~FSXk`?qi~pZJc8}2n*e1h=T?&hgFoi*Od4yN$Yb`d zIowQ4lHCk|jSCJ{C>-eZds4-k9P2tR9#TrBvVYY<;+oA|iYzPKs(P%eSqIKkmHkvKbHH7#9 zb&>J7otW}@q>Og~o8y<0P@m7W)n$Yv!7vP)5qtI^xk8 zEPu}DaQ56eB$El0%N3kFQ$=9bheRw4$I-L!`&`)GSV5(_kHbffS(03bKzTv8cszmm z`32Y=4iT&(lgYyC^NN8o5{aZbj)h`T^|?dyNF-Cp<>o~$iIVVmJYFSJ-LHyQ?U-L! z;EjvHfqorsa|l$^*_p{^v69bY`ptJR9DfW1%skE($|@)3a&vIHToT(Zf$|+8Asb*> z_zh#kBqlIrbRgm5`CnOGl@RnXkTsp0w5I_OjX6PxUKFp&wK~hoM(y(3?~f?u+3XyI zjbqtW9Qpi;l-W9#m-A>f+e(i-YUn}H#mef(>dG3b)$^)0n9H--1N!!2;>H=?kh-k${lH9T&zjFU@`y1%c_>TQbIkfrS>6To8ueR4 zz@6y=VJ;+T3B~5twpxoP64>6^lYbk{gFqmNo!$S6s{=3-jqL2~s#lLgz;A8sh$pi8 zl<7BtsoSc-i2$fz8tAk3U}Og3+5#O^DBAer009$rWbm25|`S<^?z}hmk?-P z{ooX^N%B%g_@e}(0cfM7$sy40*GkRj)i?k*RwiQ$>8fg#*8ub>u!cYz)d2otvUpd( z*mNai{EF7w>#qkOq;re_3S$8BJ@Br8_?SmVKcPtMe`QSm-^|A*Hfa1)vH7<7GHS_( z!()Q%HDO2>i$$VgLr%v?j2Z%{CbB01(EX?AeEi$=Bf#kH)&v&jS2UA(25zI# B3&a2b delta 1566 zcmV+(2I2YG4d)Gza)16wL_t(|+QnDfZd*kZ-M(Mq#E#=@?6_{QohFTwzylKPOZfqW zctb*{KfoK}@`A*lAn_GELqP(el2Y2Bv?(QZ?Kloq;`{BGwFb}WLB|0ELEXt@CNul8 zX74?F=E#dChy0o5c6N5I`ThR1TDw@9bi3W!?(Xg_jN3KlNm>2+E*br91bNM4%6ivn?Ty_Hf^ZQxZUnF=iSi39m;bvFdU9V2%PTM;nbew z9D#9eVg+C>c)ecb`i=6sJbL(x+`D^UUMj7MF`mfnZ@-foT-$+R4~+JvvOcu#)GKMf zUmP%mWK%kqmX@fO9%TqPX9BYUV51kCdXW!j-PW_f@KqBGalr!yIeEH2X6^E|MO31BW1qB8OMe3W71rl^;p zo$Ud=YmycM*?MHIECRs!^n4@B%gg53pPQTGNz-fRhd>MbN76}m-r7t6Ix`s?_7?%- zJn!gSn+<^H)>MKY{#wIhADc9j$Ld{cxS5zFr+*ay8y8%vP`G3;9H5G|Io5R?9#TqC z*_!Osn&O9ItbwOR9sFO(qO_BTYn-2*1A0Lt9>TES@1-&nnlUoitCV%RnD==AY=mRh z#cmuD2`@;i+g6Fz!x3$@JJ2;2_(7VG@p;`c7x1Bs_u&%PUnikHpI6ojvWSeob9YaA z1Ajw`tIJY5I+3$hUsf+Am63X*DT&y;tmbo4dHPgRsif5F$MX0|L&6IINyek%dRUQQ zz$4|2k{mZqWdFf|Ey;BV=moPS5=qJDFN@RVf?z{3nXLE&ei*2url-_#tk!C(&t1w( zGL@EGE)Ths9D&E@^DCLgNdsPWO8)X?-ha3bY`Ahvx1^41P+G#?%t5!u>) z1+ET@m1tyZYg@f~TmZkhxdl&T^(oVD0<+**bAS`t115lX-+5cEUwc*3>9oA{<{Oen z0v1a}$)V?a;`JLhOkOI#y0Iaje)5r&%bV&&^x^yO$@==b20$C!%*-5Mvwz)IVHBBo zET)9!Lm|X*Njz|=1FlbC9wE@Y`oRgXDe_VV{4oO20JKrk>=0=8vsuft8c)HQ$}qN( zuBvu<4M3j)dkC~q4d8;w=3RhMNSxF7)$F&|#RCB893z0L2>{;%-vGq-JTm$T1!Dhq zWAguFJ~r5(@!PO@%X%4g@PFa(0g!zThUFUSJlnHp&*UjCnrJktK2)mJDjVa+dG+jq z497frN_nMH;p3C_f%80?{7~UNLS8;rYq0e(AJW!uL&D6Wt-wY3Y&IK4A#uWxqu1*> zQD_{9p95bw4l2W#iqx3IcLI1OkmWp$a_0u-Cn#zLPR9gB4FEcW>=R>%(EY#C`SPOa QcfjcG)?Egn2a{?BZge8%|#bt8f)4Q|2{+ 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 delta 3425 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 D4iAQ0 diff --git a/Preferences/Preferences/Icons/output.png b/Preferences/Preferences/Icons/output.png index a0b1e8d63bcfd9e25a1e0d9806baff68259de33d..33b12865ac0770550ced104ac92a9ed5f0614f14 100644 GIT binary patch delta 2433 zcmV-{34ZpyD2o%2BYz1}Nklzp9^h^xn+$H$BtcqXGDzB7Yhh8vZ7I`t;HA z^76v!>+5qzjT-g;mtK15-B(?8)pP~hck$iN_$^QMsjRI0i+}SpH#ciFH8ooA-n}(G ze_cRhW24r$Z(psTpdhrhwY7TOxN)mHIyzo>njk_@@Pwl$2<7e3mR(vj3~EzWm+UvuE-C`|o4p#*LUcb0)@&86&VQ@6PWhF`aAEua~yo0eDX;Q8#WA1rxWSvX-G^= zM09ktkdTmow6ruh91aW}I#l|qM~zi;3=tH;6QL&aQgIVsJYafYVMYn zmcJ~3E>p&1MvordcH+bdbar;aYPE8eF)*9Wu-hH5Sgg{{$;pAqWa@$c^SuQ&n@!qL zQBmw`!NU(fj5BA>VC>kjZQPESrluzGaA;`gp9&}~E!8f*_~N?8C_jnICSU`F1h5AxfK-^T2)n*77`K? zETFo&T6v&x=gyr+ckS9mgD!D%AQ0#l;F5{CLS={s%@+tTi+9v_o(1Xe^ZC`*aJ&l# z4;;WR4<0-^WWa#>CZdH6NgT(d^)6%UB4vZ$yC3?Jm>DG|Bw`;`cf zc)~?b7>!1xGW1eYQ=z9|D_88OYldso<#0}z3dhn7us^gPnXi0|_^(eP_9zg00*E{V zD5GK1w?7qP4h#_iDuv#6-+j-{n>SCbl7EH^P*_-qf`UTTrigsy4T=be5Ce_t8GddL z1<@n=q+}$Ho{zZCY2>?e2>S^LIX~f_B`3%aujQ;)m zi-1zDsHCJch_bSB9!4KmZ^8fdt$#vUd4*6>R*E9O8!I z2)pAT!bZOc-42Slbj7|N0rbFRdf;yfki)1q7?IDj5ui5|c>x|yJ2T(46tI>PPx=g@ zcmEnO*K9%Z$Z1IHcL(&fm%vq43%@Z1X${vw_o&)})qfN)c<|uS3l=QEh<_0yB!P4r z4cdy5k}`>mY`tE*;N)SoQ>;^kq#+aGH+68OM_??dM!@4lfya(Aml5S&3(9j{s4VcK z)Ruwl!S_JhO#xS}+NaAD0gyvDXU?4Ouf6tKl|q9IlkNjq#C9oM48x?96bdoG#Q4uK z8jzJ7kC2;oKsWb0IDPr3@PFG-!h>7fA0vT3@1*U z`0VW2vn9@X@_Qmc!Ehuh9P=bM*lc#BrKck$H4SM@I;nAy2)(_Hdw&?_f<7qnxzw1R z@p6b&Ua@4Ok3q-`#S6{tm$BrG6h8+&^f`3dxg7SdZ>myz% zOez@}voc^xOGfxT+=6w7V9KjRp36?5d89zbtGV3tY)fS!T4!#D2)J?GlUkOmr6(1$ zOxCrxx1X6bX_C}@uDB-&NhTEIQWNB$vgkEB0w#4rYdZ|RuM}RZiGTgQ4AXpA*r&i|!9(lT zqHXS+j{_5CJgps90@m)-($5pnU6SQO!dou3Zr!RbB=m${1YCG25dkJ6%xNjeiH}9} z;zywEJOaJ91Rk@I0(=ywhb=V`H_l&<%isF}?)|SEOMm5)!@AEU3ZBr?oXvmsN_NjZ z_p~27b_{K8ZGU2n_dMBidVs$az-npE%7i&O5|-37l8S`&PeR*w1O{IjY}pw|jE{rY zWI*$bb;vmcXh+B~@*dekuAm1pU9BQuz<>b)^MTf{Uq5vyGkJ&Ga_ZD6y!P5_;Cpoo z7WCW}GgoC!PeInLFP8>$UuH&m)+aW_TEz{E zwE5?tt?n*T6W%_qxf`zzx)c6|CzQk!1F7X99DDoix9{&-v+(Q2EHR@$3{kKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000SSNkl8LsGPwmJO*kK|l1OM$~q(P1DAt5o0tIR;!{dg{B&Ii3qgXh2`Sz?9R?*cXsAn z-nSpl%m9kkZ+%amyvaEy=lMVX=Xsz1bA;BK|K~CRmM>raKVK}D%S=p61k&mBoKz}x zX)qYPz_P3s0LO7gtJUhs(b3WOvf1pxNF-uMB9U``$1n`4)hchj^%kz{`T#y{2_dLd zDwIkkE0f7Ay8im>zqw$+f_WVs9qoxkA{mK941meWNiUzz=MEn}JoL^x@9f{dfBzp& zojUb)EEaP@p%9xn<>b>2#|c92{idzI~iJb&6ar2S75J zG%}e?df~!_>F)0CE3(<_+LvB>Y4`T++kckJW6hec)7RHWsZ_$U zfQzZ`iQm_8mKR zaMxXTQ7)HhZ@-X0AOL7S7C?hJe+R9>vI4ZXw^J^cS-W;EJ9g|~;bO6f z>$)hV&IaH(4)J(=`5kxMap%Cm0Gl@T5DJA!r_*&efItXQbNF#2_2;L7q*7@@p%4!} z^dJWY2Kerscix#vrjbT|Pp68*p1T=mx z(FkE6g`jLtF;%I6)&xxxqd9>%{~|;>gT7=I?)39iQZjF>0t^FKJsmT`#Xst=5QsBCjM#spl z{2tyt_o0Ix)s%rJz^muS1*$*=81Z-KXtBgpwPFH9B9X2{B60DNBS%n5VVVY7*NaI5 z8l^O*VKQDA$1n^!I%X4($C1iqDRt6y-O)rMkvd&?rNdH47mGN-4C~D5cIa$Egwyg-A9ha2%V-$w|tS6F82Iv;;b0VFx1E+QikM zJP*(FP|E+7R*Gb6(hploPYa>y01kch(MRvKx3@EW`gBUAiAE~wKs?Vk+ja;B1DqF+ z(-aO73I=HkH6bkt0spwVu7}oMBM>136B83;GSf)6rt3PaLHJr=SytuPv15OfQaXzl zFJ@w*i08TIV8KHv56AJJ8HRyjnx}P1fYxWTrnSO#9VSX87G1rFR3@zn)l^8=#3&dH zQYaMudf>o;x0ftgLTBe}hKGl1`Byb2>S@tu+^d<=AanzO)(X$7pDaOkWQ6&bU&f8s zEOK)bV|kJ_@znaX8iqol%AP%Y9?R$RrHvc!Cl-q_Ha3Qo@)HsHAU<|%qY|0~TG4nH zAt{XKXblHhw`L7P{~0=d^u&jUNdailV4yzN6^q4P&p-cs@4R{Q*t~f&^{yXM{qoJopn*MoHllxwvFsS((m35WuVCR< zzs=9nH#7Kd{qF`C1rk2zVE9L^t*xzLt*x!JwY4#K?%dG&_3Ixydh}>fYpr+g+^JWs zTBT>tXxHb@?9k1>d{K*fPA0UbUuzy7(ARurwSMj87q!+}|MrzP3p_Sh;W@2&s%F2_ znkOn1u3EQ&ro@bMYCwI+j7Fp4$}6wDskgWH^=vllXsz|oyLs#=IdkSLyy>Q!mR)z*%+9UORnvBv3w&*NYBJjd7a!{LZWwy*AMf@LyuAOw!M_7X zfEejZmU8hmos@=)e7NUrD#gCKUVat;184zSfpD$#1JRI`i6o|7F~0ELo9*uVy9oL! zXzYFAU*SL9{cO3A`|1r8JV| rdcegV!F)^!9C~T-yD(h>t+Jbt$ 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 diff --git a/Preferences/Preferences/Icons/playlist@2x.png b/Preferences/Preferences/Icons/playlist@2x.png index 59108fc0a1be24ac10f31154036cfb8e645a8c02..a86eb1fb0b8c4b091e8457d92f717b08c537efdf 100644 GIT binary patch delta 4280 zcmV;p5J&HUBIO~FB!8?)L_t(|+Qpj%jN~}Cg;UVX-2)55GY^)T86PtZ09y-|w%Gp9XJm{p{A();hQL!8@87`+xTBdq*0+;)*LKcXoD; zLHY)8Be{!OcG%e2he(!)=oiSsg2v2vGnHftXyU-EI$Z*XfcF;3-7? z^^J{{2;SWJI~z z?enY`5a6d*R#twPTb_DL0!&X&U(}6v+;PV=IwEq}iYA;bo~`UWdq{08h_LlVUVyA-2!GX=P+HX7Lm!@@&SQIu0eh(g`45Bq zw?h6fow8M{ES`j^C+GX>tRFqcg`%}X@n~6pNvQ_h1z~x3HF83EKyvWP#4bG#Go-*etyOHbvK+(%X1<(z33G#;s zc7NZ+RCg=&YONeyItvoVdxTSNFXkTm%9XIXEFP_&XN$Oy+K)I|mHr9sulhL|Eq5Y= z)!i7b|32_3SfY^wU3SylP7+h_5Rtb4kP%^7UF2RFu2#mO9RA-Xe18m{gp2foTT^3zGuPSSImNv@d4dH@f+ z4YjZHa&F?VZja&fe>@Bf zlEl3MA#UaHcU`CLK=a^(4>p&Vmmj60Q?^_VYI&?ig|jC_@*5BuqwG@=;SP?EP=7`b zS6k#=uC0|8!jW8uBR(e>q&dknnyMb*8!ZQp&I6`YpKrPl12oo>}U-{i+!t zd&iJHh^N=P5I~!z<+vGAg#AEnI!OhqtlY;!k2$L4dwINHK%*X^w9_eY%*(1Ad6>e}jRh?QMa`ux)tu+?e2ZlKW2;e-?lf3ve|= z=5!tfh+{6rhM~c5i+Wd+e;(vditx-#(c}$u12=C(@(!4)gY|dU*9ZA2`tiBkdz#~? zw5m&QloLSxAFaexvHn=h_gw)|N z((wu0Gw!-%Z9@%b>pMUxk78_qhd95UHU@6bGy3YhgjHL#-&gKf!gD2s&ihz>AOs>b zx$k;B;sNgc3$D$*f0L7kpYr@>IlpE05Y}vW;^miL9@E6vX3a0Rf0%^)!KJ08xarWb zx1U~)4$kc%KK5?SFDy36Z&P5CH^>7d9AmmKs}tNLK#${rwMB^Y0;0M;7#ixVyoh7X z7PoTz+vE%7I>91DqAoMi9>Mi3z(E9<90;JBCBVYM!rY~oUTR`qd{_YAXn~v$5X&|p zI?ddDoceO@fnS)ke{!`e!j9;g5lWAcATTVNWsu}-Fl>%few=)iVKD`~RE`@gzcr46 zIOSP`eU6+-0-PNPFix78nHis(oAcQAy4zmhb-Aw#gKK$Lx3aogjNny^Z@f`7J$I`u zjZY0H9V_T^yxi+8Ptdyw=XELq8yPWOZ$=Xr4V=jLQu@PnWJ=;29bU zy^W1xeQj;~SI(aOihK9(Z}HXiyCF2%#JWw0YwSymLL_N-g6AE^JG&TYnwg~P-9*)P z-NrFo7p_vOe`x`1Wqpav0j^en{MEyT?hvIE(Utq*!~eMIO{u3<3J>$N{}Zth_$ z+T1;Tx?SjbvZC74v37%{m3L@=llyiNrdjc7-GqfaTSb_w1+xGe6Wz77wGOUfawZ#( z9sJ_UNjK=3W8eWT@BoXM5yb`22NJ;1UkQS({YCNVf4!%kQaf@ZVGtBWypRB($?+TH zk0ERy_a|D*JB=I!Eka%_oH} z(xILI0_z-4mVI(S&To*FM*;FQX%E2QXE@vhjkXG3d+oI?1bEqF-L9U4hf*w$VnkP) z);g08-H4?R;kq7ivbt7yS{rmVrY(+%r@R4Pe`4JKJ@seP*W?`G5x&ymc`cy+@=oC8 zB6_bi#LPCkFG_wcvK@XSGm(?9GYEt%yOgt)_-PZ1=UES|v@g=6N ze>;f-%}lv?*MEx%3sf6Ki8$FwmSrsMeRy#H_-_T_n-gb4pD&Z^S(1q67E zimx-44&|vXyLeVQmb>mCpmO{RxNvuIvAC16S<1SQ-+2@3uTk$G2zj_(7Q246(k|3#fcf3|ui?367Y$$OmXd;}sI=f8pLzm8Yj2KfWH zV2c=&$qQT5d5IzN_tgJ8jz`O0=DvXFiM6}cB!>4h|D(_n*8zQjwP-M;R@{!vr> zQ9}>Nva=!zb_LEy%d!awwbMtBf4US!-wSxY#{b`N%M-SK7GRf(D^&UoL|DrY%p0FP z#-dfyN-Zv%YkvEPxsm!wv%ct(3x5a>KP#Uw%;< zx2v{37(QMz>H5#^8AClaSpTm%{yT95JzsBuoVo&?zYikg!8(=WuHx4je_pS9%JQBe z6Ue0N1oriKJoG##$e&|)e-nrQ0=A+6W)r<&oAj6ZctEEsf7+TA7UFMtB9Xjpjzo=+ za{HNJt!PFTQ5@j%+uZY4;%4%79?*9@(9hOs>{bHVQq^3WJLaGl1@Pub;JDVcb9oeZ zSh6?f{zNgc{QqIlujlm1f71H9bpn`3pBww6Rq_rGxeb>d%VRC?#O(HQ?8ndA*^TSQ z{hJZa_2YfHk6POxz_)PxCklQ{$sP4CqD5gWa1`$|c6;DeM3~|HI<7T`{g6q>){pNA zobs!zIyZNo@2MkfgmJHzJU_uRev;E=`nKIu%D)g%2Xo?gHQF4>e_dXsp&y~4OKJQg z^x-5u$bvbKXwA>hYqb{PpPBV( zNH=XF(z8752n3H(-f*z7)wAo{`W^x`E}kt~Im6_7v3@=_dG`B%%Ci;q{|~J4%D<~- zDh2S@pp;BZYiHfOe*%$bAa*%rM33{*9de zCs@IoJM^bt`&(g>4k|(7J>+zcU?LIDLFx(07r5s*!$I-G){j4BjTriag*-ewd~))& zoX12TJ6QgA5dB*@{SWmQ%kR*y9w=%7!TO7U)Q4o z)7sGJ53&FkO5R_i&N1eHhQ56tMgJ@E4DFpX=U00=9Ev@pzLbQR8eP>du@U5T$R2|H z5+XGGB_@751lgtYajiJR$lFuo^8B#lF$DN-p7pa_env*(XK`}--kyFgPsej%?Qno> z4{{P=T|Y_Zf8E;uAcU@KmY4wPo>7)3*sFmY9=h@PgUzGBzv>^*5&n#Zo z|2^LLn_T`o5eF+gcWeLt{XIwfy{5+HmtS67OycKse@!~lrlXohq~akudJ`RafMInr z`6VMHye2PxhI&BNJ|1+i^ed46C#)}P|1)?Aoag>cB7Ilm{oVZ6Slnzh>D)*ugc{oq zPT>XD>t|`;6gYqola#d^iE+ix-}+q=q}R!oX@X{{@~Z&$|~I1KJv67nqu00xWadJJl`8kCqrogc}Jl^%HtQh)(&8 zoig7ng=m1e5*uJ$_q-c_ooxh*3MM>O3NTUTV-~{Jw;)M`Ou8GaZqi$UxW(uQ!Sdk3 a(*FZjF#N0#D&^?_0000x5JU(hd=`YGS4sx7E$lJ-L{Qk$kyrA93%g?`ATgak+$8WjS##6XB~ zz~=ba=YH8|@BW^xdDK_0W;VwT&?P-uGi%nYSs@Qj?0?-T-iLJOop&~tmzTG! zudh#UY;5cV)7@@&5{!TXtoC}nIr16sHrHoYR#q0dwFlnQ)Y!Rm=X=ud(W6Jl*Vfj4 z0n#4^$H37}r?VfT+abAyDnlGMfvR`O*T53E1b#xD7r?XJ^8>_qjXKNki2z%+(3f{9 z-Ezwb)+wFetIvo-MoJG`s zU0PcD^8Eb#54qI&e-+@~d+*iQK0=3n7t1_A2lx9LR)1H=g-lKuD1bdhxzC4&hLo$_ z9?$v-0{p|=+}vMt%h{hvfQgBTYr65|$&(Y1|1CQ5d*s(+;f5`i1+eu=0|ii76u`&! zh~eSkK2(kfy-SxaJvTc$`=?i~T={2iU%VC`Z90D~QT-6={{eUa&m1>#r%2euBEgt) zJtHOy5P#zbv8zGZFM}^~{WkjYhg5y-YPq!RTARh~x8HsnivI=pb*wj>JL?pKoDj?N z^(=%vBzuJW8a<41_2C%DA0h6%#PK;$Y!)pP+*HNWr%yL_@7{esF84*;@1{m#o&%nt zPG&MO&!QOF`X;Yy9$FACh&wuNkUl{5U#097f`9OjB+zEi+DxVU?z^vni~S~J?#~H$ z2kD5&WhqpNqdFqR-1u%JggalJ;IKb@>Q+I{@@pp^?imF-Vh&%4M<9O-z zM}G!&HWQ5kWUJ=2fXj_YT<$Mec#)Ue8=OZWJ!NjA>=@U-OVRm(0_cXi1o?dgyU#P# z9j9Kcm7_~%LE?CiaLVn)++$z45>}VRqxJJ_5f@Us5J#)huhag#pOevYCo)*wjo|v9 z1E0bYjU4E*o91?sD3t{B5Xpjwyaj-a2q4SqB5%u^cox?SsYBe;10jD#Mn<&P`7HIH;L^X;t^@M8KXvNVUR?hnl>T5OlPzyV5D(gQ8PS{FSo0>e{Be|-n z&Lp!3*ut?ri-qgEEV_PPRo3n-ZItox@sr&907VaFu5Ck9&TITbOnIM{czLZJ-P6}h zA_YBLx7Iy5;tqbmVn=Vw0%Q@?R%>*0^fSb>ujJ)&TMvi{SNRRpI%<=3 z1SWs$Y4a0Y`m@NlO<}bDF6R7Ca+3#BwwjN#XH?DmmF_L9fn~6w`jn4=G0uld>wCwC zAw2#qcJ!_+uFfm_MVUlxBElyL>|dg2rqTm=;0e^e(aX)MD4wlpg`K-&5pM4ab%w$9 z-~gB^1-n9>AA=W4!6wT2-PVsYPTd~E=Xrk^7$k}NVT3r&;bX4TcA&ZIuDhBSFJ3%N zN4MB=IjH5a8Wql-5Xmn=XoRxgMud|bAE5qLJzQ;(ceplB{yOpKd*t7S_&nS6E_s!u zv*;$7&$9?{odBm1^>M#&ZF@DvQgXlNbeIZO zS-Fpe%4M#HMyzL-oTtMd<@|TB73S3&4Mg1-JnsQ#y|V#4(p*!@yv;LcJTvKMVQ8BD{5L z(c}$u12=C(@(!4)gY`ES7yJ1U`tcd=o#FVbR(0u(aw2G9Y6Y-B2(XP+8y=Xy>pxjpdu5Pu|w#n%nIQqRy+cYD0fwjE@%| zq>kG79M?y}N-v5K-%Wr)0yx)f@Jn$aqz;de4vyiTan~hl8)`UP-vLT_6k`ee8s|6B zM&IpuMo*oWuxg9;d&(V4c&?<-c@L}ig+PQR_uYg?oZ{XmxHk2Fo18rSl;=0g`7N`D zux7hM=g*%X(Zttg%`dkYhx~taJ9g}dn+_d&`|0Iq|J)wpWAE1d!eW{HgaVtqK^`FC z7}Gsjo!}+`x*YedEkc|Z5Y_em&`@XPMI39kIL`4`$+ws51d9-fy39y>1lP9!*CD`o zUjW@K0k&`7KDB4h9uxE8!vgq53*@|yShft&3FhvD)R$}b{lcV`t6hH)c0|{VP0=g65Pz-(WDQPS3}TSup+raZR2 z?zR_rUGD2b|61PF&CSmjLwMEn+iw?5&)sTE<5R;)#|pX}U+i`-j?uqKKA0Bjq{u54 zkkctxK#=ksv~hI%_M(3?IazFFNckN>9tuL-1m*RK1-N|q@+gNRo(5G}uf|%62PUY8 z&JG5eU?!=0H&L}+w{Z;Dg{zcmS^$4rSzjV^fU6ZCfAw+s zBIMeTT!-wstq;0#eMIO|u3<3L?Y2KYHFY-@ZEnoWvBz(ZVX>zXZ5uK1c*v8!jO9bd~|nMzUCY3F&c$nkjDCkN#G23dI&AWxI_01SSK z!)4HDtMIM2-daI`^B(JV^&C8uVtEuJy4r-+nRMtFmfnZ!dc?`%VA!PW5t(>YIJ>RgO}n~($KpGfx^5*FjA(zs1DOpy8BuE8zqI@+?LWo5|25he z57TjccHVzD4f^uDN~v@cI}*~PQcvD|h00hQx_!i782)5Wcn zO;Xl@{MtKMe}Q`6CGb6pXml`7brvF%i~WqpS%3=+@&D=T9Nz~p4W2{U7g1-Qt)2-x zWs84D@*Zb8AA*R+`6Iag1-#+}%ATXnAqXC%-ZVmt zmO*ftYcq)T3V5D-mb^|)Z=f^;fhym_~S5jIfgNjm!`fpAZJSQH^! z!%)gG!U7S56VUb)t3NZRiwEltB)q~IpyyY=~}HAkpC(D_?EP=12%&I zU3PM35aB5**8bunyM5h*{iCM%qlPY!WhX@xYzUkWmt|uRYNwALbt#Iz8Ss3K|L=cr z%geTY7GQ&lb5wc+5f<_T^TsESv1paFQj5#x+TR0^*SdKg*QpQhB$hw0%wyO`O84K$ z0;oYTTt|SXq!g}_8?K%D@{7{AUA6VW;PIMC*MD};80e|N`rqdGX>kNyUvGh&Is%=) z10tirI+f$D;@25ouY1b!o*@&+r0ahK_Vsu?@H{8TUuJm!35U;ul_-GOL@!t+eM=t? z=yc^zTeHGK{4Gx;lDEx~s1Z_bKNGAK&B!8(16;n!Jx_|8$=7*6&+$M%Tc@$(1hO4f zb8YUJgI*NCn>pbKH zE;wpS80a*NyvkBAn~T`*I((wn2c0IR3hVA5-$W`WMlnFcvt9_ZYif z@FpT`<@`pjH3$8WNyyfZ?+KjptE@UVcb@O5BW#3mua`W3$20z((~Ig6VuvRH_t)jRfz4S>;S|Y5RQLiKX%5hpM&2( zFUZ&PwT!AHgYj2qKXZ(gTAz1rUjlXOrC67L}=xڢZlBQ@)*h4l*1R zKWzQ@Q`U%qKUm1a!^0;hU(0z+^l=@_{~Mw|%;|rtzgT{aesw`n3kcR<1f(8hyLte} zS0Ht!Tpt`BB{B6$fH;5e$4_emqd&+3TrGKjjk?U3|1y31ONw3;d4~2{n)9nY9S+4N zQcp@kOpVU#m)Hn$0kZobzXK5({t^?v9fIuA`M6e`LFDZza(RB(aRvdtz_b35%dg5v z{47px-`&*D<>`1XtQ`)J?Ltl>Eb1reyjyz#A#`1{#285TBm#fQ`c(qt^Bm|r%y{+; z}g`n@o>#ok{TXht&)|)Za`|Zy2Xj1kW!J7&NYHSxcf*0JRpQV8#U^haHQ`T-I#uYz*>vc$wUL?Cn`YzI^Rj!|~1Q<5Jp zF_Z{55@J*8C3J%j>*X`n%6z#Lq5-B#EP+Mc^M3qwwh=5UnD9s`z*w1&SO{C+f+P_# p=}xe^Nv{Or7GqrqmIqgt{ufs;{H)R!B{Kj3002ovPDHLkV1kQrkB$HU diff --git a/Preferences/Preferences/Icons/updates.png b/Preferences/Preferences/Icons/updates.png index 75de347247e2fece414b4889a0e70c117ddcb219..6e94ba7c994b5648652f2bd078ad0c6f7e1e9e74 100644 GIT binary patch delta 2385 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 O00{s|MNUMnLSTZ9hPST( diff --git a/Preferences/Preferences/Icons/updates@2x.png b/Preferences/Preferences/Icons/updates@2x.png index 3bb63dc0d121079a1f0d1954281b89f688309b73..e6160e1dfa7bf0c484dce2d8595f31abe6c23cf1 100644 GIT binary patch literal 6682 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 #k=?bPq=1WtPav{eAbU)9*fJ&o$z ziCL5DlOWsKD<;T`TER8J2Q3;SINyRc*2d+_Zt|@keqiLcj;|Xlud%L^;}hS++pqjs z@b3DXn9dFp7=tK&`|AC5Xbd=EUVAv&+|1O%gGjxHtjycU&Q#8x+x^}TJ&`$~K~KAt zLc6sZSQ}yEChlo$g0pG*qpESORGZtod-lj5J=VDRw@(1afLR|atpeahXaZGjoUhid zymh)!TbvZ6=H~-MLIkyF)X?VqqOD!NU}rwGH<5E&t+?;!9_XofKfuYwtwd<*k-5=jS zKYaee2JtGy>aG_4lBh>Y@KV=X3Nps{yEn?J>|Ef=lreAyboTV?O0ra$bY%! zw=HKizS3)_-qzJH6g5i_w&4mLQXd%TE^k=dzqoZve(42|7VGOq7v1}w_}-zpX2<=a z5HDjaB@BVI4`}%scvvW+c*^&(OIq95>Ot2(kYFUF3nVUwY7h;<2 zPDK?HN&;Sh6cxQu3w;LZb!DcAn@TCR_WXrCx^#KS_He!PPOr{ z&d-ZGdD8#-7A^eyzaQpS?9FdJ0R#>n5?GZmCG?bGz9N(&rIwY2DdM@giAFQiC|^@qqbRpw z;a7dtG`D7ff)&`UuoW1Bu-!3Gf_^6u%k0OIxr>jLr1L8;g{J%N)<6+OuMKoJ$pC$3 zF`Tu{xODOC^c3}GpR=q99SkYtfULsBgbN9l+qLjEf20t55k`P6g>DcPsIycXk-6i0 z7uv@zd_wZ0FBS=d*0>OE$>h3EHmXjSt~_26nm+cr(z z6-{Vq!!hF6#wDn@i|_t;*m2LLAlEmBI$-5sQdJOn_TqKfsqZUFvwVKf%)TE#J^BFk z(b0Jehd*uqcbTa2X*ChrfK5X$e*1T{4ZiRz5EG&b2qFS_ZmO-Ks=h3Pix($mkNxSd zEFJpUw{~=J5s3LTTG-jir`sC9%B9(zV&}_l_S6`d+ykW?4r_QWAWI*g;W3Vj?&Se{~&JZ0B~;Qe(lq5!e`F>0XV(DI$+%7?p~i8df~ck z=N+pvZEJc>P|PV#ZF8X1i+Uc0`30tYZMp) zdi=9Y%YPEK;75rT_o{ literal 6701 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 + +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