2007-02-24 20:36:27 +00:00
|
|
|
#import "PluginController.h"
|
2015-04-13 07:39:24 +00:00
|
|
|
#import "CogPluginMulti.h"
|
2022-02-07 05:49:27 +00:00
|
|
|
#import "Plugin.h"
|
2007-02-24 20:36:27 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2022-01-18 11:06:03 +00:00
|
|
|
#import "NSFileHandle+CreateFile.h"
|
|
|
|
|
2022-02-10 23:29:13 +00:00
|
|
|
#import "NSDictionary+Merge.h"
|
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
#import "RedundantPlaylistDataStore.h"
|
|
|
|
|
|
|
|
#import <chrono>
|
|
|
|
#import <map>
|
|
|
|
#import <mutex>
|
|
|
|
#import <thread>
|
|
|
|
|
|
|
|
struct Cached_Metadata {
|
|
|
|
std::chrono::steady_clock::time_point time_accessed;
|
|
|
|
NSDictionary *properties;
|
|
|
|
NSDictionary *metadata;
|
|
|
|
Cached_Metadata()
|
|
|
|
: properties(nil), metadata(nil) {
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static std::mutex Cache_Lock;
|
|
|
|
|
|
|
|
static std::map<std::string, Cached_Metadata> Cache_List;
|
|
|
|
|
|
|
|
static RedundantPlaylistDataStore *Cache_Data_Store = nil;
|
|
|
|
|
|
|
|
static bool Cache_Running = false;
|
|
|
|
|
|
|
|
static std::thread *Cache_Thread = NULL;
|
|
|
|
|
|
|
|
static void cache_run();
|
|
|
|
|
|
|
|
static void cache_init() {
|
|
|
|
Cache_Data_Store = [[RedundantPlaylistDataStore alloc] init];
|
|
|
|
Cache_Thread = new std::thread(cache_run);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cache_deinit() {
|
|
|
|
Cache_Running = false;
|
|
|
|
Cache_Thread->join();
|
|
|
|
delete Cache_Thread;
|
|
|
|
Cache_Data_Store = nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cache_insert_properties(NSURL *url, NSDictionary *properties) {
|
2022-06-10 06:18:51 +00:00
|
|
|
if(properties == nil) return;
|
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
std::lock_guard<std::mutex> lock(Cache_Lock);
|
|
|
|
|
|
|
|
std::string path = [[url absoluteString] UTF8String];
|
|
|
|
properties = [Cache_Data_Store coalesceEntryInfo:properties];
|
|
|
|
|
|
|
|
Cached_Metadata &entry = Cache_List[path];
|
|
|
|
|
|
|
|
entry.properties = properties;
|
|
|
|
entry.time_accessed = std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cache_insert_metadata(NSURL *url, NSDictionary *metadata) {
|
2022-06-10 06:18:51 +00:00
|
|
|
if(metadata == nil) return;
|
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
std::lock_guard<std::mutex> lock(Cache_Lock);
|
|
|
|
|
|
|
|
std::string path = [[url absoluteString] UTF8String];
|
|
|
|
metadata = [Cache_Data_Store coalesceEntryInfo:metadata];
|
|
|
|
|
|
|
|
Cached_Metadata &entry = Cache_List[path];
|
|
|
|
|
|
|
|
entry.metadata = metadata;
|
|
|
|
entry.time_accessed = std::chrono::steady_clock::now();
|
|
|
|
}
|
|
|
|
|
|
|
|
static NSDictionary *cache_access_properties(NSURL *url) {
|
|
|
|
std::lock_guard<std::mutex> lock(Cache_Lock);
|
|
|
|
|
|
|
|
std::string path = [[url absoluteString] UTF8String];
|
|
|
|
|
|
|
|
Cached_Metadata &entry = Cache_List[path];
|
|
|
|
|
|
|
|
if(entry.properties) {
|
|
|
|
entry.time_accessed = std::chrono::steady_clock::now();
|
|
|
|
return entry.properties;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static NSDictionary *cache_access_metadata(NSURL *url) {
|
|
|
|
std::lock_guard<std::mutex> lock(Cache_Lock);
|
|
|
|
|
|
|
|
std::string path = [[url absoluteString] UTF8String];
|
|
|
|
|
|
|
|
Cached_Metadata &entry = Cache_List[path];
|
|
|
|
|
|
|
|
if(entry.metadata) {
|
|
|
|
entry.time_accessed = std::chrono::steady_clock::now();
|
|
|
|
return entry.metadata;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cache_run() {
|
|
|
|
std::chrono::milliseconds dura(250);
|
|
|
|
|
|
|
|
while(Cache_Running) {
|
|
|
|
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
|
|
|
|
2022-02-27 07:30:18 +00:00
|
|
|
@autoreleasepool {
|
2022-02-27 07:24:32 +00:00
|
|
|
std::lock_guard<std::mutex> lock(Cache_Lock);
|
|
|
|
for(auto it = Cache_List.begin(); it != Cache_List.end();) {
|
|
|
|
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - it->second.time_accessed);
|
|
|
|
if(elapsed.count() >= 10) {
|
|
|
|
it = Cache_List.erase(it);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(Cache_List.size() == 0)
|
|
|
|
[Cache_Data_Store reset];
|
|
|
|
}
|
|
|
|
|
|
|
|
std::this_thread::sleep_for(dura);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
@implementation PluginController
|
|
|
|
|
2008-03-01 15:28:17 +00:00
|
|
|
@synthesize sources;
|
|
|
|
@synthesize containers;
|
|
|
|
@synthesize metadataReaders;
|
|
|
|
|
|
|
|
@synthesize propertiesReadersByExtension;
|
|
|
|
@synthesize propertiesReadersByMimeType;
|
|
|
|
|
|
|
|
@synthesize decodersByExtension;
|
|
|
|
@synthesize decodersByMimeType;
|
|
|
|
|
|
|
|
@synthesize configured;
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
static PluginController *sharedPluginController = nil;
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
+ (id<CogPluginController>)sharedPluginController {
|
2007-02-24 20:36:27 +00:00
|
|
|
@synchronized(self) {
|
2022-02-07 05:49:27 +00:00
|
|
|
if(sharedPluginController == nil) {
|
2008-03-03 02:18:27 +00:00
|
|
|
sharedPluginController = [[self alloc] init];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
return sharedPluginController;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (id)init {
|
|
|
|
self = [super init];
|
2022-02-07 05:49:27 +00:00
|
|
|
if(self) {
|
|
|
|
self.sources = [[NSMutableDictionary alloc] init];
|
|
|
|
self.containers = [[NSMutableDictionary alloc] init];
|
|
|
|
|
|
|
|
self.metadataReaders = [[NSMutableDictionary alloc] init];
|
|
|
|
|
|
|
|
self.propertiesReadersByExtension = [[NSMutableDictionary alloc] init];
|
|
|
|
self.propertiesReadersByMimeType = [[NSMutableDictionary alloc] init];
|
|
|
|
|
|
|
|
self.decodersByExtension = [[NSMutableDictionary alloc] init];
|
|
|
|
self.decodersByMimeType = [[NSMutableDictionary alloc] init];
|
|
|
|
|
|
|
|
[self setup];
|
2022-02-27 07:24:32 +00:00
|
|
|
|
|
|
|
cache_init();
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
- (void)dealloc {
|
|
|
|
cache_deinit();
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setup {
|
|
|
|
if(self.configured == NO) {
|
2008-03-01 15:32:28 +00:00
|
|
|
self.configured = YES;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-10-20 15:46:39 +00:00
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bundleDidLoad:) name:NSBundleDidLoadNotification object:nil];
|
|
|
|
|
|
|
|
[self loadPlugins];
|
|
|
|
[self printPluginInfo];
|
2022-02-07 05:49:27 +00:00
|
|
|
}
|
2007-10-20 15:46:39 +00:00
|
|
|
}
|
2007-02-24 20:36:27 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)bundleDidLoad:(NSNotification *)notification {
|
2007-10-20 15:46:39 +00:00
|
|
|
NSArray *classNames = [[notification userInfo] objectForKey:@"NSLoadedClasses"];
|
2022-02-07 05:49:27 +00:00
|
|
|
for(NSString *className in classNames) {
|
|
|
|
Class bundleClass = NSClassFromString(className);
|
|
|
|
if([bundleClass conformsToProtocol:@protocol(CogVersionCheck)]) {
|
|
|
|
DLog(@"Component has version check: %@", className);
|
|
|
|
if(![bundleClass shouldLoadForOSVersion:[[NSProcessInfo processInfo] operatingSystemVersion]]) {
|
|
|
|
DLog(@"Plugin fails OS version check, ignoring");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(NSString *className in classNames) {
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Class loaded: %@", className);
|
2007-10-20 15:46:39 +00:00
|
|
|
Class bundleClass = NSClassFromString(className);
|
2022-02-07 05:49:27 +00:00
|
|
|
if([bundleClass conformsToProtocol:@protocol(CogContainer)]) {
|
2007-10-20 15:46:39 +00:00
|
|
|
[self setupContainer:className];
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
if([bundleClass conformsToProtocol:@protocol(CogDecoder)]) {
|
2007-10-20 15:46:39 +00:00
|
|
|
[self setupDecoder:className];
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
if([bundleClass conformsToProtocol:@protocol(CogMetadataReader)]) {
|
2007-10-20 15:46:39 +00:00
|
|
|
[self setupMetadataReader:className];
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
if([bundleClass conformsToProtocol:@protocol(CogPropertiesReader)]) {
|
2007-10-20 15:46:39 +00:00
|
|
|
[self setupPropertiesReader:className];
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
if([bundleClass conformsToProtocol:@protocol(CogSource)]) {
|
2007-10-20 15:46:39 +00:00
|
|
|
[self setupSource:className];
|
|
|
|
}
|
|
|
|
}
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)loadPluginsAtPath:(NSString *)path {
|
2013-10-03 08:00:58 +00:00
|
|
|
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
|
2007-02-24 20:36:27 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
for(NSString *pname in dirContents) {
|
2007-02-24 20:36:27 +00:00
|
|
|
NSString *ppath;
|
2022-02-07 05:49:27 +00:00
|
|
|
ppath = [NSString pathWithComponents:@[path, pname]];
|
|
|
|
|
|
|
|
if([[pname pathExtension] isEqualToString:@"bundle"]) {
|
2007-02-24 20:36:27 +00:00
|
|
|
NSBundle *b = [NSBundle bundleWithPath:ppath];
|
2007-10-20 15:46:39 +00:00
|
|
|
[b load];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)loadPlugins {
|
|
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
|
|
|
|
NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
|
2021-12-24 09:01:21 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
[self loadPluginsAtPath:[[NSBundle mainBundle] builtInPlugInsPath]];
|
2021-12-24 09:01:21 +00:00
|
|
|
[self loadPluginsAtPath:[basePath stringByAppendingPathComponent:@"Plugins"]];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setupContainer:(NSString *)className {
|
2007-10-09 01:20:46 +00:00
|
|
|
Class container = NSClassFromString(className);
|
2022-02-07 05:49:27 +00:00
|
|
|
if(container && [container respondsToSelector:@selector(fileTypes)]) {
|
|
|
|
for(id fileType in [container fileTypes]) {
|
|
|
|
NSString *ext = [fileType lowercaseString];
|
|
|
|
NSMutableArray *containerSet;
|
|
|
|
if(![containers objectForKey:ext]) {
|
|
|
|
containerSet = [[NSMutableArray alloc] init];
|
|
|
|
[containers setObject:containerSet forKey:ext];
|
|
|
|
} else
|
|
|
|
containerSet = [containers objectForKey:ext];
|
|
|
|
[containerSet addObject:className];
|
2007-10-09 01:20:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setupDecoder:(NSString *)className {
|
2007-03-02 01:36:52 +00:00
|
|
|
Class decoder = NSClassFromString(className);
|
2022-02-07 05:49:27 +00:00
|
|
|
if(decoder && [decoder respondsToSelector:@selector(fileTypes)]) {
|
|
|
|
for(id fileType in [decoder fileTypes]) {
|
|
|
|
NSString *ext = [fileType lowercaseString];
|
|
|
|
NSMutableArray *decoders;
|
|
|
|
if(![decodersByExtension objectForKey:ext]) {
|
|
|
|
decoders = [[NSMutableArray alloc] init];
|
|
|
|
[decodersByExtension setObject:decoders forKey:ext];
|
|
|
|
} else
|
|
|
|
decoders = [decodersByExtension objectForKey:ext];
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 17:54:11 +00:00
|
|
|
[decoders addObject:className];
|
2007-10-14 18:12:15 +00:00
|
|
|
}
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
|
|
|
if(decoder && [decoder respondsToSelector:@selector(mimeTypes)]) {
|
|
|
|
for(id mimeType in [decoder mimeTypes]) {
|
|
|
|
NSString *mimetype = [mimeType lowercaseString];
|
|
|
|
NSMutableArray *decoders;
|
|
|
|
if(![decodersByMimeType objectForKey:mimetype]) {
|
|
|
|
decoders = [[NSMutableArray alloc] init];
|
|
|
|
[decodersByMimeType setObject:decoders forKey:mimetype];
|
|
|
|
} else
|
|
|
|
decoders = [decodersByMimeType objectForKey:mimetype];
|
|
|
|
[decoders addObject:className];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
2007-03-02 01:36:52 +00:00
|
|
|
}
|
|
|
|
}
|
2007-02-24 20:36:27 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setupMetadataReader:(NSString *)className {
|
2007-03-02 01:36:52 +00:00
|
|
|
Class metadataReader = NSClassFromString(className);
|
2022-02-07 05:49:27 +00:00
|
|
|
if(metadataReader && [metadataReader respondsToSelector:@selector(fileTypes)]) {
|
|
|
|
for(id fileType in [metadataReader fileTypes]) {
|
|
|
|
NSString *ext = [fileType lowercaseString];
|
|
|
|
NSMutableArray *readers;
|
|
|
|
if(![metadataReaders objectForKey:ext]) {
|
|
|
|
readers = [[NSMutableArray alloc] init];
|
|
|
|
[metadataReaders setObject:readers forKey:ext];
|
|
|
|
} else
|
|
|
|
readers = [metadataReaders objectForKey:ext];
|
|
|
|
[readers addObject:className];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
2007-03-02 01:36:52 +00:00
|
|
|
}
|
|
|
|
}
|
2007-02-24 20:36:27 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setupPropertiesReader:(NSString *)className {
|
2007-03-02 01:36:52 +00:00
|
|
|
Class propertiesReader = NSClassFromString(className);
|
2022-02-07 05:49:27 +00:00
|
|
|
if(propertiesReader && [propertiesReader respondsToSelector:@selector(fileTypes)]) {
|
|
|
|
for(id fileType in [propertiesReader fileTypes]) {
|
|
|
|
NSString *ext = [fileType lowercaseString];
|
|
|
|
NSMutableArray *readers;
|
|
|
|
if(![propertiesReadersByExtension objectForKey:ext]) {
|
|
|
|
readers = [[NSMutableArray alloc] init];
|
|
|
|
[propertiesReadersByExtension setObject:readers forKey:ext];
|
|
|
|
} else
|
|
|
|
readers = [propertiesReadersByExtension objectForKey:ext];
|
|
|
|
[readers addObject:className];
|
2007-10-14 18:12:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
if(propertiesReader && [propertiesReader respondsToSelector:@selector(mimeTypes)]) {
|
|
|
|
for(id mimeType in [propertiesReader mimeTypes]) {
|
|
|
|
NSString *mimetype = [mimeType lowercaseString];
|
|
|
|
NSMutableArray *readers;
|
|
|
|
if(![propertiesReadersByMimeType objectForKey:mimetype]) {
|
|
|
|
readers = [[NSMutableArray alloc] init];
|
|
|
|
[propertiesReadersByMimeType setObject:readers forKey:mimetype];
|
|
|
|
} else
|
|
|
|
readers = [propertiesReadersByMimeType objectForKey:mimetype];
|
|
|
|
[readers addObject:className];
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setupSource:(NSString *)className {
|
2007-03-02 01:36:52 +00:00
|
|
|
Class source = NSClassFromString(className);
|
2022-02-07 05:49:27 +00:00
|
|
|
if(source && [source respondsToSelector:@selector(schemes)]) {
|
|
|
|
for(id scheme in [source schemes]) {
|
2007-03-02 01:36:52 +00:00
|
|
|
[sources setObject:className forKey:scheme];
|
|
|
|
}
|
|
|
|
}
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
2022-06-22 01:59:57 +00:00
|
|
|
static NSString *xmlEscapeString(NSString * string) {
|
|
|
|
CFStringRef textXML = CFXMLCreateStringByEscapingEntities(kCFAllocatorDefault, (CFStringRef)string, nil);
|
|
|
|
if(textXML) {
|
|
|
|
NSString *textString = (__bridge NSString *)textXML;
|
|
|
|
CFRelease(textXML);
|
|
|
|
return textString;
|
|
|
|
}
|
|
|
|
return @"";
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)printPluginInfo {
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Sources: %@", self.sources);
|
|
|
|
ALog(@"Containers: %@", self.containers);
|
|
|
|
ALog(@"Metadata Readers: %@", self.metadataReaders);
|
2007-10-14 18:12:15 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Properties Readers By Extension: %@", self.propertiesReadersByExtension);
|
|
|
|
ALog(@"Properties Readers By Mime Type: %@", self.propertiesReadersByMimeType);
|
2007-10-14 18:12:15 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Decoders by Extension: %@", self.decodersByExtension);
|
|
|
|
ALog(@"Decoders by Mime Type: %@", self.decodersByMimeType);
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-01-18 11:06:03 +00:00
|
|
|
#if 0
|
|
|
|
// XXX Keep in sync with Info.plist on disk!
|
|
|
|
NSString * plistHeader = @"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
|
|
|
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
|
|
|
<plist version=\"1.0\">\n\
|
|
|
|
<dict>\n\
|
2022-06-22 02:01:06 +00:00
|
|
|
\t<key>FirebaseCrashlyticsCollectionEnabled</key>\n\
|
|
|
|
\t<false/>\n\
|
2022-06-21 00:13:54 +00:00
|
|
|
\t<key>SUEnableInstallerLauncherService</key>\n\
|
|
|
|
\t<true/>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t<key>CFBundleDevelopmentRegion</key>\n\
|
2022-06-21 00:13:54 +00:00
|
|
|
\t<string>en_US</string>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t<key>CFBundleDocumentTypes</key>\n\
|
|
|
|
\t<array>\n\
|
|
|
|
\t\t<dict>\n\
|
|
|
|
\t\t\t<key>CFBundleTypeExtensions</key>\n\
|
|
|
|
\t\t\t<array>\n\
|
|
|
|
\t\t\t\t<string>*</string>\n\
|
|
|
|
\t\t\t</array>\n\
|
|
|
|
\t\t\t<key>CFBundleTypeIconFile</key>\n\
|
|
|
|
\t\t\t<string>song.icns</string>\n\
|
2022-04-21 21:21:37 +00:00
|
|
|
\t\t\t<key>CFBundleTypeIconSystemGenerated</key>\n\
|
|
|
|
\t\t\t<integer>1</integer>\n\
|
2022-06-21 14:08:05 +00:00
|
|
|
\t\t\t<key>CFBundleTypeName</key>\n\
|
|
|
|
\t\t\t<string>Folder</string>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t\t<key>CFBundleTypeOSTypes</key>\n\
|
|
|
|
\t\t\t<array>\n\
|
|
|
|
\t\t\t\t<string>****</string>\n\
|
|
|
|
\t\t\t\t<string>fold</string>\n\
|
|
|
|
\t\t\t\t<string>disk</string>\n\
|
|
|
|
\t\t\t</array>\n\
|
|
|
|
\t\t\t<key>CFBundleTypeRole</key>\n\
|
|
|
|
\t\t\t<string>None</string>\n\
|
2022-04-21 21:21:37 +00:00
|
|
|
\t\t\t<key>LSHandlerRank</key>\n\
|
|
|
|
\t\t\t<string>Default</string>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t</dict>\n";
|
|
|
|
NSString * plistFooter = @"\t</array>\n\
|
|
|
|
\t<key>CFBundleExecutable</key>\n\
|
|
|
|
\t<string>Cog</string>\n\
|
|
|
|
\t<key>CFBundleHelpBookFolder</key>\n\
|
|
|
|
\t<string>Cog.help</string>\n\
|
|
|
|
\t<key>CFBundleHelpBookName</key>\n\
|
|
|
|
\t<string>org.cogx.cog.help</string>\n\
|
|
|
|
\t<key>CFBundleIdentifier</key>\n\
|
|
|
|
\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\
|
|
|
|
\t<key>CFBundleInfoDictionaryVersion</key>\n\
|
|
|
|
\t<string>6.0</string>\n\
|
|
|
|
\t<key>CFBundlePackageType</key>\n\
|
|
|
|
\t<string>APPL</string>\n\
|
|
|
|
\t<key>CFBundleShortVersionString</key>\n\
|
|
|
|
\t<string>0.08</string>\n\
|
|
|
|
\t<key>CFBundleSignature</key>\n\
|
|
|
|
\t<string>????</string>\n\
|
|
|
|
\t<key>CFBundleVersion</key>\n\
|
|
|
|
\t<string>r516</string>\n\
|
|
|
|
\t<key>LSApplicationCategoryType</key>\n\
|
|
|
|
\t<string>public.app-category.music</string>\n\
|
|
|
|
\t<key>LSMinimumSystemVersion</key>\n\
|
|
|
|
\t<string>$(MACOSX_DEPLOYMENT_TARGET)</string>\n\
|
|
|
|
\t<key>NSAppTransportSecurity</key>\n\
|
|
|
|
\t<dict>\n\
|
|
|
|
\t\t<key>NSAllowsArbitraryLoads</key>\n\
|
|
|
|
\t\t<true/>\n\
|
|
|
|
\t</dict>\n\
|
|
|
|
\t<key>NSAppleScriptEnabled</key>\n\
|
|
|
|
\t<string>YES</string>\n\
|
|
|
|
\t<key>NSCalendarsUsageDescription</key>\n\
|
|
|
|
\t<string>Cog has no use for your calendar information. Why are you trying to open your Calendar with an audio player?</string>\n\
|
|
|
|
\t<key>NSCameraUsageDescription</key>\n\
|
|
|
|
\t<string>Cog is an audio player. It will never use your camera. Why is it asking for permission to use your camera?</string>\n\
|
|
|
|
\t<key>NSContactsUsageDescription</key>\n\
|
|
|
|
\t<string>Cog has no use for your contacts information. Why are you trying to open your contacts with an audio player?</string>\n\
|
|
|
|
\t<key>NSLocationUsageDescription</key>\n\
|
|
|
|
\t<string>Cog has no use for your location information. Something is obviously wrong with the application.</string>\n\
|
|
|
|
\t<key>NSMainNibFile</key>\n\
|
|
|
|
\t<string>MainMenu</string>\n\
|
|
|
|
\t<key>NSMicrophoneUsageDescription</key>\n\
|
|
|
|
\t<string>Cog is an audio player. It does not, however, record audio. Why is it asking for permission to use your microphone?</string>\n\
|
|
|
|
\t<key>NSPhotoLibraryUsageDescription</key>\n\
|
|
|
|
\t<string>Cog is an audio player. Why are you trying to access your Photos Library with an audio player?</string>\n\
|
|
|
|
\t<key>NSPrincipalClass</key>\n\
|
|
|
|
\t<string>MediaKeysApplication</string>\n\
|
|
|
|
\t<key>NSRemindersUsageDescription</key>\n\
|
|
|
|
\t<string>Cog has no use for your reminders. Why are you trying to access them with an audio player?</string>\n\
|
2022-04-21 21:21:37 +00:00
|
|
|
\t<key>OSAScriptingDefinition</key>\n\
|
|
|
|
\t<string>Cog.sdef</string>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t<key>SUFeedURL</key>\n\
|
|
|
|
\t<string>https://cogcdn.cog.losno.co/mercury.xml</string>\n\
|
|
|
|
\t<key>SUPublicEDKey</key>\n\
|
|
|
|
\t<string>omxG7Rp0XK9/YEvKbVy7cd44eVAh1LJB6CmjQwjOJz4=</string>\n\
|
|
|
|
</dict>\n\
|
|
|
|
</plist>\n";
|
|
|
|
NSMutableArray * decodersRegistered = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
NSArray * allKeys = [self.decodersByExtension allKeys];
|
|
|
|
for (NSString * ext in allKeys) {
|
|
|
|
NSArray * decoders = [self.decodersByExtension objectForKey:ext];
|
|
|
|
for (NSString * decoder in decoders) {
|
|
|
|
if (![decodersRegistered containsObject:decoder]) {
|
|
|
|
[decodersRegistered addObject:decoder];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-01-19 00:57:35 +00:00
|
|
|
|
2022-01-18 11:06:03 +00:00
|
|
|
NSMutableArray * stringList = [[NSMutableArray alloc] init];
|
2022-01-19 00:57:35 +00:00
|
|
|
|
2022-01-18 11:06:03 +00:00
|
|
|
[stringList addObject:plistHeader];
|
2022-01-19 00:57:35 +00:00
|
|
|
|
|
|
|
// These aren't handled by decoders, but as containers
|
|
|
|
NSArray * staticTypes = @[
|
|
|
|
@[@"M3U Playlist File", @"m3u.icns", @"m3u", @"m3u8"],
|
|
|
|
@[@"PLS Playlist File", @"pls.icns", @"pls"],
|
|
|
|
@[@"RAR Archive of SPC Files", @"vg.icns", @"rsn"],
|
|
|
|
@[@"7Z Archive of VGM Files", @"vg.icns", @"vgm7z"]
|
|
|
|
];
|
|
|
|
|
|
|
|
NSMutableArray * assocTypes = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
[assocTypes addObjectsFromArray:staticTypes];
|
2022-01-18 11:06:03 +00:00
|
|
|
|
|
|
|
for (NSString * decoderString in decodersRegistered) {
|
|
|
|
Class decoder = NSClassFromString(decoderString);
|
|
|
|
if (decoder && [decoder respondsToSelector:@selector(fileTypeAssociations)]) {
|
|
|
|
NSArray * types = [decoder fileTypeAssociations];
|
2022-01-19 00:57:35 +00:00
|
|
|
[assocTypes addObjectsFromArray:types];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (NSArray * type in assocTypes) {
|
|
|
|
[stringList addObject:@"\t\t<dict>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t\t<key>CFBundleTypeExtensions</key>\n\
|
|
|
|
\t\t\t<array>\n\
|
|
|
|
"];
|
2022-01-19 00:57:35 +00:00
|
|
|
for (size_t i = 2; i < [type count]; ++i) {
|
|
|
|
[stringList addObject:@"\t\t\t\t<string>"];
|
|
|
|
[stringList addObject:[[type objectAtIndex:i] lowercaseString]];
|
|
|
|
[stringList addObject:@"</string>\n"];
|
|
|
|
}
|
|
|
|
[stringList addObject:@"\t\t\t</array>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t\t<key>CFBundleTypeIconFile</key>\n\
|
|
|
|
\t\t\t<string>"];
|
2022-01-19 00:57:35 +00:00
|
|
|
[stringList addObject:[type objectAtIndex:1]];
|
|
|
|
[stringList addObject:@"</string>\n\
|
2022-04-21 21:21:37 +00:00
|
|
|
\t\t\t<key>CFBundleTypeIconSystemGenerated</key>\n\
|
|
|
|
\t\t\t<integer>1</integer>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t\t<key>CFBundleTypeName</key>\n\
|
|
|
|
\t\t\t<string>"];
|
2022-06-22 01:59:57 +00:00
|
|
|
[stringList addObject:xmlEscapeString([type objectAtIndex:0])];
|
2022-01-19 00:57:35 +00:00
|
|
|
[stringList addObject:@"</string>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t\t<key>CFBundleTypeRole</key>\n\
|
|
|
|
\t\t\t<string>Viewer</string>\n\
|
2022-04-21 21:21:37 +00:00
|
|
|
\t\t\t<key>LSHandlerRank</key>\n\
|
|
|
|
\t\t\t<string>Default</string>\n\
|
2022-01-18 11:06:03 +00:00
|
|
|
\t\t\t<key>LSTypeIsPackage</key>\n\
|
|
|
|
\t\t\t<false/>\n\
|
|
|
|
\t\t</dict>\n"];
|
|
|
|
}
|
|
|
|
|
|
|
|
[stringList addObject:plistFooter];
|
|
|
|
|
2022-06-21 14:07:42 +00:00
|
|
|
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:[NSTemporaryDirectory() stringByAppendingPathComponent:@"Cog_Info.plist"] createFile:YES];
|
2022-01-18 11:06:03 +00:00
|
|
|
if (!fileHandle) {
|
|
|
|
DLog(@"Error saving Info.plist!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
[fileHandle truncateFileAtOffset:0];
|
|
|
|
[fileHandle writeData:[[stringList componentsJoinedByString:@""] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
[fileHandle closeFile];
|
|
|
|
#endif
|
2007-02-24 20:36:27 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (id<CogSource>)audioSourceForURL:(NSURL *)url {
|
2007-10-09 01:20:46 +00:00
|
|
|
NSString *scheme = [url scheme];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-10-09 01:20:46 +00:00
|
|
|
Class source = NSClassFromString([sources objectForKey:scheme]);
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
return [[source alloc] init];
|
2007-10-09 01:20:46 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (NSArray *)urlsForContainerURL:(NSURL *)url {
|
2016-06-19 19:57:18 +00:00
|
|
|
NSString *ext = [url pathExtension];
|
2022-02-07 05:49:27 +00:00
|
|
|
NSArray *containerSet = [containers objectForKey:[ext lowercaseString]];
|
|
|
|
NSString *classString;
|
|
|
|
if(containerSet) {
|
|
|
|
if([containerSet count] > 1) {
|
|
|
|
return [CogContainerMulti urlsForContainerURL:url containers:containerSet];
|
|
|
|
} else {
|
|
|
|
classString = [containerSet objectAtIndex:0];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2015-04-13 07:39:24 +00:00
|
|
|
Class container = NSClassFromString(classString);
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-10-09 01:20:46 +00:00
|
|
|
return [container urlsForContainerURL:url];
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
// Note: Source is assumed to already be opened.
|
|
|
|
- (id<CogDecoder>)audioDecoderForSource:(id<CogSource>)source skipCue:(BOOL)skip {
|
2016-06-19 19:57:18 +00:00
|
|
|
NSString *ext = [[source url] pathExtension];
|
Implemented support for multiple decoders per file name extension, with a floating point priority control per interface. In the event that more than one input is registered to a given extension, and we match that extension, it will be passed off to an instance of the multi-decoder wrapper, which will try opening the file with all of the decoders in order of priority, until either one of them accepts it, or all of them have failed. This paves the way for adding a VGMSTREAM input, so I can give it a very low priority, since it has several formats that are verified by file name extension only. All current inputs have been given a priority of 1.0, except for CoreAudio, which was given a priority of 0.5, because it contains an MP3 and AC3 decoders that I'd rather not use if I don't have to.
2013-10-21 17:54:11 +00:00
|
|
|
NSArray *decoders = [decodersByExtension objectForKey:[ext lowercaseString]];
|
2022-02-07 05:49:27 +00:00
|
|
|
NSString *classString;
|
|
|
|
if(decoders) {
|
|
|
|
if([decoders count] > 1) {
|
|
|
|
if(skip) {
|
|
|
|
NSMutableArray *_decoders = [decoders mutableCopy];
|
|
|
|
for(int i = 0; i < [_decoders count];) {
|
|
|
|
if([[_decoders objectAtIndex:i] isEqualToString:@"CueSheetDecoder"])
|
|
|
|
[_decoders removeObjectAtIndex:i];
|
|
|
|
else
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
return [[CogDecoderMulti alloc] initWithDecoders:_decoders];
|
|
|
|
}
|
|
|
|
return [[CogDecoderMulti alloc] initWithDecoders:decoders];
|
|
|
|
} else {
|
|
|
|
classString = [decoders objectAtIndex:0];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
decoders = [decodersByMimeType objectForKey:[[source mimeType] lowercaseString]];
|
|
|
|
if(decoders) {
|
|
|
|
if([decoders count] > 1) {
|
|
|
|
return [[CogDecoderMulti alloc] initWithDecoders:decoders];
|
|
|
|
} else {
|
|
|
|
classString = [decoders objectAtIndex:0];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
classString = @"SilenceDecoder";
|
|
|
|
}
|
2007-10-14 18:12:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Class decoder = NSClassFromString(classString);
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2016-05-05 20:05:39 +00:00
|
|
|
return [[decoder alloc] init];
|
2007-10-09 01:20:46 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (NSDictionary *)metadataForURL:(NSURL *)url skipCue:(BOOL)skip {
|
|
|
|
NSString *urlScheme = [url scheme];
|
|
|
|
if([urlScheme isEqualToString:@"http"] ||
|
|
|
|
[urlScheme isEqualToString:@"https"])
|
|
|
|
return nil;
|
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
NSDictionary *cacheData = cache_access_metadata(url);
|
|
|
|
if(cacheData) return cacheData;
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
NSString *ext = [url pathExtension];
|
2022-02-07 05:49:27 +00:00
|
|
|
NSArray *readers = [metadataReaders objectForKey:[ext lowercaseString]];
|
|
|
|
NSString *classString;
|
|
|
|
if(readers) {
|
|
|
|
if([readers count] > 1) {
|
|
|
|
if(skip) {
|
|
|
|
NSMutableArray *_readers = [readers mutableCopy];
|
|
|
|
for(int i = 0; i < [_readers count];) {
|
|
|
|
if([[_readers objectAtIndex:i] isEqualToString:@"CueSheetMetadataReader"])
|
|
|
|
[_readers removeObjectAtIndex:i];
|
|
|
|
else
|
|
|
|
++i;
|
|
|
|
}
|
2022-02-27 07:24:32 +00:00
|
|
|
cacheData = [CogMetadataReaderMulti metadataForURL:url readers:_readers];
|
|
|
|
cache_insert_metadata(url, cacheData);
|
|
|
|
return cacheData;
|
2022-02-07 05:49:27 +00:00
|
|
|
}
|
2022-02-27 07:24:32 +00:00
|
|
|
cacheData = [CogMetadataReaderMulti metadataForURL:url readers:readers];
|
|
|
|
cache_insert_metadata(url, cacheData);
|
|
|
|
return cacheData;
|
2022-02-07 05:49:27 +00:00
|
|
|
} else {
|
|
|
|
classString = [readers objectAtIndex:0];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
2015-04-13 07:39:24 +00:00
|
|
|
Class metadataReader = NSClassFromString(classString);
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
cacheData = [metadataReader metadataForURL:url];
|
|
|
|
cache_insert_metadata(url, cacheData);
|
|
|
|
return cacheData;
|
2007-10-09 01:20:46 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
// If no properties reader is defined, use the decoder's properties.
|
2022-02-13 20:18:58 +00:00
|
|
|
- (NSDictionary *)propertiesForURL:(NSURL *)url skipCue:(BOOL)skip {
|
2022-02-07 05:49:27 +00:00
|
|
|
NSString *urlScheme = [url scheme];
|
|
|
|
if([urlScheme isEqualToString:@"http"] ||
|
|
|
|
[urlScheme isEqualToString:@"https"])
|
|
|
|
return nil;
|
2007-10-14 17:31:20 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
NSDictionary *properties = nil;
|
2022-02-27 07:24:32 +00:00
|
|
|
|
|
|
|
properties = cache_access_properties(url);
|
|
|
|
if(properties) return properties;
|
|
|
|
|
2016-06-19 19:57:18 +00:00
|
|
|
NSString *ext = [url pathExtension];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-10-09 01:20:46 +00:00
|
|
|
id<CogSource> source = [self audioSourceForURL:url];
|
2022-02-07 05:49:27 +00:00
|
|
|
if(![source open:url])
|
2007-10-09 01:20:46 +00:00
|
|
|
return nil;
|
2007-10-14 18:12:15 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
NSArray *readers = [propertiesReadersByExtension objectForKey:[ext lowercaseString]];
|
|
|
|
NSString *classString = nil;
|
|
|
|
if(readers) {
|
|
|
|
if([readers count] > 1) {
|
|
|
|
properties = [CogPropertiesReaderMulti propertiesForSource:source readers:readers];
|
2022-02-27 07:24:32 +00:00
|
|
|
if(properties != nil && [properties count]) {
|
|
|
|
cache_insert_properties(url, properties);
|
2022-02-07 05:49:27 +00:00
|
|
|
return properties;
|
2022-02-27 07:24:32 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
} else {
|
|
|
|
classString = [readers objectAtIndex:0];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
readers = [propertiesReadersByMimeType objectForKey:[[source mimeType] lowercaseString]];
|
|
|
|
if(readers) {
|
|
|
|
if([readers count] > 1) {
|
|
|
|
properties = [CogPropertiesReaderMulti propertiesForSource:source readers:readers];
|
2022-02-27 07:24:32 +00:00
|
|
|
if(properties != nil && [properties count]) {
|
|
|
|
cache_insert_properties(url, properties);
|
2022-02-07 05:49:27 +00:00
|
|
|
return properties;
|
2022-02-27 07:24:32 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
} else {
|
|
|
|
classString = [readers objectAtIndex:0];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(classString) {
|
2007-10-14 17:31:20 +00:00
|
|
|
Class propertiesReader = NSClassFromString(classString);
|
2007-03-02 01:36:52 +00:00
|
|
|
|
2020-02-16 09:00:39 +00:00
|
|
|
properties = [propertiesReader propertiesForSource:source];
|
2022-02-27 07:24:32 +00:00
|
|
|
if(properties != nil && [properties count]) {
|
|
|
|
cache_insert_properties(url, properties);
|
2022-02-07 05:49:27 +00:00
|
|
|
return properties;
|
2022-02-27 07:24:32 +00:00
|
|
|
}
|
2007-10-14 17:31:20 +00:00
|
|
|
}
|
2020-02-16 09:00:39 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
{
|
2022-02-13 20:18:58 +00:00
|
|
|
id<CogDecoder> decoder = [self audioDecoderForSource:source skipCue:skip];
|
2022-02-07 05:49:27 +00:00
|
|
|
if(![decoder open:source]) {
|
2007-10-14 17:31:20 +00:00
|
|
|
return nil;
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-10-14 17:31:20 +00:00
|
|
|
NSDictionary *properties = [decoder properties];
|
2022-02-10 23:29:13 +00:00
|
|
|
NSDictionary *metadata = [decoder metadata];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2007-10-14 17:31:20 +00:00
|
|
|
[decoder close];
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-02-27 07:24:32 +00:00
|
|
|
NSDictionary *cacheData = [NSDictionary dictionaryByMerging:properties with:metadata];
|
|
|
|
cache_insert_properties(url, cacheData);
|
|
|
|
return cacheData;
|
2007-10-14 17:31:20 +00:00
|
|
|
}
|
2007-10-09 01:20:46 +00:00
|
|
|
}
|
2007-03-02 01:36:52 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (int)putMetadataInURL:(NSURL *)url {
|
|
|
|
return 0;
|
2013-10-03 08:00:58 +00:00
|
|
|
}
|
|
|
|
|
2007-02-24 20:36:27 +00:00
|
|
|
@end
|