Cog/Utils/SandboxBroker.m
Christopher Snowhill 5cb08291bf [Sandbox / Core Data] Refine global variable usage
Refine the global variable usage, eliminating double underscore prefixes
and trusting that static global variables will exist in the object where
the class was created. Got rid of that nasty NSApplication extension
hack that was previously in use.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-21 00:01:07 -07:00

228 lines
4.9 KiB
Objective-C

//
// 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 *kSharedSandboxBroker = nil;
static NSURL *urlWithoutFragment(NSURL *u) {
if(![u isFileURL]) return u;
NSNumber *isDirectory;
BOOL success = [u getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil];
if(success && [isDirectory boolValue]) return 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, ^{
kSharedSandboxBroker = [[self alloc] init];
});
return kSharedSandboxBroker;
}
+ (NSPersistentContainer *)sharedPersistentContainer {
return [NSClassFromString(@"PlaylistController") sharedPersistentContainer];
}
- (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];
}
}
}
+ (BOOL)isPath:(NSURL *)path aSubdirectoryOf:(NSURL *)directory {
NSArray *pathComponents = [path pathComponents];
NSArray *directoryComponents = [directory pathComponents];
if([pathComponents count] < [directoryComponents count])
return NO;
for(size_t i = 0; i < [directoryComponents count]; ++i) {
if(![pathComponents[i] isEqualToString:directoryComponents[i]])
return NO;
}
return YES;
}
- (SandboxEntry *)recursivePathTest:(NSURL *)url {
for(SandboxEntry *entry in storage) {
if([SandboxBroker isPath:url aSubdirectoryOf:[NSURL fileURLWithPath:entry.path]]) {
entry.refCount += 1;
return entry;
}
}
NSPersistentContainer *pc = [SandboxBroker sharedPersistentContainer];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"path.length" ascending:NO];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"SandboxToken"];
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([SandboxBroker isPath:url aSubdirectoryOf:[NSURL fileURLWithPath:token.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 nil;
}
entry.secureUrl = secureUrl;
[secureUrl startAccessingSecurityScopedResource];
return entry;
}
}
}
return nil;
}
- (const void *)beginFolderAccess:(NSURL *)fileUrl {
NSURL *folderUrl = [urlWithoutFragment(fileUrl) URLByDeletingLastPathComponent];
if(![folderUrl isFileURL]) return NULL;
SandboxEntry *entry;
@synchronized(self) {
entry = [self recursivePathTest:folderUrl];
}
if(entry) {
return CFBridgingRetain(entry);
} else {
return NULL;
}
}
- (void)endFolderAccess:(const void *)handle {
if(!handle) return;
SandboxEntry *entry = CFBridgingRelease(handle);
if(!entry) return;
@synchronized(self) {
if(entry.refCount > 1) {
entry.refCount -= 1;
return;
} else {
if(entry.secureUrl) {
[entry.secureUrl stopAccessingSecurityScopedResource];
entry.secureUrl = nil;
}
entry.refCount = 0;
[storage removeObject:entry];
}
}
}
@end