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
|
@ -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"];
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -11,8 +11,6 @@
|
|||
|
||||
#import "PlaybackController.h"
|
||||
|
||||
@class AudioScrobbler;
|
||||
|
||||
@interface PlaybackEventController
|
||||
: NSObject <NSUserNotificationCenterDelegate, UNUserNotificationCenterDelegate> {
|
||||
IBOutlet PlaybackController *playbackController;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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"/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
NSUInteger size;
|
||||
|
||||
NSURL *_url;
|
||||
NSURL *fileURL;
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -24,6 +24,8 @@ class BMPlayer;
|
|||
MIDIPlayer* player;
|
||||
midi_container midi_file;
|
||||
|
||||
NSURL* sandboxURL;
|
||||
|
||||
NSString* globalSoundFontPath;
|
||||
BOOL soundFontsAssigned;
|
||||
BOOL isLooped;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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")
|
||||
|
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 830 B |
After Width: | Height: | Size: 509 B |
After Width: | Height: | Size: 963 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 541 B |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
|
@ -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 */,
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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
|
|
@ -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
|