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 <kode54@gmail.com>
swiftingly
Christopher Snowhill 2022-06-20 03:35:29 -07:00
parent 8009d911c2
commit c23bece62c
55 changed files with 767 additions and 676 deletions

View File

@ -12,6 +12,7 @@
#import "PlaylistLoader.h"
#import "PlaylistView.h"
#import "SQLiteStore.h"
#import "SandboxBroker.h"
#import "SpotlightWindowController.h"
#import "StringToURLTransformer.h"
#import <CogAudio/Status.h>
@ -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"];

View File

@ -3,7 +3,6 @@
#import <Cocoa/Cocoa.h>
#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

View File

@ -11,8 +11,6 @@
#import "PlaybackController.h"
@class AudioScrobbler;
@interface PlaybackEventController
: NSObject <NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate> {
IBOutlet PlaybackController *playbackController;

View File

@ -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 {

View File

@ -1,45 +0,0 @@
/*
* $Id: AudioScrobbler.h 238 2007-01-26 22:55:20Z stephen_booth $
*
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
*
* 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 <Cocoa/Cocoa.h>
#import <mach/mach.h>
@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

View File

@ -1,259 +0,0 @@
/*
* $Id: AudioScrobbler.m 238 2007-01-26 22:55:20Z stephen_booth $
*
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
*
* 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

View File

@ -1,41 +0,0 @@
/*
* $Id: AudioScrobblerClient.h 666 2007-04-26 16:35:18Z stephen_booth $
*
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
*
* 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 <Cocoa/Cocoa.h>
#include <netdb.h>
@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

View File

@ -1,212 +0,0 @@
/*
* $Id: AudioScrobblerClient.m 869 2007-06-18 15:51:33Z stephen_booth $
*
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
*
* 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 <arpa/inet.h>
#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

View File

@ -25,17 +25,17 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<splitView dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2123">
<rect key="frame" x="0.0" y="226" width="1000" height="174"/>
<rect key="frame" x="0.0" y="277" width="1000" height="123"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="0.0" verticalLineScroll="24" verticalPageScroll="0.0" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="206" userLabel="Scroll View - Playlist View">
<rect key="frame" x="0.0" y="0.0" width="1000" height="174"/>
<rect key="frame" x="0.0" y="0.0" width="1000" height="123"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KWC-Ti-8KY">
<rect key="frame" x="0.0" y="0.0" width="1000" height="174"/>
<rect key="frame" x="0.0" y="0.0" width="1000" height="123"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="Playlist" rowHeight="18" headerView="1517" viewBased="YES" id="207" customClass="PlaylistView">
<rect key="frame" x="0.0" y="0.0" width="1000" height="157"/>
<rect key="frame" x="0.0" y="0.0" width="1000" height="106"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="6"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>

View File

@ -2,7 +2,19 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.assets.movies.read-only</key>
<true/>
<key>com.apple.security.assets.music.read-only</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.files.downloads.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -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 = "<group>"; };
1755E1F60BA0D2B600CA3560 /* PlaylistLoader.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlaylistLoader.h; sourceTree = "<group>"; };
1755E1F70BA0D2B600CA3560 /* PlaylistLoader.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PlaylistLoader.m; sourceTree = "<group>"; };
1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobbler.h; sourceTree = "<group>"; };
1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobbler.m; sourceTree = "<group>"; };
1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobblerClient.h; sourceTree = "<group>"; };
1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobblerClient.m; sourceTree = "<group>"; };
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 = "<group>"; };
177042980B8BC53600B86321 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = "<group>"; };
@ -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 = "<group>"; };
56DB08540D67185300453B6A /* NSArray+CogSort.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSArray+CogSort.m"; path = "Spotlight/NSArray+CogSort.m"; sourceTree = "<group>"; };
83059634277F011100EBFAAE /* File_Extractor.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = File_Extractor.xcodeproj; path = Frameworks/File_Extractor/File_Extractor.xcodeproj; sourceTree = "<group>"; };
8307D30C28606148000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SandboxBroker.h; sourceTree = "<group>"; };
8307D30D28606148000FF8EB /* SandboxBroker.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SandboxBroker.m; sourceTree = "<group>"; };
830C37A027B95E3000E02BB0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Equalizer.xib; sourceTree = "<group>"; };
830C37A327B95EB300E02BB0 /* EqualizerWindowController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EqualizerWindowController.h; path = Equalizer/EqualizerWindowController.h; sourceTree = "<group>"; };
830C37A427B95EB300E02BB0 /* EqualizerWindowController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = EqualizerWindowController.m; path = Equalizer/EqualizerWindowController.m; sourceTree = "<group>"; };
@ -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 = "<group>";
};
1766C68D0B911DF1004A7AE4 /* AudioScrobbler */ = {
isa = PBXGroup;
children = (
1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */,
1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */,
1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */,
1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */,
);
path = AudioScrobbler;
sourceTree = "<group>";
};
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 = "<group>";
@ -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 */,

View File

@ -60,9 +60,14 @@
<attribute name="volume" optional="YES" attributeType="Float" defaultValueString="1" usesScalarValueType="YES"/>
<attribute name="year" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
<entity name="SandboxToken" representedClassName="SandboxToken" syncable="YES" codeGenerationType="class">
<attribute name="bookmark" optional="YES" attributeType="Binary"/>
<attribute name="path" optional="YES" attributeType="String"/>
</entity>
<elements>
<element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/>
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="704"/>
<element name="PlayCount" positionX="-18" positionY="171" width="128" height="134"/>
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="704"/>
<element name="SandboxToken" positionX="-18" positionY="171" width="128" height="59"/>
</elements>
</model>

View File

@ -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;

View File

@ -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<NSString *, AlbumArtwork *> *__artworkDictionary = nil;
static void *playlistControllerContext = &playlistControllerContext;

View File

@ -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 = "<group>"; };
8359009717FEF6490060F3ED /* ArchiveSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveSource.h; sourceTree = "<group>"; };
8359009817FEF6490060F3ED /* ArchiveSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ArchiveSource.m; sourceTree = "<group>"; };
8359009A17FEFDA80060F3ED /* ArchiveContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ArchiveContainer.h; sourceTree = "<group>"; };
@ -102,6 +103,7 @@
8359FF2017FEF35C0060F3ED /* ArchiveSource */ = {
isa = PBXGroup;
children = (
8307D31A286070EA000FF8EB /* SandboxBroker.h */,
8384913518081BA000E7332D /* Logging.h */,
835900A017FF079C0060F3ED /* Plugin.h */,
8359009A17FEFDA80060F3ED /* ArchiveContainer.h */,

View File

@ -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;
}

View File

@ -20,6 +20,7 @@
NSUInteger size;
NSURL *_url;
NSURL *fileURL;
}
@end

View File

@ -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 {

View File

@ -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 {

View File

@ -37,6 +37,7 @@
17ADB4180B979AEB00257CA2 /* FileSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileSource.h; sourceTree = "<group>"; };
17ADB4190B979AEB00257CA2 /* FileSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileSource.m; sourceTree = "<group>"; };
32DBCF630370AF2F00C91783 /* FileSource_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileSource_Prefix.pch; sourceTree = "<group>"; };
8307D31B2860722C000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../Utils/SandboxBroker.h; sourceTree = "<group>"; };
8335FF6817FF765A002D8DD2 /* File_Extractor.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = File_Extractor.xcodeproj; path = ../../Frameworks/File_Extractor/File_Extractor.xcodeproj; sourceTree = "<group>"; };
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 = "<group>"; };
@ -88,6 +89,7 @@
08FB77AFFE84173DC02AAC07 /* Classes */ = {
isa = PBXGroup;
children = (
8307D31B2860722C000FF8EB /* SandboxBroker.h */,
17ADB4080B979A8A00257CA2 /* Plugin.h */,
17ADB4180B979AEB00257CA2 /* FileSource.h */,
17ADB4190B979AEB00257CA2 /* FileSource.m */,

View File

@ -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);

View File

@ -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 = "<group>"; };
831E2A7F27B4B2B2006F1C86 /* bassmidi.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bassmidi.h; sourceTree = "<group>"; };
831E2A8027B4B2B2006F1C86 /* sflist.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sflist.c; sourceTree = "<group>"; };
831E2A8227B4B2B2006F1C86 /* sflist_rewrite.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sflist_rewrite.c; sourceTree = "<group>"; };
@ -295,6 +296,7 @@
83B06690180D5668008E3612 /* MIDI */ = {
isa = PBXGroup;
children = (
8307D31E28607377000FF8EB /* SandboxBroker.h */,
831E2A9127B4B2FA006F1C86 /* json */,
831E2A7D27B4B2B2006F1C86 /* BASS */,
8356BCC427B352620074E50C /* BMPlayer.cpp */,

View File

@ -24,6 +24,8 @@ class BMPlayer;
MIDIPlayer* player;
midi_container midi_file;
NSURL* sandboxURL;
NSString* globalSoundFontPath;
BOOL soundFontsAssigned;
BOOL isLooped;

View File

@ -18,6 +18,8 @@
#import "PlaylistController.h"
#import "SandboxBroker.h"
#import <dlfcn.h>
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 {

View File

@ -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 = "<group>"; };
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 = "<group>"; };
8307D31C286072BF000FF8EB /* SandboxBroker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SandboxBroker.h; path = ../../Utils/SandboxBroker.h; sourceTree = "<group>"; };
8356BCE327B377C20074E50C /* TagLibID3v2Reader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TagLibID3v2Reader.h; sourceTree = "<group>"; };
8356BCE427B377C20074E50C /* TagLibID3v2Reader.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TagLibID3v2Reader.mm; sourceTree = "<group>"; };
8384913918081FFC00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
@ -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;

View File

@ -19,6 +19,8 @@
#import <taglib/ogg/vorbis/vorbisfile.h>
#import <taglib/ogg/xiphcomment.h>
#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;
}

View File

@ -9,13 +9,13 @@
<customObject id="-2" userLabel="File's Owner" customClass="GeneralPreferencesPlugin">
<connections>
<outlet property="appearanceView" destination="CgN-sy-RmM" id="wvB-aW-Gfx"/>
<outlet property="generalPane" destination="tm7-TU-wbV" id="jTO-w4-xwD"/>
<outlet property="hotKeyPane" destination="6" id="14"/>
<outlet property="iTunesStyleCheck" destination="AIz-WH-Wqk" id="2n1-pY-cZR"/>
<outlet property="midiPane" destination="i5B-ga-Atm" id="rbe-uK-5n2"/>
<outlet property="notificationsView" destination="U4w-jw-ca5" id="wVJ-GH-A21"/>
<outlet property="outputPane" destination="57" id="75"/>
<outlet property="playlistView" destination="231" id="244"/>
<outlet property="scrobblerView" destination="0nK-XQ-5MY" id="dFj-Pk-FCG"/>
<outlet property="updatesView" destination="50" id="99"/>
</connections>
</customObject>
@ -368,6 +368,12 @@
<outlet property="view" destination="JXu-ar-J3Y" id="uI3-0O-h4x"/>
</connections>
</customObject>
<customObject id="tm7-TU-wbV" customClass="GeneralPane">
<connections>
<outlet property="sandboxBehaviorController" destination="9mC-3k-IQG" id="QzA-u4-vH7"/>
<outlet property="view" destination="0nK-XQ-5MY" id="TDK-Vn-N5G"/>
</connections>
</customObject>
<arrayController id="59" userLabel="OutputDevices" customClass="OutputsArrayController">
<declaredKeys>
<string>name</string>
@ -389,45 +395,96 @@
</declaredKeys>
<classReference key="objectClass" className="NSDictionary"/>
</arrayController>
<customView id="0nK-XQ-5MY" userLabel="ScrobblerView">
<rect key="frame" x="0.0" y="0.0" width="530" height="99"/>
<customView id="0nK-XQ-5MY" userLabel="GeneralView">
<rect key="frame" x="0.0" y="0.0" width="530" height="202"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3gi-0d-78Z">
<rect key="frame" x="18" y="41" width="396" height="18"/>
<autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="check" title="Automatically launch Last.fm client" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="ze8-9p-SeX">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="52" name="value" keyPath="values.automaticallyLaunchLastFM" id="Ere-1R-mgh"/>
</connections>
</button>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ABj-cO-MaV">
<rect key="frame" x="18" y="69" width="396" height="18"/>
<autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="check" title="Enable Last.fm support (Last.fm client must be installed)" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="2N5-DH-IO6">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="52" name="value" keyPath="values.enableAudioScrobbler" id="R2p-gP-5oR"/>
</connections>
</button>
<button fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Hi4-pa-uDu">
<rect key="frame" x="18" y="13" width="389" height="18"/>
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="dIu-uT-8YW">
<rect key="frame" x="18" y="165" width="226" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="Allow Last.fm client to control media keys" bezelStyle="regularSquare" imagePosition="left" inset="2" id="4Yi-67-ivc">
<buttonCell key="cell" type="check" title="Allow insecure SSL connections" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="sva-DV-ina">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<binding destination="52" name="value" keyPath="values.allowLastfmMediaKeys" id="OZH-0Q-thF"/>
<binding destination="52" name="value" keyPath="values.allowInsecureSSL" id="78K-25-qki"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="crf-C9-9YF">
<rect key="frame" x="20" y="142" width="170" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Sandbox allow paths:" id="wWm-AD-cD4">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6b3-9W-Flc">
<rect key="frame" x="20" y="20" width="490" height="115"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="gUE-Yu-LLA">
<rect key="frame" x="1" y="1" width="488" height="113"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" headerView="9rQ-Rq-K6J" id="gHG-xw-OyR">
<rect key="frame" x="0.0" y="0.0" width="488" height="85"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="17" height="0.0"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="388" minWidth="40" maxWidth="1000" id="Yz2-Ee-bZ0" userLabel="path">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Path">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Dhd-k8-ZXq">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<connections>
<binding destination="9mC-3k-IQG" name="value" keyPath="arrangedObjects.path" id="eva-uV-ce2"/>
</connections>
</tableColumn>
<tableColumn width="41" minWidth="40" maxWidth="1000" id="lEg-E3-TBs" userLabel="valid">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Valid">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="bWQ-dT-X3w">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<connections>
<binding destination="9mC-3k-IQG" name="value" keyPath="arrangedObjects.valid" id="Kg6-FI-Mcl"/>
</connections>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="OdN-bV-v6B">
<rect key="frame" x="1" y="99" width="488" height="15"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="rJY-00-GfQ">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<tableHeaderView key="headerView" wantsLayer="YES" id="9rQ-Rq-K6J">
<rect key="frame" x="0.0" y="0.0" width="488" height="28"/>
<autoresizingMask key="autoresizingMask"/>
</tableHeaderView>
<connections>
<outlet property="menu" destination="ape-AV-kEr" id="hVx-ll-1cd"/>
</connections>
</scrollView>
</subviews>
<point key="canvasLocation" x="450" y="-262"/>
<point key="canvasLocation" x="450" y="-211"/>
</customView>
<customView id="U4w-jw-ca5" userLabel="NotificationsView">
<rect key="frame" x="0.0" y="0.0" width="530" height="99"/>
@ -709,5 +766,28 @@
</declaredKeys>
<classReference key="objectClass" className="NSDictionary"/>
</arrayController>
<arrayController id="9mC-3k-IQG" customClass="SandboxPathBehaviorController">
<declaredKeys>
<string>path</string>
<string>valid</string>
</declaredKeys>
</arrayController>
<menu id="ape-AV-kEr" userLabel="SandboxContextMenu">
<items>
<menuItem title="Add Path" id="JxP-0t-mTB">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="addPath:" target="tm7-TU-wbV" id="j3o-oG-PA4"/>
</connections>
</menuItem>
<menuItem title="Delete Paths" id="nva-pz-xSS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="deleteSelectedPaths:" target="tm7-TU-wbV" id="suU-Gb-vMn"/>
</connections>
</menuItem>
</items>
<point key="canvasLocation" x="533" y="-395"/>
</menu>
</objects>
</document>

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -7,7 +7,6 @@
objects = {
/* Begin PBXBuildFile section */
1766C7A80B912A71004A7AE4 /* lastfm.png in Resources */ = {isa = PBXBuildFile; fileRef = 1766C7A70B912A71004A7AE4 /* lastfm.png */; };
178E386E0C3DA64500EE6711 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 178E386D0C3DA64500EE6711 /* InfoPlist.strings */; };
17C643380B8A77CC00C53518 /* OutputsArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C643360B8A77CC00C53518 /* OutputsArrayController.m */; };
17C6433F0B8A783F00C53518 /* OutputPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 17C6433E0B8A783F00C53518 /* OutputPane.m */; };
@ -17,18 +16,21 @@
17E41DB80C130AA500AC744D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 17E41DB70C130AA500AC744D /* Localizable.strings */; };
17E78A7E0D68BE3C005C5A59 /* file_tree.png in Resources */ = {isa = PBXBuildFile; fileRef = 17E78A7D0D68BE3C005C5A59 /* file_tree.png */; };
17E78B6A0D68C1E3005C5A59 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 17E78B680D68C1E3005C5A59 /* Preferences.xib */; };
8307D30528604D8B000FF8EB /* SandboxPathBehaviorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */; };
8307D30B286057B5000FF8EB /* GeneralPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 8307D30A286057B5000FF8EB /* GeneralPane.m */; };
8307D31628606EAF000FF8EB /* general@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31228606EAF000FF8EB /* general@2x.png */; };
8307D31728606EAF000FF8EB /* growl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31328606EAF000FF8EB /* growl@2x.png */; };
8307D31828606EAF000FF8EB /* general.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31428606EAF000FF8EB /* general.png */; };
8307D31928606EAF000FF8EB /* growl.png in Resources */ = {isa = PBXBuildFile; fileRef = 8307D31528606EAF000FF8EB /* growl.png */; };
83651DA527322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83651DA327322C8700A2C097 /* MIDIFlavorBehaviorArrayController.m */; };
8372053718E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 8372053618E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.m */; };
837C0D401C50954000CAE18F /* MIDIPluginBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 837C0D3F1C50954000CAE18F /* MIDIPluginBehaviorArrayController.m */; };
8384917718084D9F00E7332D /* appearance.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384917518084D9F00E7332D /* appearance.png */; };
8384917818084D9F00E7332D /* growl.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384917618084D9F00E7332D /* growl.png */; };
83A3B72C283AE04800CC6593 /* ColorToValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 83A3B72B283AE04800CC6593 /* ColorToValueTransformer.m */; };
83B06729180D85B8008E3612 /* MIDIPane.m in Sources */ = {isa = PBXBuildFile; fileRef = 83B06728180D85B8008E3612 /* MIDIPane.m */; };
83B0672B180D8B39008E3612 /* midi.png in Resources */ = {isa = PBXBuildFile; fileRef = 83B0672A180D8B39008E3612 /* midi.png */; };
83EF495F17FBC96A00642E3C /* VolumeBehaviorArrayController.m in Sources */ = {isa = PBXBuildFile; fileRef = 83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */; };
83F27E6B1810DD3A00CEF538 /* appearance@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E651810DD3A00CEF538 /* appearance@2x.png */; };
83F27E6C1810DD3A00CEF538 /* growl@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E661810DD3A00CEF538 /* growl@2x.png */; };
83F27E6D1810DD3A00CEF538 /* lastfm@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E671810DD3A00CEF538 /* lastfm@2x.png */; };
83F27E6E1810DD3A00CEF538 /* midi@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E681810DD3A00CEF538 /* midi@2x.png */; };
83F27E6F1810DD3A00CEF538 /* playlist@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E691810DD3A00CEF538 /* playlist@2x.png */; };
83F27E701810DD3A00CEF538 /* updates@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 83F27E6A1810DD3A00CEF538 /* updates@2x.png */; };
@ -78,7 +80,6 @@
089C1672FE841209C02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
089C167FFE841241C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
1058C7ADFEA557BF11CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
1766C7A70B912A71004A7AE4 /* lastfm.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = lastfm.png; path = Icons/lastfm.png; sourceTree = "<group>"; };
17C643360B8A77CC00C53518 /* OutputsArrayController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = OutputsArrayController.m; sourceTree = "<group>"; };
17C643370B8A77CC00C53518 /* OutputsArrayController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputsArrayController.h; sourceTree = "<group>"; };
17C6433D0B8A783F00C53518 /* OutputPane.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = OutputPane.h; sourceTree = "<group>"; };
@ -91,6 +92,15 @@
32DBCF630370AF2F00C91783 /* Preferences_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Preferences_Prefix.pch; sourceTree = "<group>"; };
3DFAC48E235B6B8100A29416 /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = "<group>"; };
3DFAC48F235B6B8100A29416 /* Shared.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
8307D30328604D8B000FF8EB /* SandboxPathBehaviorController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SandboxPathBehaviorController.h; sourceTree = "<group>"; };
8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SandboxPathBehaviorController.m; sourceTree = "<group>"; };
8307D30828604ECF000FF8EB /* PlaylistController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlaylistController.h; path = ../../Playlist/PlaylistController.h; sourceTree = "<group>"; };
8307D309286057B5000FF8EB /* GeneralPane.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneralPane.h; sourceTree = "<group>"; };
8307D30A286057B5000FF8EB /* GeneralPane.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneralPane.m; sourceTree = "<group>"; };
8307D31228606EAF000FF8EB /* general@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "general@2x.png"; path = "Icons/general@2x.png"; sourceTree = "<group>"; };
8307D31328606EAF000FF8EB /* growl@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "growl@2x.png"; path = "Icons/growl@2x.png"; sourceTree = "<group>"; };
8307D31428606EAF000FF8EB /* general.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = general.png; path = Icons/general.png; sourceTree = "<group>"; };
8307D31528606EAF000FF8EB /* growl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = growl.png; path = Icons/growl.png; sourceTree = "<group>"; };
833F681B1CDBCAA700AFB9F0 /* es */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = "<group>"; };
833F681C1CDBCAA700AFB9F0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
8347435D20E6D58800063D45 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Preferences.strings; sourceTree = "<group>"; };
@ -106,7 +116,6 @@
837C0D3F1C50954000CAE18F /* MIDIPluginBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MIDIPluginBehaviorArrayController.m; sourceTree = "<group>"; };
8384913618081ECB00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logging.h; path = ../../Utils/Logging.h; sourceTree = "<group>"; };
8384917518084D9F00E7332D /* appearance.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = appearance.png; path = Icons/appearance.png; sourceTree = "<group>"; };
8384917618084D9F00E7332D /* growl.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = growl.png; path = Icons/growl.png; sourceTree = "<group>"; };
83A3B72A283AE04800CC6593 /* ColorToValueTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ColorToValueTransformer.h; sourceTree = "<group>"; };
83A3B72B283AE04800CC6593 /* ColorToValueTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ColorToValueTransformer.m; sourceTree = "<group>"; };
83B06727180D85B8008E3612 /* MIDIPane.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MIDIPane.h; sourceTree = "<group>"; };
@ -117,8 +126,6 @@
83EF495D17FBC96A00642E3C /* VolumeBehaviorArrayController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VolumeBehaviorArrayController.h; sourceTree = "<group>"; };
83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = VolumeBehaviorArrayController.m; sourceTree = "<group>"; };
83F27E651810DD3A00CEF538 /* appearance@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "appearance@2x.png"; path = "Icons/appearance@2x.png"; sourceTree = "<group>"; };
83F27E661810DD3A00CEF538 /* growl@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "growl@2x.png"; path = "Icons/growl@2x.png"; sourceTree = "<group>"; };
83F27E671810DD3A00CEF538 /* lastfm@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "lastfm@2x.png"; path = "Icons/lastfm@2x.png"; sourceTree = "<group>"; };
83F27E681810DD3A00CEF538 /* midi@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "midi@2x.png"; path = "Icons/midi@2x.png"; sourceTree = "<group>"; };
83F27E691810DD3A00CEF538 /* playlist@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "playlist@2x.png"; path = "Icons/playlist@2x.png"; sourceTree = "<group>"; };
83F27E6A1810DD3A00CEF538 /* updates@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "updates@2x.png"; path = "Icons/updates@2x.png"; sourceTree = "<group>"; };
@ -192,6 +199,7 @@
08FB77AFFE84173DC02AAC07 /* Classes */ = {
isa = PBXGroup;
children = (
8307D30828604ECF000FF8EB /* PlaylistController.h */,
83F27E711810E41A00CEF538 /* Transformers */,
8384913618081ECB00E7332D /* Logging.h */,
83D34B7D27A10FA700784D34 /* HeadphoneFilter.h */,
@ -235,6 +243,8 @@
17C6433E0B8A783F00C53518 /* OutputPane.m */,
83B06727180D85B8008E3612 /* MIDIPane.h */,
83B06728180D85B8008E3612 /* MIDIPane.m */,
8307D309286057B5000FF8EB /* GeneralPane.h */,
8307D30A286057B5000FF8EB /* GeneralPane.m */,
);
name = Panes;
sourceTree = "<group>";
@ -254,6 +264,8 @@
8372053618E3DEAF007EFAD4 /* ResamplerBehaviorArrayController.m */,
83EF495D17FBC96A00642E3C /* VolumeBehaviorArrayController.h */,
83EF495E17FBC96A00642E3C /* VolumeBehaviorArrayController.m */,
8307D30328604D8B000FF8EB /* SandboxPathBehaviorController.h */,
8307D30428604D8B000FF8EB /* SandboxPathBehaviorController.m */,
);
name = Custom;
sourceTree = "<group>";
@ -300,21 +312,21 @@
8E07ABD90AAC95AF00A4B32F /* Icons */ = {
isa = PBXGroup;
children = (
83F27E651810DD3A00CEF538 /* appearance@2x.png */,
83F27E661810DD3A00CEF538 /* growl@2x.png */,
83F27E671810DD3A00CEF538 /* lastfm@2x.png */,
83F27E681810DD3A00CEF538 /* midi@2x.png */,
83F27E691810DD3A00CEF538 /* playlist@2x.png */,
83F27E6A1810DD3A00CEF538 /* updates@2x.png */,
83B0672A180D8B39008E3612 /* midi.png */,
8384917518084D9F00E7332D /* appearance.png */,
8384917618084D9F00E7332D /* growl.png */,
17C7E5AF0DCCC30A003CBCF7 /* playlist.png */,
83F27E651810DD3A00CEF538 /* appearance@2x.png */,
17E78A7D0D68BE3C005C5A59 /* file_tree.png */,
1766C7A70B912A71004A7AE4 /* lastfm.png */,
17C643680B8A788000C53518 /* output.png */,
8E15A86B0B894768006DC802 /* updates.png */,
8307D31428606EAF000FF8EB /* general.png */,
8307D31228606EAF000FF8EB /* general@2x.png */,
8307D31528606EAF000FF8EB /* growl.png */,
8307D31328606EAF000FF8EB /* growl@2x.png */,
8E07ABDB0AAC95BC00A4B32F /* hot_keys.png */,
83B0672A180D8B39008E3612 /* midi.png */,
83F27E681810DD3A00CEF538 /* midi@2x.png */,
17C643680B8A788000C53518 /* output.png */,
17C7E5AF0DCCC30A003CBCF7 /* playlist.png */,
83F27E691810DD3A00CEF538 /* playlist@2x.png */,
8E15A86B0B894768006DC802 /* updates.png */,
83F27E6A1810DD3A00CEF538 /* updates@2x.png */,
);
name = Icons;
sourceTree = "<group>";
@ -425,21 +437,21 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8307D31628606EAF000FF8EB /* general@2x.png in Resources */,
83B0672B180D8B39008E3612 /* midi.png in Resources */,
83F27E6D1810DD3A00CEF538 /* lastfm@2x.png in Resources */,
178E386E0C3DA64500EE6711 /* InfoPlist.strings in Resources */,
8307D31928606EAF000FF8EB /* growl.png in Resources */,
8E07ABDD0AAC95BC00A4B32F /* hot_keys.png in Resources */,
8384917818084D9F00E7332D /* growl.png in Resources */,
8307D31728606EAF000FF8EB /* growl@2x.png in Resources */,
8E15A86C0B894768006DC802 /* updates.png in Resources */,
8384917718084D9F00E7332D /* appearance.png in Resources */,
17C643690B8A788000C53518 /* output.png in Resources */,
1766C7A80B912A71004A7AE4 /* lastfm.png in Resources */,
83F27E6F1810DD3A00CEF538 /* playlist@2x.png in Resources */,
17E41DB80C130AA500AC744D /* Localizable.strings in Resources */,
8307D31828606EAF000FF8EB /* general.png in Resources */,
17E78A7E0D68BE3C005C5A59 /* file_tree.png in Resources */,
83F27E701810DD3A00CEF538 /* updates@2x.png in Resources */,
17E78B6A0D68C1E3005C5A59 /* Preferences.xib in Resources */,
83F27E6C1810DD3A00CEF538 /* growl@2x.png in Resources */,
83F27E6B1810DD3A00CEF538 /* appearance@2x.png in Resources */,
17C7E5B00DCCC30A003CBCF7 /* playlist.png in Resources */,
83F27E6E1810DD3A00CEF538 /* midi@2x.png in Resources */,
@ -461,6 +473,8 @@
8E07AA8A0AAC8EA200A4B32F /* GeneralPreferencesPlugin.m in Sources */,
83EF495F17FBC96A00642E3C /* VolumeBehaviorArrayController.m in Sources */,
17C643380B8A77CC00C53518 /* OutputsArrayController.m in Sources */,
8307D30528604D8B000FF8EB /* SandboxPathBehaviorController.m in Sources */,
8307D30B286057B5000FF8EB /* GeneralPane.m in Sources */,
83A3B72C283AE04800CC6593 /* ColorToValueTransformer.m in Sources */,
837C0D401C50954000CAE18F /* MIDIPluginBehaviorArrayController.m in Sources */,
17C6433F0B8A783F00C53518 /* OutputPane.m in Sources */,

View File

@ -0,0 +1,19 @@
//
// SandboxPathBehaviorController.h
// Preferences
//
// Created by Christopher Snowhill on 6/19/22.
//
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface SandboxPathBehaviorController : NSArrayController
- (void)addUrl:(NSURL *)url;
- (void)removePath:(NSString *)path;
@end
NS_ASSUME_NONNULL_END

View File

@ -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 <CoreData/CoreData.h>
#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

View File

@ -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";
"Enqueue and play" = "Enqueue and play";
"ValidNo" = "No";
"ValidYes" = "Yes";

View File

@ -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";
"Enqueue and play" = "Enqueue and play";
"ValidNo" = "No";
"ValidYes" = "Yes";

View File

@ -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]) {

26
Utils/SandboxBroker.h Normal file
View File

@ -0,0 +1,26 @@
//
// SandboxBroker.h
// Cog
//
// Created by Christopher Snowhill on 6/20/22.
//
#import <Foundation/Foundation.h>
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

237
Utils/SandboxBroker.m Normal file
View File

@ -0,0 +1,237 @@
//
// SandboxBroker.m
// Cog
//
// Created by Christopher Snowhill on 6/20/22.
//
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#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