2007-03-07 01:26:50 +00:00
|
|
|
//
|
|
|
|
// PlaylistLoader.m
|
|
|
|
// Cog
|
|
|
|
//
|
|
|
|
// Created by Vincent Spader on 3/05/07.
|
|
|
|
// Copyright 2007 Vincent Spader All rights reserved.
|
|
|
|
//
|
|
|
|
|
2013-10-09 15:45:16 +00:00
|
|
|
#include <objc/runtime.h>
|
|
|
|
|
2016-09-02 00:20:53 +00:00
|
|
|
#include <mach/semaphore.h>
|
|
|
|
|
2007-03-07 01:26:50 +00:00
|
|
|
#import "PlaylistLoader.h"
|
|
|
|
#import "PlaylistController.h"
|
|
|
|
#import "PlaylistEntry.h"
|
2008-03-01 18:29:14 +00:00
|
|
|
#import "AppController.h"
|
2007-03-07 01:26:50 +00:00
|
|
|
|
2007-03-14 02:28:30 +00:00
|
|
|
#import "NSFileHandle+CreateFile.h"
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
#import "CogAudio/AudioPlayer.h"
|
2007-10-09 01:20:46 +00:00
|
|
|
#import "CogAudio/AudioContainer.h"
|
2008-03-01 18:29:14 +00:00
|
|
|
#import "CogAudio/AudioPropertiesReader.h"
|
|
|
|
#import "CogAudio/AudioMetadataReader.h"
|
2007-03-09 01:16:06 +00:00
|
|
|
|
2018-06-28 10:59:59 +00:00
|
|
|
#import "XmlContainer.h"
|
2013-10-09 15:45:16 +00:00
|
|
|
|
2013-10-09 23:14:23 +00:00
|
|
|
#import "NSData+MD5.h"
|
|
|
|
|
2018-06-28 10:59:59 +00:00
|
|
|
#import "NSString+FinderCompare.h"
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2007-03-07 01:26:50 +00:00
|
|
|
@implementation PlaylistLoader
|
|
|
|
|
2008-05-03 16:01:27 +00:00
|
|
|
- (id)init
|
|
|
|
{
|
|
|
|
self = [super init];
|
|
|
|
if (self)
|
|
|
|
{
|
2009-08-16 16:49:34 +00:00
|
|
|
[self initDefaults];
|
|
|
|
|
2009-03-06 04:37:44 +00:00
|
|
|
queue = [[NSOperationQueue alloc] init];
|
2021-05-08 00:19:10 +00:00
|
|
|
[queue setMaxConcurrentOperationCount:8];
|
2008-05-03 16:01:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2009-08-16 16:49:34 +00:00
|
|
|
- (void)initDefaults
|
|
|
|
{
|
|
|
|
NSDictionary *defaultsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
|
|
[NSNumber numberWithBool:YES], @"readCueSheetsInFolders",
|
|
|
|
nil];
|
|
|
|
|
|
|
|
[[NSUserDefaults standardUserDefaults] registerDefaults:defaultsDictionary];
|
|
|
|
}
|
|
|
|
|
2007-03-07 01:26:50 +00:00
|
|
|
- (BOOL)save:(NSString *)filename
|
|
|
|
{
|
|
|
|
NSString *ext = [filename pathExtension];
|
|
|
|
if ([ext isEqualToString:@"pls"])
|
|
|
|
{
|
|
|
|
return [self save:filename asType:kPlaylistPls];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return [self save:filename asType:kPlaylistM3u];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)save:(NSString *)filename asType:(PlaylistType)type
|
|
|
|
{
|
|
|
|
if (type == kPlaylistM3u)
|
|
|
|
{
|
|
|
|
return [self saveM3u:filename];
|
|
|
|
}
|
|
|
|
else if (type == kPlaylistPls)
|
|
|
|
{
|
|
|
|
return [self savePls:filename];
|
|
|
|
}
|
2013-10-09 15:45:16 +00:00
|
|
|
else if (type == kPlaylistXml)
|
|
|
|
{
|
|
|
|
return [self saveXml:filename];
|
|
|
|
}
|
2007-03-07 01:26:50 +00:00
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
- (NSString *)relativePathFrom:(NSString *)filename toURL:(NSURL *)entryURL
|
2007-03-07 01:26:50 +00:00
|
|
|
{
|
|
|
|
NSString *basePath = [[[filename stringByStandardizingPath] stringByDeletingLastPathComponent] stringByAppendingString:@"/"];
|
|
|
|
|
2007-10-15 22:19:14 +00:00
|
|
|
if ([entryURL isFileURL]) {
|
2007-03-07 01:26:50 +00:00
|
|
|
//We want relative paths.
|
2016-05-05 20:05:39 +00:00
|
|
|
NSMutableString *entryPath = [[[entryURL path] stringByStandardizingPath] mutableCopy];
|
2007-03-07 01:26:50 +00:00
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
[entryPath replaceOccurrencesOfString:basePath withString:@"" options:(NSAnchoredSearch | NSLiteralSearch | NSCaseInsensitiveSearch) range:NSMakeRange(0, [entryPath length])];
|
2007-10-15 22:19:14 +00:00
|
|
|
if ([entryURL fragment])
|
|
|
|
{
|
|
|
|
[entryPath appendString:@"#"];
|
|
|
|
[entryPath appendString:[entryURL fragment]];
|
|
|
|
}
|
2007-03-07 01:26:50 +00:00
|
|
|
|
|
|
|
return entryPath;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
//Write [entryURL absoluteString] to file
|
|
|
|
return [entryURL absoluteString];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)saveM3u:(NSString *)filename
|
|
|
|
{
|
2007-03-14 02:28:30 +00:00
|
|
|
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filename createFile:YES];
|
2007-03-07 01:26:50 +00:00
|
|
|
if (!fileHandle) {
|
2013-10-11 12:03:55 +00:00
|
|
|
ALog(@"Error saving m3u!");
|
2008-02-13 01:50:39 +00:00
|
|
|
return NO;
|
2007-03-07 01:26:50 +00:00
|
|
|
}
|
2007-03-09 01:16:06 +00:00
|
|
|
[fileHandle truncateFileAtOffset:0];
|
|
|
|
|
2013-10-18 07:43:55 +00:00
|
|
|
for (PlaylistEntry *pe in [playlistController arrangedObjects])
|
2007-03-07 01:26:50 +00:00
|
|
|
{
|
2008-02-20 00:44:40 +00:00
|
|
|
NSString *path = [self relativePathFrom:filename toURL:[pe URL]];
|
2007-03-07 01:26:50 +00:00
|
|
|
[fileHandle writeData:[[path stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
[fileHandle closeFile];
|
|
|
|
|
2007-03-07 01:26:50 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)savePls:(NSString *)filename
|
|
|
|
{
|
2007-03-14 02:28:30 +00:00
|
|
|
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filename createFile:YES];
|
2007-03-07 01:26:50 +00:00
|
|
|
if (!fileHandle) {
|
|
|
|
return NO;
|
|
|
|
}
|
2007-03-09 01:16:06 +00:00
|
|
|
[fileHandle truncateFileAtOffset:0];
|
|
|
|
|
2013-10-03 08:00:58 +00:00
|
|
|
[fileHandle writeData:[[NSString stringWithFormat:@"[playlist]\nnumberOfEntries=%lu\n\n",(unsigned long)[[playlistController content] count]] dataUsingEncoding:NSUTF8StringEncoding]];
|
2007-03-07 01:26:50 +00:00
|
|
|
|
2007-03-07 01:45:45 +00:00
|
|
|
int i = 1;
|
2013-10-18 07:43:55 +00:00
|
|
|
for (PlaylistEntry *pe in [playlistController arrangedObjects])
|
2007-03-07 01:26:50 +00:00
|
|
|
{
|
2008-02-20 00:44:40 +00:00
|
|
|
NSString *path = [self relativePathFrom:filename toURL:[pe URL]];
|
2007-03-07 01:26:50 +00:00
|
|
|
NSString *entry = [NSString stringWithFormat:@"File%i=%@\n",i,path];
|
|
|
|
|
|
|
|
[fileHandle writeData:[entry dataUsingEncoding:NSUTF8StringEncoding]];
|
2007-03-07 01:45:45 +00:00
|
|
|
i++;
|
2007-03-07 01:26:50 +00:00
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
[fileHandle writeData:[@"\nVERSION=2" dataUsingEncoding:NSUTF8StringEncoding]];
|
|
|
|
[fileHandle closeFile];
|
|
|
|
|
2007-03-07 01:26:50 +00:00
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2013-10-09 15:45:16 +00:00
|
|
|
NSMutableDictionary * dictionaryWithPropertiesOfObject(id obj, NSArray * filterList)
|
|
|
|
{
|
|
|
|
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
2013-10-09 17:34:32 +00:00
|
|
|
|
|
|
|
Class class = [obj class];
|
2013-10-09 15:45:16 +00:00
|
|
|
|
2013-10-09 17:34:32 +00:00
|
|
|
do {
|
|
|
|
unsigned count;
|
|
|
|
objc_property_t *properties = class_copyPropertyList(class, &count);
|
2013-10-09 15:45:16 +00:00
|
|
|
|
2013-10-09 17:34:32 +00:00
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
|
|
|
|
if ([filterList containsObject:key]) continue;
|
|
|
|
|
|
|
|
Class classObject = NSClassFromString([key capitalizedString]);
|
|
|
|
if (classObject) {
|
|
|
|
id subObj = dictionaryWithPropertiesOfObject([obj valueForKey:key], filterList);
|
|
|
|
[dict setObject:subObj forKey:key];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
id value = [obj valueForKey:key];
|
|
|
|
if(value) [dict setObject:value forKey:key];
|
|
|
|
}
|
2013-10-09 15:45:16 +00:00
|
|
|
}
|
2013-10-09 17:34:32 +00:00
|
|
|
|
|
|
|
free(properties);
|
|
|
|
|
2014-09-17 01:27:14 +00:00
|
|
|
if (count) break;
|
|
|
|
|
2013-10-09 17:34:32 +00:00
|
|
|
class = [class superclass];
|
|
|
|
} while (class);
|
2013-10-09 15:45:16 +00:00
|
|
|
|
|
|
|
return dict;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)saveXml:(NSString *)filename
|
|
|
|
{
|
|
|
|
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filename createFile:YES];
|
|
|
|
if (!fileHandle) {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
[fileHandle truncateFileAtOffset:0];
|
|
|
|
|
2014-12-04 06:34:13 +00:00
|
|
|
NSArray * filterList = [NSArray arrayWithObjects:@"display", @"length", @"path", @"filename", @"status", @"statusMessage", @"spam", @"lengthText", @"positionText", @"stopAfter", @"shuffleIndex", @"index", @"current", @"queued", @"currentPosition", @"queuePosition", @"error", @"removed", @"URL", @"albumArt", nil];
|
2013-10-09 15:45:16 +00:00
|
|
|
|
2013-10-09 23:14:23 +00:00
|
|
|
NSMutableDictionary * albumArtSet = [[NSMutableDictionary alloc] init];
|
|
|
|
|
2013-10-09 15:45:16 +00:00
|
|
|
NSMutableArray * topLevel = [[NSMutableArray alloc] init];
|
|
|
|
|
2013-10-18 07:43:55 +00:00
|
|
|
for (PlaylistEntry *pe in [playlistController arrangedObjects])
|
2013-10-09 15:45:16 +00:00
|
|
|
{
|
2013-10-13 00:20:57 +00:00
|
|
|
BOOL error = [pe error];
|
|
|
|
|
2013-10-09 15:45:16 +00:00
|
|
|
NSMutableDictionary * dict = dictionaryWithPropertiesOfObject(pe, filterList);
|
|
|
|
|
|
|
|
NSString *path = [self relativePathFrom:filename toURL:[pe URL]];
|
|
|
|
|
|
|
|
[dict setObject:path forKey:@"URL"];
|
2013-10-09 20:53:13 +00:00
|
|
|
NSData * albumArt = [dict objectForKey:@"albumArtInternal"];
|
|
|
|
if (albumArt)
|
2013-10-09 17:34:32 +00:00
|
|
|
{
|
2013-10-09 20:53:13 +00:00
|
|
|
[dict removeObjectForKey:@"albumArtInternal"];
|
2013-10-09 23:14:23 +00:00
|
|
|
NSString * hash = [albumArt MD5];
|
|
|
|
if (![albumArtSet objectForKey:hash])
|
|
|
|
[albumArtSet setObject:albumArt forKey:hash];
|
|
|
|
[dict setObject:hash forKey:@"albumArt"];
|
2013-10-09 17:34:32 +00:00
|
|
|
}
|
|
|
|
|
2013-10-13 00:20:57 +00:00
|
|
|
if (error)
|
|
|
|
[dict removeObjectForKey:@"metadataLoaded"];
|
|
|
|
|
2013-10-09 15:45:16 +00:00
|
|
|
[topLevel addObject:dict];
|
|
|
|
}
|
|
|
|
|
2013-10-10 08:43:04 +00:00
|
|
|
NSMutableArray * queueList = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
for (PlaylistEntry *pe in [playlistController queueList])
|
|
|
|
{
|
2021-04-30 01:16:24 +00:00
|
|
|
[queueList addObject:[NSNumber numberWithInteger:pe.index]];
|
2013-10-10 08:43:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
NSDictionary * dictionary = [NSDictionary dictionaryWithObjectsAndKeys:albumArtSet, @"albumArt", queueList, @"queue", topLevel, @"items", nil];
|
2013-10-09 23:14:23 +00:00
|
|
|
|
2017-09-18 03:03:30 +00:00
|
|
|
NSError * err;
|
|
|
|
|
|
|
|
NSData * data = [NSPropertyListSerialization dataWithPropertyList:dictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:&err];
|
2013-10-09 23:14:23 +00:00
|
|
|
|
2013-10-09 15:45:16 +00:00
|
|
|
[fileHandle writeData:data];
|
|
|
|
|
|
|
|
[fileHandle closeFile];
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
- (NSArray *)fileURLsAtPath:(NSString *)path
|
|
|
|
{
|
|
|
|
NSFileManager *manager = [NSFileManager defaultManager];
|
|
|
|
|
|
|
|
NSMutableArray *urls = [NSMutableArray array];
|
|
|
|
|
|
|
|
NSArray *subpaths = [manager subpathsAtPath:path];
|
|
|
|
|
2008-03-01 15:04:46 +00:00
|
|
|
for (NSString *subpath in subpaths)
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
|
|
|
NSString *absoluteSubpath = [NSString pathWithComponents:[NSArray arrayWithObjects:path,subpath,nil]];
|
|
|
|
|
|
|
|
BOOL isDir;
|
|
|
|
if ( [manager fileExistsAtPath:absoluteSubpath isDirectory:&isDir] && isDir == NO)
|
|
|
|
{
|
2009-08-16 16:49:34 +00:00
|
|
|
if ([[absoluteSubpath pathExtension] caseInsensitiveCompare:@"cue"] != NSOrderedSame ||
|
|
|
|
[[NSUserDefaults standardUserDefaults] boolForKey:@"readCueSheetsInFolders"])
|
|
|
|
{
|
|
|
|
[urls addObject:[NSURL fileURLWithPath:absoluteSubpath]];
|
|
|
|
}
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-17 03:05:17 +00:00
|
|
|
|
|
|
|
NSSortDescriptor * sd_path = [[NSSortDescriptor alloc] initWithKey:@"path" ascending:YES];
|
|
|
|
[urls sortUsingDescriptors:[NSArray arrayWithObject:sd_path]];
|
2007-03-09 01:16:06 +00:00
|
|
|
|
|
|
|
return urls;
|
|
|
|
}
|
|
|
|
|
2021-04-30 01:16:24 +00:00
|
|
|
- (NSArray*)insertURLs:(NSArray *)urls atIndex:(NSInteger)index sort:(BOOL)sort
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
2007-05-28 14:58:08 +00:00
|
|
|
NSMutableSet *uniqueURLs = [NSMutableSet set];
|
|
|
|
|
|
|
|
NSMutableArray *expandedURLs = [NSMutableArray array];
|
2007-10-19 02:23:10 +00:00
|
|
|
NSMutableArray *containedURLs = [NSMutableArray array];
|
|
|
|
NSMutableArray *fileURLs = [NSMutableArray array];
|
2007-05-28 14:58:08 +00:00
|
|
|
NSMutableArray *validURLs = [NSMutableArray array];
|
2013-10-10 08:43:04 +00:00
|
|
|
NSDictionary *xmlData = nil;
|
2007-03-09 01:16:06 +00:00
|
|
|
|
|
|
|
if (!urls)
|
2008-05-09 21:24:49 +00:00
|
|
|
return [NSArray array];
|
2007-03-09 01:16:06 +00:00
|
|
|
|
|
|
|
if (index < 0)
|
|
|
|
index = 0;
|
2008-05-03 15:15:45 +00:00
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
NSURL *url;
|
2008-02-20 00:54:45 +00:00
|
|
|
for (url in urls)
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
|
|
|
if ([url isFileURL]) {
|
|
|
|
BOOL isDir;
|
2007-10-09 01:20:46 +00:00
|
|
|
if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir])
|
|
|
|
{
|
2007-03-09 01:16:06 +00:00
|
|
|
if (isDir == YES)
|
|
|
|
{
|
|
|
|
//Get subpaths
|
2007-05-28 14:58:08 +00:00
|
|
|
[expandedURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]];
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-04-06 23:50:17 +00:00
|
|
|
[expandedURLs addObject:[NSURL fileURLWithPath:[url path]]];
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//Non-file URL..
|
2007-05-28 14:58:08 +00:00
|
|
|
[expandedURLs addObject:url];
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
|
|
|
}
|
2007-10-19 02:23:10 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Expanded urls: %@", expandedURLs);
|
2007-03-09 01:16:06 +00:00
|
|
|
|
2007-05-28 14:58:08 +00:00
|
|
|
NSArray *sortedURLs;
|
|
|
|
if (sort == YES)
|
|
|
|
{
|
2009-08-16 16:49:34 +00:00
|
|
|
sortedURLs = [expandedURLs sortedArrayUsingSelector:@selector(finderCompare:)];
|
|
|
|
// sortedURLs = [expandedURLs sortedArrayUsingSelector:@selector(compareTrackNumbers:)];
|
2007-05-28 14:58:08 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-02-28 17:41:37 +00:00
|
|
|
sortedURLs = expandedURLs;
|
2007-05-28 14:58:08 +00:00
|
|
|
}
|
|
|
|
|
2008-02-20 00:54:45 +00:00
|
|
|
for (url in sortedURLs)
|
2007-05-28 14:58:08 +00:00
|
|
|
{
|
2007-10-20 03:22:13 +00:00
|
|
|
//Container vs non-container url
|
2016-06-19 19:57:18 +00:00
|
|
|
if ([[self acceptableContainerTypes] containsObject:[[url pathExtension] lowercaseString]]) {
|
2020-02-16 09:00:39 +00:00
|
|
|
NSArray * urls = [AudioContainer urlsForContainerURL:url];
|
|
|
|
|
|
|
|
if (urls != nil && [urls count] != 0) {
|
|
|
|
[containedURLs addObjectsFromArray:urls];
|
2007-10-14 20:36:10 +00:00
|
|
|
|
2020-02-16 09:00:39 +00:00
|
|
|
//Make sure the container isn't added twice.
|
|
|
|
[uniqueURLs addObject:url];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Fall back on adding the raw file if all container parsers have failed. */
|
|
|
|
[fileURLs addObject:url];
|
|
|
|
}
|
2007-05-28 14:58:08 +00:00
|
|
|
}
|
2016-06-19 19:57:18 +00:00
|
|
|
else if ([[[url pathExtension] lowercaseString] isEqualToString:@"xml"])
|
2013-10-09 15:45:16 +00:00
|
|
|
{
|
2013-10-10 08:43:04 +00:00
|
|
|
xmlData = [XmlContainer entriesForContainerURL:url];
|
2013-10-09 15:45:16 +00:00
|
|
|
}
|
2007-05-28 14:58:08 +00:00
|
|
|
else
|
|
|
|
{
|
2007-10-19 02:23:10 +00:00
|
|
|
[fileURLs addObject:url];
|
2007-05-28 14:58:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"File urls: %@", fileURLs);
|
2007-10-19 02:23:10 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Contained urls: %@", containedURLs);
|
2007-10-19 02:23:10 +00:00
|
|
|
|
2008-02-20 00:54:45 +00:00
|
|
|
for (url in fileURLs)
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
|
|
|
if (![[AudioPlayer schemes] containsObject:[url scheme]])
|
|
|
|
continue;
|
2016-06-19 19:57:18 +00:00
|
|
|
|
|
|
|
NSString *ext = [[url pathExtension] lowercaseString];
|
2007-03-09 01:16:06 +00:00
|
|
|
|
|
|
|
//Need a better way to determine acceptable file types than basing it on extensions.
|
2016-06-19 19:57:18 +00:00
|
|
|
if ([url isFileURL] && ![[AudioPlayer fileTypes] containsObject:ext])
|
2007-03-09 01:16:06 +00:00
|
|
|
continue;
|
2007-05-27 16:14:29 +00:00
|
|
|
|
|
|
|
if (![uniqueURLs containsObject:url])
|
|
|
|
{
|
|
|
|
[validURLs addObject:url];
|
2007-03-09 01:16:06 +00:00
|
|
|
|
2007-05-27 16:14:29 +00:00
|
|
|
[uniqueURLs addObject:url];
|
|
|
|
}
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
2007-10-19 02:23:10 +00:00
|
|
|
|
2013-10-11 12:03:55 +00:00
|
|
|
DLog(@"Valid urls: %@", validURLs);
|
2007-10-19 02:23:10 +00:00
|
|
|
|
2008-02-20 00:54:45 +00:00
|
|
|
for (url in containedURLs)
|
2007-10-19 02:23:10 +00:00
|
|
|
{
|
|
|
|
if (![[AudioPlayer schemes] containsObject:[url scheme]])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
//Need a better way to determine acceptable file types than basing it on extensions.
|
2016-06-19 19:57:18 +00:00
|
|
|
if ([url isFileURL] && ![[AudioPlayer fileTypes] containsObject:[[url pathExtension] lowercaseString]])
|
2007-10-19 02:23:10 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
[validURLs addObject:url];
|
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
//Create actual entries
|
2016-06-30 05:10:29 +00:00
|
|
|
int count = (int) [validURLs count];
|
2013-10-10 08:43:04 +00:00
|
|
|
if (xmlData) count += [[xmlData objectForKey:@"entries"] count];
|
2013-10-09 15:45:16 +00:00
|
|
|
|
2018-09-23 23:44:44 +00:00
|
|
|
// no valid URLs, or they use an unsupported URL scheme
|
|
|
|
if (!count)
|
|
|
|
return [NSArray array];
|
|
|
|
|
2021-04-30 01:16:24 +00:00
|
|
|
NSInteger i = 0;
|
2013-10-09 15:45:16 +00:00
|
|
|
NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
|
2013-10-10 08:43:04 +00:00
|
|
|
for (NSURL *url in validURLs)
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
2008-03-13 01:43:33 +00:00
|
|
|
PlaylistEntry *pe;
|
2018-06-28 10:59:59 +00:00
|
|
|
pe = [[PlaylistEntry alloc] init];
|
2007-03-09 01:16:06 +00:00
|
|
|
|
2008-03-01 19:56:10 +00:00
|
|
|
pe.URL = url;
|
2008-02-23 19:46:23 +00:00
|
|
|
pe.index = index+i;
|
2008-03-01 19:56:10 +00:00
|
|
|
pe.title = [[url path] lastPathComponent];
|
|
|
|
pe.queuePosition = -1;
|
2007-03-09 01:16:06 +00:00
|
|
|
[entries addObject:pe];
|
|
|
|
|
2013-10-10 08:43:04 +00:00
|
|
|
++i;
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
2013-10-10 08:43:04 +00:00
|
|
|
|
2021-04-30 01:16:24 +00:00
|
|
|
NSInteger j = index + i;
|
2013-10-09 15:45:16 +00:00
|
|
|
|
2013-10-10 08:43:04 +00:00
|
|
|
if (xmlData)
|
2013-10-09 15:45:16 +00:00
|
|
|
{
|
2013-10-10 08:43:04 +00:00
|
|
|
for (NSDictionary *entry in [xmlData objectForKey:@"entries"])
|
|
|
|
{
|
|
|
|
PlaylistEntry *pe;
|
2018-06-28 10:59:59 +00:00
|
|
|
pe = [[PlaylistEntry alloc] init];
|
2013-10-10 08:43:04 +00:00
|
|
|
|
|
|
|
[pe setValuesForKeysWithDictionary:entry];
|
|
|
|
pe.index = index+i;
|
|
|
|
pe.queuePosition = -1;
|
|
|
|
[entries addObject:pe];
|
|
|
|
|
|
|
|
++i;
|
|
|
|
}
|
2013-10-09 15:45:16 +00:00
|
|
|
}
|
2007-03-09 01:16:06 +00:00
|
|
|
|
|
|
|
NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, [entries count])];
|
2008-02-10 16:16:45 +00:00
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
[playlistController insertObjects:entries atArrangedObjectIndexes:is];
|
2013-10-10 08:43:04 +00:00
|
|
|
|
|
|
|
if (xmlData && [[xmlData objectForKey:@"queue"] count])
|
|
|
|
{
|
|
|
|
[playlistController emptyQueueList:self];
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
for (NSNumber *index in [xmlData objectForKey:@"queue"])
|
|
|
|
{
|
2021-04-30 01:16:24 +00:00
|
|
|
NSInteger indexVal = [index intValue] + j;
|
2013-10-10 08:43:04 +00:00
|
|
|
PlaylistEntry *pe = [entries objectAtIndex:indexVal];
|
|
|
|
pe.queuePosition = i;
|
|
|
|
pe.queued = YES;
|
|
|
|
|
|
|
|
[[playlistController queueList] addObject:pe];
|
|
|
|
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-15 17:38:37 +00:00
|
|
|
//Clear the selection
|
2016-06-29 02:33:48 +00:00
|
|
|
[playlistController setSelectionIndexes:[NSIndexSet indexSet]];
|
2019-12-06 03:04:46 +00:00
|
|
|
|
2021-05-08 00:19:10 +00:00
|
|
|
NSArray* arrayFirst = [NSArray arrayWithObject:[entries objectAtIndex:0]];
|
|
|
|
NSMutableArray* arrayRest = [entries mutableCopy];
|
|
|
|
[arrayRest removeObjectAtIndex:0];
|
|
|
|
|
|
|
|
[self performSelectorOnMainThread:@selector(syncLoadInfoForEntries:) withObject:arrayFirst waitUntilDone:YES];
|
|
|
|
if ([arrayRest count])
|
|
|
|
[self performSelectorInBackground:@selector(loadInfoForEntries:) withObject:arrayRest];
|
2008-05-09 21:24:49 +00:00
|
|
|
return entries;
|
2008-03-03 02:25:52 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 00:19:10 +00:00
|
|
|
static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_block_t block) {
|
|
|
|
if (dispatch_queue_get_label(queue) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) {
|
|
|
|
block();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dispatch_sync(queue, block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-30 01:16:24 +00:00
|
|
|
- (void)loadInfoForEntries:(NSArray *)entries
|
|
|
|
{
|
2021-05-08 00:19:10 +00:00
|
|
|
NSMutableIndexSet *update_indexes = [[NSMutableIndexSet alloc] init];
|
|
|
|
long i, j;
|
|
|
|
NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init];
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
j = 0;
|
|
|
|
for (PlaylistEntry *pe in entries)
|
|
|
|
{
|
|
|
|
long idx = j++;
|
|
|
|
|
|
|
|
if ([pe metadataLoaded]) continue;
|
|
|
|
|
|
|
|
[update_indexes addIndex:pe.index];
|
|
|
|
[load_info_indexes addIndex:idx];
|
|
|
|
|
|
|
|
++i;
|
|
|
|
}
|
2021-04-30 01:16:24 +00:00
|
|
|
|
2021-05-08 00:19:10 +00:00
|
|
|
if (!i)
|
|
|
|
{
|
|
|
|
[playlistController performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
NSLock *outLock = [[NSLock alloc] init];
|
|
|
|
NSMutableArray *outArray = [[NSMutableArray alloc] init];
|
|
|
|
|
|
|
|
__block NSLock *weakLock = outLock;
|
|
|
|
__block NSMutableArray *weakArray = outArray;
|
|
|
|
|
|
|
|
{
|
|
|
|
[load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop)
|
|
|
|
{
|
|
|
|
__block PlaylistEntry *weakPe = [entries objectAtIndex:idx];
|
|
|
|
|
|
|
|
NSBlockOperation *op = [[NSBlockOperation alloc] init];
|
|
|
|
|
|
|
|
[op addExecutionBlock:^{
|
|
|
|
NSMutableDictionary *entryInfo = [NSMutableDictionary dictionaryWithCapacity:20];
|
|
|
|
|
|
|
|
NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:weakPe.URL];
|
|
|
|
if (entryProperties == nil)
|
|
|
|
return;
|
|
|
|
|
|
|
|
[entryInfo addEntriesFromDictionary:entryProperties];
|
|
|
|
[entryInfo addEntriesFromDictionary:[AudioMetadataReader metadataForURL:weakPe.URL]];
|
|
|
|
|
|
|
|
[weakLock lock];
|
|
|
|
[weakArray addObject:weakPe];
|
|
|
|
[weakArray addObject:entryInfo];
|
|
|
|
[weakLock unlock];
|
|
|
|
}];
|
|
|
|
|
|
|
|
[queue addOperation:op];
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
[queue waitUntilAllOperationsAreFinished];
|
|
|
|
|
|
|
|
for (i = 0, j = [outArray count]; i < j; i += 2) {
|
|
|
|
__block PlaylistEntry *weakPe = [outArray objectAtIndex:i];
|
|
|
|
__block NSDictionary *entryInfo = [outArray objectAtIndex:i + 1];
|
|
|
|
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
|
|
|
|
[weakPe setMetadata:entryInfo];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[playlistController performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO];
|
|
|
|
|
|
|
|
{
|
|
|
|
__block NSScrollView *weakPlaylistView = playlistView;
|
|
|
|
__block NSIndexSet *weakIndexSet = update_indexes;
|
|
|
|
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
|
|
|
|
unsigned long columns = [[[weakPlaylistView documentView] tableColumns] count];
|
|
|
|
[weakPlaylistView.documentView reloadDataForRowIndexes:weakIndexSet columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,columns-1)]];
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-12-06 03:04:46 +00:00
|
|
|
// To be called on main thread only
|
|
|
|
- (void)syncLoadInfoForEntries:(NSArray *)entries
|
|
|
|
{
|
|
|
|
NSMutableIndexSet *update_indexes = [[NSMutableIndexSet alloc] init];
|
2021-04-06 23:50:17 +00:00
|
|
|
long i, j;
|
|
|
|
NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init];
|
2019-12-06 03:04:46 +00:00
|
|
|
|
|
|
|
i = 0;
|
2021-04-06 23:50:17 +00:00
|
|
|
j = 0;
|
2019-12-06 03:04:46 +00:00
|
|
|
for (PlaylistEntry *pe in entries)
|
|
|
|
{
|
2021-04-06 23:50:17 +00:00
|
|
|
long idx = j++;
|
|
|
|
|
2019-12-06 03:04:46 +00:00
|
|
|
if ([pe metadataLoaded]) continue;
|
|
|
|
|
|
|
|
[update_indexes addIndex:pe.index];
|
2021-04-06 23:50:17 +00:00
|
|
|
[load_info_indexes addIndex:idx];
|
2019-12-06 03:04:46 +00:00
|
|
|
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!i)
|
|
|
|
{
|
|
|
|
[self->playlistController updateTotalTime];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-04-06 23:50:17 +00:00
|
|
|
[load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop)
|
2019-12-06 03:04:46 +00:00
|
|
|
{
|
2021-04-06 23:50:17 +00:00
|
|
|
PlaylistEntry *pe = [entries objectAtIndex:idx];
|
|
|
|
|
2019-12-06 03:04:46 +00:00
|
|
|
NSMutableDictionary *entryInfo = [NSMutableDictionary dictionaryWithCapacity:20];
|
|
|
|
|
|
|
|
NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:pe.URL];
|
|
|
|
if (entryProperties == nil)
|
2021-04-06 23:50:17 +00:00
|
|
|
return;
|
2019-12-06 03:04:46 +00:00
|
|
|
|
|
|
|
[entryInfo addEntriesFromDictionary:entryProperties];
|
|
|
|
[entryInfo addEntriesFromDictionary:[AudioMetadataReader metadataForURL:pe.URL]];
|
|
|
|
|
|
|
|
[pe setMetadata:entryInfo];
|
2021-04-06 23:50:17 +00:00
|
|
|
}];
|
2019-12-06 03:04:46 +00:00
|
|
|
|
|
|
|
[self->playlistController updateTotalTime];
|
|
|
|
|
|
|
|
{
|
|
|
|
unsigned long columns = [[[self->playlistView documentView] tableColumns] count];
|
|
|
|
[self->playlistView.documentView reloadDataForRowIndexes:update_indexes columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,columns-1)]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-16 16:49:34 +00:00
|
|
|
- (void)clear:(id)sender
|
|
|
|
{
|
|
|
|
[playlistController clear:sender];
|
|
|
|
}
|
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
- (NSArray*)addURLs:(NSArray *)urls sort:(BOOL)sort
|
2007-03-09 01:16:06 +00:00
|
|
|
{
|
2016-06-30 05:10:29 +00:00
|
|
|
return [self insertURLs:urls atIndex:(int)[[playlistController content] count] sort:sort];
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
|
|
|
|
2008-05-09 21:24:49 +00:00
|
|
|
- (NSArray*)addURL:(NSURL *)url
|
2007-05-27 16:14:29 +00:00
|
|
|
{
|
2016-06-30 05:10:29 +00:00
|
|
|
return [self insertURLs:[NSArray arrayWithObject:url] atIndex:(int)[[playlistController content] count] sort:NO];
|
2007-05-27 16:14:29 +00:00
|
|
|
}
|
|
|
|
|
2007-03-09 01:16:06 +00:00
|
|
|
- (NSArray *)acceptableFileTypes
|
|
|
|
{
|
2007-10-09 01:20:46 +00:00
|
|
|
return [[self acceptableContainerTypes] arrayByAddingObjectsFromArray:[AudioPlayer fileTypes]];
|
2007-03-09 01:16:06 +00:00
|
|
|
}
|
|
|
|
|
2007-10-13 04:53:48 +00:00
|
|
|
- (NSArray *)acceptablePlaylistTypes
|
|
|
|
{
|
|
|
|
return [NSArray arrayWithObjects:@"m3u", @"pls", nil];
|
|
|
|
}
|
|
|
|
|
2007-10-09 01:20:46 +00:00
|
|
|
- (NSArray *)acceptableContainerTypes
|
2007-03-07 01:26:50 +00:00
|
|
|
{
|
2007-10-09 01:20:46 +00:00
|
|
|
return [AudioPlayer containerTypes];
|
2007-03-07 01:26:50 +00:00
|
|
|
}
|
|
|
|
|
2009-02-28 22:22:33 +00:00
|
|
|
- (void)willInsertURLs:(NSArray*)urls origin:(URLOrigin)origin
|
2008-05-09 21:24:49 +00:00
|
|
|
{
|
2009-02-28 22:22:33 +00:00
|
|
|
[playlistController willInsertURLs:urls origin:origin];
|
2008-05-09 21:24:49 +00:00
|
|
|
}
|
2009-02-28 22:22:33 +00:00
|
|
|
- (void)didInsertURLs:(NSArray*)urls origin:(URLOrigin)origin
|
2008-05-09 21:24:49 +00:00
|
|
|
{
|
2009-02-28 22:22:33 +00:00
|
|
|
[playlistController didInsertURLs:urls origin:origin];
|
2008-05-09 21:24:49 +00:00
|
|
|
}
|
|
|
|
|
2007-03-07 01:26:50 +00:00
|
|
|
@end
|