2007-03-06 22:26:50 -03:00
//
// PlaylistLoader . m
// Cog
//
// Created by Vincent Spader on 3 / 05 / 07.
// Copyright 2007 Vincent Spader All rights reserved .
//
2013-10-09 12:45:16 -03:00
# include < objc / runtime . h >
2016-09-01 21:20:53 -03:00
# include < mach / semaphore . h >
2022-06-16 10:14:33 -04:00
# import "Cog-Swift.h"
# import < CoreData / CoreData . h >
2022-02-07 02:49:27 -03:00
# import "AppController.h"
2007-03-06 22:26:50 -03:00
# import "PlaylistController.h"
# import "PlaylistEntry.h"
2022-02-07 02:49:27 -03:00
# import "PlaylistLoader.h"
2007-03-06 22:26:50 -03:00
2007-03-13 22:28:30 -04:00
# import "NSFileHandle+CreateFile.h"
2007-10-08 21:20:46 -04:00
# import "CogAudio/AudioContainer.h"
2008-03-01 15:29:14 -03:00
# import "CogAudio/AudioMetadataReader.h"
2022-02-07 02:49:27 -03:00
# import "CogAudio/AudioPlayer.h"
# import "CogAudio/AudioPropertiesReader.h"
2007-03-08 22:16:06 -03:00
2018-06-28 06:59:59 -04:00
# import "XmlContainer.h"
2013-10-09 12:45:16 -03:00
2013-10-09 20:14:23 -03:00
# import "NSData+MD5.h"
2018-06-28 06:59:59 -04:00
# import "NSString+FinderCompare.h"
2021-12-24 06:01:21 -03:00
# import "SQLiteStore.h"
2013-10-11 09:03:55 -03:00
# import "Logging.h"
2022-02-15 01:02:18 -03:00
# import "NSDictionary+Merge.h"
2022-02-17 02:38:43 -03:00
# import "RedundantPlaylistDataStore.h"
2022-06-29 02:13:42 -04:00
# import "SandboxBroker.h"
2025-02-27 01:28:51 -03:00
@ import Sentry ;
2022-06-21 03:01:07 -04:00
extern NSMutableDictionary < NSString * , AlbumArtwork * > * kArtworkDictionary ;
2022-06-16 10:14:33 -04:00
2007-03-06 22:26:50 -03:00
@ implementation PlaylistLoader
2022-02-07 02:49:27 -03:00
- ( id ) init {
2008-05-03 12:01:27 -04:00
self = [ super init ] ;
2022-02-07 02:49:27 -03:00
if ( self ) {
2009-08-16 12:49:34 -04:00
[ self initDefaults ] ;
2022-02-07 02:49:27 -03:00
2025-02-28 00:02:33 -03:00
containerQueue = [ [ NSOperationQueue alloc ] init ] ;
[ containerQueue setMaxConcurrentOperationCount : 8 ] ;
2009-03-06 01:37:44 -03:00
queue = [ [ NSOperationQueue alloc ] init ] ;
2021-05-07 20:19:10 -04:00
[ queue setMaxConcurrentOperationCount : 8 ] ;
2022-06-21 02:22:07 -04:00
queuedURLs = [ [ NSMutableDictionary alloc ] init ] ;
2008-05-03 12:01:27 -04:00
}
2022-02-07 02:49:27 -03:00
return self ;
2008-05-03 12:01:27 -04:00
}
2022-02-07 02:49:27 -03:00
- ( void ) initDefaults {
2025-03-10 03:01:40 -03:00
NSDictionary * defaultsDictionary = @ { @ "readCueSheetsInFolders" : @ NO ,
@ "readPlaylistsInFolders" : @ NO ,
@ "addOtherFilesInFolders" : @ NO } ;
2022-02-07 02:49:27 -03:00
2009-08-16 12:49:34 -04:00
[ [ NSUserDefaults standardUserDefaults ] registerDefaults : defaultsDictionary ] ;
}
2022-02-07 02:49:27 -03:00
- ( BOOL ) save : ( NSString * ) filename {
2007-03-06 22:26:50 -03:00
NSString * ext = [ filename pathExtension ] ;
2022-02-07 02:49:27 -03:00
if ( [ ext isEqualToString : @ "pls" ] ) {
2007-03-06 22:26:50 -03:00
return [ self save : filename asType : kPlaylistPls ] ;
2022-02-07 02:49:27 -03:00
} else {
2007-03-06 22:26:50 -03:00
return [ self save : filename asType : kPlaylistM3u ] ;
}
2022-02-07 02:49:27 -03:00
}
2007-03-06 22:26:50 -03:00
2022-02-07 02:49:27 -03:00
- ( BOOL ) save : ( NSString * ) filename asType : ( PlaylistType ) type {
if ( type = = kPlaylistM3u ) {
2007-03-06 22:26:50 -03:00
return [ self saveM3u : filename ] ;
2022-02-07 02:49:27 -03:00
} else if ( type = = kPlaylistPls ) {
2007-03-06 22:26:50 -03:00
return [ self savePls : filename ] ;
2022-02-07 02:49:27 -03:00
} else if ( type = = kPlaylistXml ) {
return [ self saveXml : filename ] ;
2007-03-06 22:26:50 -03:00
}
return NO ;
}
2022-02-07 02:49:27 -03:00
- ( NSString * ) relativePathFrom : ( NSString * ) filename toURL : ( NSURL * ) entryURL {
2007-03-06 22:26:50 -03:00
NSString * basePath = [ [ [ filename stringByStandardizingPath ] stringByDeletingLastPathComponent ] stringByAppendingString : @ "/" ] ;
2022-02-07 02:49:27 -03:00
if ( [ entryURL isFileURL ] ) {
// We want relative paths .
2016-05-05 17:05:39 -03:00
NSMutableString * entryPath = [ [ [ entryURL path ] stringByStandardizingPath ] mutableCopy ] ;
2007-03-06 22:26:50 -03:00
2007-03-08 22:16:06 -03:00
[ entryPath replaceOccurrencesOfString : basePath withString : @ "" options : ( NSAnchoredSearch | NSLiteralSearch | NSCaseInsensitiveSearch ) range : NSMakeRange ( 0 , [ entryPath length ] ) ] ;
2022-02-07 02:49:27 -03:00
if ( [ entryURL fragment ] ) {
2007-10-15 19:19:14 -03:00
[ entryPath appendString : @ "#" ] ;
[ entryPath appendString : [ entryURL fragment ] ] ;
}
2007-03-06 22:26:50 -03:00
2022-02-07 02:49:27 -03:00
return entryPath ;
} else {
// Write [ entryURL absoluteString ] to file
2007-03-06 22:26:50 -03:00
return [ entryURL absoluteString ] ;
}
}
2022-02-07 02:49:27 -03:00
- ( BOOL ) saveM3u : ( NSString * ) filename {
2007-03-13 22:28:30 -04:00
NSFileHandle * fileHandle = [ NSFileHandle fileHandleForWritingAtPath : filename createFile : YES ] ;
2022-02-07 02:49:27 -03:00
if ( ! fileHandle ) {
2013-10-11 09:03:55 -03:00
ALog ( @ "Error saving m3u!" ) ;
2008-02-12 22:50:39 -03:00
return NO ;
2007-03-06 22:26:50 -03:00
}
2007-03-08 22:16:06 -03:00
[ fileHandle truncateFileAtOffset : 0 ] ;
2022-02-07 02:49:27 -03:00
[ fileHandle writeData : [ @ "#\n" dataUsingEncoding : NSUTF8StringEncoding ] ] ;
for ( PlaylistEntry * pe in [ playlistController arrangedObjects ] ) {
2022-06-16 10:14:33 -04:00
NSString * path = [ self relativePathFrom : filename toURL : pe . url ] ;
2007-03-06 22:26:50 -03:00
[ fileHandle writeData : [ [ path stringByAppendingString : @ "\n" ] dataUsingEncoding : NSUTF8StringEncoding ] ] ;
}
2007-03-08 22:16:06 -03:00
[ fileHandle closeFile ] ;
2007-03-06 22:26:50 -03:00
return YES ;
}
2022-02-07 02:49:27 -03:00
- ( BOOL ) savePls : ( NSString * ) filename {
2007-03-13 22:28:30 -04:00
NSFileHandle * fileHandle = [ NSFileHandle fileHandleForWritingAtPath : filename createFile : YES ] ;
2022-02-07 02:49:27 -03:00
if ( ! fileHandle ) {
2007-03-06 22:26:50 -03:00
return NO ;
}
2007-03-08 22:16:06 -03:00
[ fileHandle truncateFileAtOffset : 0 ] ;
2022-02-07 02:49:27 -03:00
[ fileHandle writeData : [ [ NSString stringWithFormat : @ "[playlist]\nnumberOfEntries=%lu\n\n" , ( unsigned long ) [ [ playlistController content ] count ] ] dataUsingEncoding : NSUTF8StringEncoding ] ] ;
2007-03-06 22:26:50 -03:00
2007-03-06 22:45:45 -03:00
int i = 1 ;
2022-02-07 02:49:27 -03:00
for ( PlaylistEntry * pe in [ playlistController arrangedObjects ] ) {
2022-06-16 10:14:33 -04:00
NSString * path = [ self relativePathFrom : filename toURL : pe . url ] ;
2022-02-07 02:49:27 -03:00
NSString * entry = [ NSString stringWithFormat : @ "File%i=%@\n" , i , path ] ;
2007-03-06 22:26:50 -03:00
[ fileHandle writeData : [ entry dataUsingEncoding : NSUTF8StringEncoding ] ] ;
2007-03-06 22:45:45 -03:00
i + + ;
2007-03-06 22:26:50 -03:00
}
2007-03-08 22:16:06 -03:00
[ fileHandle writeData : [ @ "\nVERSION=2" dataUsingEncoding : NSUTF8StringEncoding ] ] ;
[ fileHandle closeFile ] ;
2007-03-06 22:26:50 -03:00
return YES ;
}
2022-02-07 02:49:27 -03:00
NSMutableDictionary * dictionaryWithPropertiesOfObject ( id obj , NSArray * filterList ) {
NSMutableDictionary * dict = [ NSMutableDictionary dictionary ] ;
Class class = [ obj class ] ;
do {
unsigned count ;
objc_property _t * properties = class_copyPropertyList ( class , & count ) ;
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 ] ;
}
}
free ( properties ) ;
if ( count ) break ;
class = [ class superclass ] ;
} while ( class ) ;
return dict ;
2013-10-09 12:45:16 -03:00
}
2022-02-07 02:49:27 -03:00
- ( BOOL ) saveXml : ( NSString * ) filename {
2013-10-09 12:45:16 -03:00
NSFileHandle * fileHandle = [ NSFileHandle fileHandleForWritingAtPath : filename createFile : YES ] ;
2022-02-07 02:49:27 -03:00
if ( ! fileHandle ) {
2013-10-09 12:45:16 -03:00
return NO ;
}
[ fileHandle truncateFileAtOffset : 0 ] ;
2022-02-07 02:49:27 -03:00
NSArray * filterList = @ [ @ "display" , @ "length" , @ "path" , @ "filename" , @ "status" , @ "statusMessage" , @ "spam" , @ "lengthText" , @ "positionText" , @ "stopAfter" , @ "shuffleIndex" , @ "index" , @ "current" , @ "queued" , @ "currentPosition" , @ "queuePosition" , @ "error" , @ "removed" , @ "URL" , @ "albumArt" ] ;
NSMutableDictionary * albumArtSet = [ [ NSMutableDictionary alloc ] init ] ;
NSMutableArray * topLevel = [ [ NSMutableArray alloc ] init ] ;
for ( PlaylistEntry * pe in [ playlistController arrangedObjects ] ) {
BOOL error = [ pe error ] ;
NSMutableDictionary * dict = dictionaryWithPropertiesOfObject ( pe , filterList ) ;
2013-10-09 12:45:16 -03:00
2022-06-16 10:14:33 -04:00
NSString * path = [ self relativePathFrom : filename toURL : pe . url ] ;
2022-02-07 02:49:27 -03:00
[ dict setObject : path forKey : @ "URL" ] ;
NSData * albumArt = [ dict objectForKey : @ "albumArtInternal" ] ;
if ( albumArt ) {
[ dict removeObjectForKey : @ "albumArtInternal" ] ;
NSString * hash = [ albumArt MD5 ] ;
if ( ! [ albumArtSet objectForKey : hash ] )
[ albumArtSet setObject : albumArt forKey : hash ] ;
[ dict setObject : hash forKey : @ "albumArt" ] ;
}
if ( error )
[ dict removeObjectForKey : @ "metadataLoaded" ] ;
[ topLevel addObject : dict ] ;
}
NSMutableArray * queueList = [ [ NSMutableArray alloc ] init ] ;
for ( PlaylistEntry * pe in [ playlistController queueList ] ) {
2022-06-17 09:39:02 -04:00
[ queueList addObject : @ ( pe . index ) ] ;
2022-02-07 02:49:27 -03:00
}
2022-02-09 00:42:03 -03:00
NSDictionary * dictionary = @ { @ "albumArt" : albumArtSet , @ "queue" : queueList , @ "items" : topLevel } ;
2022-02-07 02:49:27 -03:00
NSError * err ;
NSData * data = [ NSPropertyListSerialization dataWithPropertyList : dictionary format : NSPropertyListXMLFormat_v1 _0 options : 0 error : & err ] ;
[ fileHandle writeData : data ] ;
[ fileHandle closeFile ] ;
2013-10-09 12:45:16 -03:00
return YES ;
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) fileURLsAtPath : ( NSString * ) path {
2007-03-08 22:16:06 -03:00
NSFileManager * manager = [ NSFileManager defaultManager ] ;
2022-02-07 02:49:27 -03:00
2007-03-08 22:16:06 -03:00
NSMutableArray * urls = [ NSMutableArray array ] ;
2022-02-07 02:49:27 -03:00
2022-07-25 22:33:51 -04:00
const void * sbHandle = [ [ SandboxBroker sharedSandboxBroker ] beginFolderAccess : [ NSURL fileURLWithPath : path ] ] ;
2007-03-08 22:16:06 -03:00
NSArray * subpaths = [ manager subpathsAtPath : path ] ;
2022-07-25 22:33:51 -04:00
[ [ SandboxBroker sharedSandboxBroker ] endFolderAccess : sbHandle ] ;
2007-03-08 22:16:06 -03:00
2025-03-10 03:01:40 -03:00
BOOL readCueSheets = [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "readCueSheetsInFolders" ] ;
BOOL readPlaylists = [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "readPlaylistsInFolders" ] ;
2022-02-07 02:49:27 -03:00
for ( NSString * subpath in subpaths ) {
NSString * absoluteSubpath = [ NSString pathWithComponents : @ [ path , subpath ] ] ;
2007-03-08 22:16:06 -03:00
BOOL isDir ;
2022-02-07 02:49:27 -03:00
if ( [ manager fileExistsAtPath : absoluteSubpath isDirectory : & isDir ] && isDir = = NO ) {
2025-03-10 03:01:40 -03:00
BOOL readFile = YES ;
NSString * ext = [ absoluteSubpath pathExtension ] ;
if ( [ ext caseInsensitiveCompare : @ "cue" ] = = NSOrderedSame ) {
readFile = readCueSheets ;
} else if ( [ ext caseInsensitiveCompare : @ "m3u" ] = = NSOrderedSame ||
[ ext caseInsensitiveCompare : @ "m3u8" ] = = NSOrderedSame ||
[ ext caseInsensitiveCompare : @ "pls" ] = = NSOrderedSame ) {
readFile = readPlaylists ;
}
if ( readFile ) {
2009-08-16 12:49:34 -04:00
[ urls addObject : [ NSURL fileURLWithPath : absoluteSubpath ] ] ;
}
2007-03-08 22:16:06 -03:00
}
}
2022-02-07 02:49:27 -03:00
NSSortDescriptor * sd_path = [ [ NSSortDescriptor alloc ] initWithKey : @ "path" ascending : YES ] ;
[ urls sortUsingDescriptors : @ [ sd_path ] ] ;
2007-03-08 22:16:06 -03:00
return urls ;
}
2022-01-09 07:10:08 -03:00
static inline void dispatch_sync _reentrant ( dispatch_queue _t queue , dispatch_block _t block ) {
2022-02-07 02:49:27 -03:00
if ( dispatch_queue _get _label ( queue ) = = dispatch_queue _get _label ( DISPATCH_CURRENT _QUEUE _LABEL ) ) {
block ( ) ;
} else {
dispatch_sync ( queue , block ) ;
}
2022-01-09 07:10:08 -03:00
}
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
- ( void ) beginProgress : ( NSString * ) localizedDescription {
2022-06-15 04:10:53 -04:00
while ( playbackController . progressOverall ) {
[ [ NSRunLoop currentRunLoop ] runUntilDate : [ NSDate dateWithTimeIntervalSinceNow : 0.01 ] ] ;
}
2022-02-07 02:49:27 -03:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
self -> playbackController . progressOverall = [ NSProgress progressWithTotalUnitCount : 100000 ] ;
self -> playbackController . progressOverall . localizedDescription = localizedDescription ;
2022-02-07 02:49:27 -03:00
} ) ;
2022-01-09 07:10:08 -03:00
}
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
- ( void ) completeProgress {
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
if ( self -> playbackController . progressJob ) {
[ self -> playbackController . progressJob setCompletedUnitCount : 100000 ] ;
self -> playbackController . progressJob = nil ;
}
[ self -> playbackController . progressOverall setCompletedUnitCount : 100000 ] ;
self -> playbackController . progressOverall = nil ;
} ) ;
}
- ( void ) beginProgressJob : ( NSString * ) localizedDescription percentOfTotal : ( double ) percent {
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
NSUInteger jobCount = ( NSUInteger ) ceil ( 1000.0 * percent ) ;
self -> playbackController . progressJob = [ NSProgress progressWithTotalUnitCount : 100000 ] ;
self -> playbackController . progressJob . localizedDescription = localizedDescription ;
[ self -> playbackController . progressOverall addChild : self -> playbackController . progressJob withPendingUnitCount : jobCount ] ;
} ) ;
}
- ( void ) completeProgressJob {
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[ self -> playbackController . progressJob setCompletedUnitCount : 100000 ] ;
self -> playbackController . progressJob = nil ;
} ) ;
}
- ( void ) setProgressStatus : ( double ) status {
if ( status >= 0 ) {
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
NSUInteger jobCount = ( NSUInteger ) ceil ( 1000.0 * status ) ;
[ self -> playbackController . progressOverall setCompletedUnitCount : jobCount ] ;
} ) ;
} else {
[ self completeProgress ] ;
}
}
- ( void ) setProgressJobStatus : ( double ) status {
if ( status >= 0 ) {
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
NSUInteger jobCount = ( NSUInteger ) ceil ( 1000.0 * status ) ;
[ self -> playbackController . progressJob setCompletedUnitCount : jobCount ] ;
} ) ;
} else {
[ self completeProgressJob ] ;
}
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) insertURLs : ( NSArray * ) urls atIndex : ( NSInteger ) index sort : ( BOOL ) sort {
2025-02-28 00:02:33 -03:00
__block NSMutableSet * uniqueURLs = [ NSMutableSet set ] ;
2022-02-07 02:49:27 -03:00
2025-05-10 00:04:35 -04:00
__block NSMutableDictionary * expandedURLs = [ [ NSMutableDictionary alloc ] init ] ;
__block NSMutableDictionary * loadedURLs = [ [ NSMutableDictionary alloc ] init ] ;
2025-02-28 00:02:33 -03:00
__block NSMutableArray * fileURLs = [ [ NSMutableArray alloc ] init ] ;
NSMutableArray * validURLs = [ [ NSMutableArray alloc ] init ] ;
NSMutableArray * folderURLs = [ [ NSMutableArray alloc ] init ] ;
NSMutableArray * dependencyURLs = [ [ NSMutableArray alloc ] init ] ;
__block NSDictionary * xmlData = nil ;
2022-02-07 02:49:27 -03:00
2022-07-25 02:12:11 -04:00
BOOL addOtherFilesInFolder = [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "addOtherFilesInFolders" ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
double progress ;
2022-02-07 02:49:27 -03:00
if ( ! urls ) {
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self completeProgress ] ;
2022-01-18 23:12:57 -03:00
return @ [ ] ;
2022-02-07 02:49:27 -03:00
}
2022-06-18 19:10:18 -04:00
[ self beginProgress : NSLocalizedString ( @ "ProgressActionLoader" , @ "" ) ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
2022-06-18 19:10:18 -04:00
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionLoaderListingFiles" , @ "" ) percentOfTotal : 20.0 ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
2022-02-07 02:49:27 -03:00
if ( index < 0 )
2007-03-08 22:16:06 -03:00
index = 0 ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = 0.0 ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
double progressstep = [ urls count ] ? 100.0 / ( double ) ( [ urls count ] ) : 0 ;
2008-05-03 11:15:45 -04:00
2025-02-27 01:28:51 -03:00
id < SentrySpan > mainTask = [ SentrySDK startTransactionWithName : @ "Loading playlist entries" operation : @ "Main task" ] ;
id < SentrySpan > sandboxTask = [ mainTask startChildWithOperation : @ "Initial Sandbox sweep" description : @ "Attempt load the files into the Sandbox storage, or locate them if they're already in storage" ] ;
2007-03-08 22:16:06 -03:00
NSURL * url ;
2022-02-07 02:49:27 -03:00
for ( url in urls ) {
2025-02-27 01:28:51 -03:00
id < SentrySpan > pathTask = [ sandboxTask startChildWithOperation : @ "Process one folder" description : [ NSString stringWithFormat : @ "Processing file or folder: %@" , url ] ] ;
@ try {
if ( [ url isFileURL ] ) {
BOOL isDir ;
if ( [ [ NSFileManager defaultManager ] fileExistsAtPath : [ url path ] isDirectory : & isDir ] ) {
if ( isDir = = YES ) {
// Get subpaths
[ [ SandboxBroker sharedSandboxBroker ] addFolderIfMissing : url ] ;
2025-05-10 00:04:35 -04:00
NSArray * pathURLs = [ self fileURLsAtPath : [ url path ] ] ;
for ( NSURL * url in pathURLs ) {
[ expandedURLs setValue : url forKey : [ url absoluteString ] ] ;
}
2025-02-27 01:28:51 -03:00
} else if ( addOtherFilesInFolder ) {
NSURL * folderUrl = [ url URLByDeletingLastPathComponent ] ;
if ( ! [ folderURLs containsObject : folderUrl ] ) {
[ [ SandboxBroker sharedSandboxBroker ] requestFolderForFile : url ] ;
2025-05-10 00:04:35 -04:00
NSArray * pathURLs = [ self fileURLsAtPath : [ folderUrl path ] ] ;
for ( NSURL * url in pathURLs ) {
[ expandedURLs setValue : url forKey : [ url absoluteString ] ] ;
}
2025-02-27 01:28:51 -03:00
[ folderURLs addObject : folderUrl ] ;
}
} else {
[ [ SandboxBroker sharedSandboxBroker ] addFileIfMissing : url ] ;
2025-05-10 00:04:35 -04:00
[ expandedURLs setValue : url forKey : [ url absoluteString ] ] ;
2022-07-25 02:12:11 -04:00
}
2007-03-08 22:16:06 -03:00
}
2025-02-27 01:28:51 -03:00
} else {
// Non - file URL . .
2025-05-10 00:04:35 -04:00
[ expandedURLs setValue : url forKey : [ url absoluteString ] ] ;
2007-03-08 22:16:06 -03:00
}
2025-02-27 01:28:51 -03:00
[ pathTask finish ] ;
}
2025-03-05 20:24:10 -03:00
@ catch ( NSException * e ) {
DLog ( @ "Exception caught while processing path: %@" , e ) ;
2025-03-09 19:03:31 -03:00
if ( e ) {
[ SentrySDK captureException : e ] ;
} else {
[ SentrySDK captureMessage : [ NSString stringWithFormat : @ "Null exception when processing path: %@" , url ] ] ;
}
2025-02-27 01:28:51 -03:00
[ pathTask finishWithStatus : kSentrySpanStatusInternalError ] ;
2007-03-08 22:16:06 -03:00
}
2022-02-07 02:49:27 -03:00
progress + = progressstep ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self setProgressJobStatus : progress ] ;
2022-02-07 02:49:27 -03:00
}
2025-02-27 01:28:51 -03:00
[ sandboxTask finish ] ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self completeProgressJob ] ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = 0.0 ;
2022-02-07 02:49:27 -03:00
2013-10-11 09:03:55 -03:00
DLog ( @ "Expanded urls: %@" , expandedURLs ) ;
2007-03-08 22:16:06 -03:00
2022-06-18 19:10:18 -04:00
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionLoaderFilteringContainerFiles" , @ "" ) percentOfTotal : 20.0 ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
2025-02-27 05:56:58 -03:00
progressstep = [ expandedURLs count ] ? 100.0 / ( double ) ( [ expandedURLs count ] ) : 0 ;
2022-02-07 02:49:27 -03:00
2025-02-28 00:02:33 -03:00
if ( [ expandedURLs count ] ) {
__block id < SentrySpan > containerTask = [ mainTask startChildWithOperation : @ "Process paths for containers" ] ;
__block NSLock * lock = [ [ NSLock alloc ] init ] ;
__block NSArray * acceptableContainerTypes = [ self acceptableContainerTypes ] ;
__block double weakProgress = progress ;
__block double weakProgressstep = progressstep ;
2025-02-27 01:28:51 -03:00
2022-02-07 02:49:27 -03:00
// Container vs non - container url
2025-05-10 00:04:35 -04:00
[ expandedURLs enumerateKeysAndObjectsUsingBlock : ^ ( id _Nonnull key , id _Nonnull obj , BOOL * _Nonnull stop ) {
2025-02-28 00:02:33 -03:00
NSBlockOperation * op = [ [ NSBlockOperation alloc ] init ] ;
[ op addExecutionBlock : ^ {
id < SentrySpan > pathTask = nil ;
id < SentrySpan > innerTask = nil ;
2025-03-09 19:03:31 -03:00
NSURL * url = nil ;
2025-02-28 00:02:33 -03:00
@ try {
if ( containerTask ) {
pathTask = [ containerTask startChildWithOperation : @ "Process path as container" description : [ NSString stringWithFormat : @ "Checking if file is container: %@" , url ] ] ;
2022-07-25 22:35:11 -04:00
}
2025-02-28 00:02:33 -03:00
2025-05-10 00:04:35 -04:00
url = obj ;
2025-02-28 00:02:33 -03:00
[ lock lock ] ;
2025-05-10 00:04:35 -04:00
if ( [ uniqueURLs containsObject : url ] ) {
[ lock unlock ] ;
return ;
}
2025-02-28 00:02:33 -03:00
[ lock unlock ] ;
if ( [ acceptableContainerTypes containsObject : [ [ url pathExtension ] lowercaseString ] ] ) {
if ( pathTask ) {
innerTask = [ pathTask startChildWithOperation : @ "Container, processing" ] ;
}
NSArray * urls = [ AudioContainer urlsForContainerURL : url ] ;
if ( urls ! = nil && [ urls count ] ! = 0 ) {
[ lock lock ] ;
2025-05-10 00:04:35 -04:00
[ loadedURLs setValue : urls forKey : key ] ;
2025-02-28 00:02:33 -03:00
[ lock unlock ] ;
// Make sure the container isn ' t added twice .
[ lock lock ] ;
[ uniqueURLs addObject : url ] ;
[ lock unlock ] ;
// Find the dependencies
NSArray * depURLs = [ AudioContainer dependencyUrlsForContainerURL : url ] ;
BOOL localFound = NO ;
for ( NSURL * u in urls ) {
if ( [ u isFileURL ] ) {
localFound = YES ;
break ;
}
2025-02-27 01:28:51 -03:00
}
2025-02-28 00:02:33 -03:00
if ( depURLs ) {
[ lock lock ] ;
[ dependencyURLs addObjectsFromArray : depURLs ] ;
[ lock unlock ] ;
for ( NSURL * u in depURLs ) {
if ( [ u isFileURL ] ) {
localFound = YES ;
break ;
}
}
}
if ( localFound ) {
[ [ SandboxBroker sharedSandboxBroker ] requestFolderForFile : url ] ;
}
} else {
/ * Fall back on adding the raw file if all container parsers have failed . * /
[ lock lock ] ;
2025-05-10 00:04:35 -04:00
[ loadedURLs setValue : url forKey : key ] ;
2025-02-28 00:02:33 -03:00
[ lock unlock ] ;
2025-02-27 01:28:51 -03:00
}
2025-02-28 00:02:33 -03:00
if ( innerTask ) {
[ innerTask finish ] ;
innerTask = nil ;
}
} else if ( [ [ [ url pathExtension ] lowercaseString ] isEqualToString : @ "xml" ] ) {
[ lock lock ] ;
xmlData = [ XmlContainer entriesForContainerURL : url ] ;
[ lock unlock ] ;
} else {
[ lock lock ] ;
2025-05-10 00:04:35 -04:00
[ loadedURLs setValue : url forKey : key ] ;
2025-02-28 00:02:33 -03:00
[ lock unlock ] ;
2025-02-27 01:28:51 -03:00
}
2025-02-28 00:02:33 -03:00
if ( pathTask ) {
[ pathTask finish ] ;
pathTask = nil ;
2025-02-27 01:28:51 -03:00
}
2022-07-25 22:35:11 -04:00
}
2025-03-05 20:24:10 -03:00
@ catch ( NSException * e ) {
DLog ( @ "Exception caught while processing for containers: %@" , e ) ;
2025-03-09 19:03:31 -03:00
if ( e ) {
[ SentrySDK captureException : e ] ;
} else {
[ SentrySDK captureMessage : [ NSString stringWithFormat : @ "Null exception caught while processing containers for URL: %@" , url ] ] ;
}
2025-02-28 00:02:33 -03:00
if ( innerTask ) {
[ innerTask finishWithStatus : kSentrySpanStatusInternalError ] ;
}
if ( pathTask ) {
[ pathTask finishWithStatus : kSentrySpanStatusInternalError ] ;
}
}
[ lock lock ] ;
weakProgress + = weakProgressstep ;
[ self setProgressJobStatus : weakProgress ] ;
[ lock unlock ] ;
} ] ;
2025-05-10 00:04:35 -04:00
[ self -> containerQueue addOperation : op ] ;
} ] ;
2022-02-07 02:49:27 -03:00
2025-02-28 00:02:33 -03:00
[ containerQueue waitUntilAllOperationsAreFinished ] ;
2022-02-07 02:49:27 -03:00
2025-02-28 00:02:33 -03:00
progress = weakProgress ;
[ containerTask finish ] ;
}
2025-02-27 01:28:51 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = 0.0 ;
[ self completeProgressJob ] ;
2025-05-10 00:04:35 -04:00
if ( [ loadedURLs count ] > 0 ) {
2022-06-18 19:10:18 -04:00
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionLoaderFilteringFiles" , @ "" ) percentOfTotal : 20.0 ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
} else {
[ self setProgressStatus : 60.0 ] ;
}
2007-05-28 10:58:08 -04:00
2025-03-09 19:03:31 -03:00
NSArray * fileTypes = [ AudioPlayer fileTypes ] ;
2025-02-27 01:28:51 -03:00
id < SentrySpan > filterTask = [ mainTask startChildWithOperation : @ "Filtering URLs for dupes and supported tracks" ] ;
2025-05-10 00:04:35 -04:00
NSArray * keys = [ loadedURLs allKeys ] ;
if ( sort ) {
keys = [ keys sortedArrayUsingSelector : @ selector ( finderCompare : ) ] ;
}
NSArray * objs = [ loadedURLs objectsForKeys : keys notFoundMarker : [ NSNull null ] ] ;
2025-05-11 08:32:49 -04:00
/ * Pass 1 : Collect unique URLs
* v2 : from containers only
* /
2025-05-10 00:04:35 -04:00
for ( id obj in objs ) {
2025-05-11 08:32:49 -04:00
/ * if ( [ obj isKindOfClass : [ NSURL class ] ] ) {
} else * /
if ( [ obj isKindOfClass : [ NSArray class ] ] ) {
2025-05-10 00:04:35 -04:00
for ( NSURL * url in obj ) {
if ( ! [ uniqueURLs containsObject : url ] ) {
[ uniqueURLs addObject : url ] ;
}
2022-07-25 22:35:11 -04:00
}
}
}
2022-07-25 02:13:03 -04:00
2025-05-11 08:32:49 -04:00
/ * Pass 2 : Only add outer URLs that are unique , but add all contained URLs
* v2 : only add outer URLs to unique list here , otherwise they don ' t get added at all : D
* Technically doing it for outer paths here isn ' t necessary , as the expanded URLs
* dictionary will end up deduplicating input paths anyway . We just don ' t want it
* happening to playlist or container contents
* /
2025-05-10 00:04:35 -04:00
for ( id obj in objs ) {
if ( [ obj isKindOfClass : [ NSURL class ] ] ) {
if ( ! [ uniqueURLs containsObject : obj ] ) {
[ fileURLs addObject : obj ] ;
2025-05-11 08:32:49 -04:00
[ uniqueURLs addObject : obj ] ;
2025-05-10 00:04:35 -04:00
}
} else if ( [ obj isKindOfClass : [ NSArray class ] ] ) {
[ fileURLs addObjectsFromArray : obj ] ;
}
}
2007-10-18 23:23:10 -03:00
2025-05-10 00:04:35 -04:00
DLog ( @ "File urls: %@" , fileURLs ) ;
2007-10-18 23:23:10 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progressstep = [ fileURLs count ] ? 100.0 / ( double ) ( [ fileURLs count ] ) : 0 ;
2022-02-07 02:49:27 -03:00
for ( url in fileURLs ) {
2025-02-27 01:28:51 -03:00
id < SentrySpan > fileTask = nil ;
2022-02-07 02:49:27 -03:00
2025-02-27 01:28:51 -03:00
@ try {
fileTask = [ filterTask startChildWithOperation : @ "Filtering individual path" description : [ NSString stringWithFormat : @ "File path: %@" , url ] ] ;
progress + = progressstep ;
if ( ! [ [ AudioPlayer schemes ] containsObject : [ url scheme ] ] )
continue ;
NSString * ext = [ [ url pathExtension ] lowercaseString ] ;
// Need a better way to determine acceptable file types than basing it on extensions .
2025-03-09 19:03:31 -03:00
if ( [ url isFileURL ] && ! [ fileTypes containsObject : ext ] )
2025-02-27 01:28:51 -03:00
continue ;
2025-05-10 00:04:35 -04:00
[ validURLs addObject : url ] ;
2022-02-07 02:49:27 -03:00
2025-02-27 01:28:51 -03:00
[ fileTask finish ] ;
}
2025-03-05 20:24:10 -03:00
@ catch ( NSException * e ) {
DLog ( @ "Exception caught while filtering paths: %@" , e ) ;
2025-03-09 19:03:31 -03:00
if ( e ) {
[ SentrySDK captureException : e ] ;
} else {
[ SentrySDK captureMessage : [ NSString stringWithFormat : @ "Null exception caught when filtering paths for URL: %@" , url ] ] ;
}
2025-02-27 01:28:51 -03:00
if ( fileTask ) {
[ fileTask finishWithStatus : kSentrySpanStatusInternalError ] ;
}
2007-05-27 12:14:29 -04:00
}
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self setProgressJobStatus : progress ] ;
2022-02-07 02:49:27 -03:00
}
2025-02-27 01:28:51 -03:00
[ filterTask finish ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = 0.0 ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
if ( [ fileURLs count ] > 0 ) {
[ self completeProgressJob ] ;
}
2025-02-27 01:28:51 -03:00
2013-10-11 09:03:55 -03:00
DLog ( @ "Valid urls: %@" , validURLs ) ;
2007-10-18 23:23:10 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = 0.0 ;
2022-02-07 02:49:27 -03:00
// Create actual entries
int count = ( int ) [ validURLs count ] ;
if ( xmlData ) count + = [ [ xmlData objectForKey : @ "entries" ] count ] ;
// no valid URLs , or they use an unsupported URL scheme
if ( ! count ) {
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self completeProgress ] ;
2022-02-07 02:49:27 -03:00
return @ [ ] ;
}
2022-06-18 19:10:18 -04:00
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionLoaderAddingEntries" , @ "" ) percentOfTotal : 20.0 ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progressstep = 100.0 / ( double ) ( count ) ;
2022-02-07 02:49:27 -03:00
2025-05-10 00:04:35 -04:00
__block id < SentrySpan > addTask = [ mainTask startChildWithOperation : @ "Add entries to playlist" description : [ NSString stringWithFormat : @ "Adding %lu entries to the playlist" , [ validURLs count ] ] ] ;
2025-02-27 01:28:51 -03:00
2021-04-29 21:16:24 -04:00
NSInteger i = 0 ;
2022-06-28 23:26:55 -04:00
__block NSMutableArray * entries = [ NSMutableArray arrayWithCapacity : count ] ;
2025-05-10 00:04:35 -04:00
for ( NSURL * url in validURLs ) {
2022-06-28 23:26:55 -04:00
__block PlaylistEntry * pe ;
2022-10-30 20:48:59 -03:00
2022-06-28 23:26:55 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
2025-02-27 01:28:51 -03:00
id < SentrySpan > addItemTask = nil ;
if ( addTask ) {
addItemTask = [ addTask startChildWithOperation : @ "Add individual item on main queue" description : [ NSString stringWithFormat : @ "Track URL: %@" , url ] ] ;
}
2022-06-28 23:26:55 -04:00
pe = [ NSEntityDescription insertNewObjectForEntityForName : @ "PlaylistEntry" inManagedObjectContext : self -> playlistController . persistentContainer . viewContext ] ;
pe . url = url ;
pe . index = index + i ;
pe . rawTitle = [ [ url path ] lastPathComponent ] ;
pe . queuePosition = -1 ;
2025-02-27 01:28:51 -03:00
[ addItemTask finish ] ;
2022-06-28 23:26:55 -04:00
} ) ;
2007-03-08 22:16:06 -03:00
[ entries addObject : pe ] ;
2022-02-07 02:49:27 -03:00
+ + i ;
progress + = progressstep ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self setProgressJobStatus : progress ] ;
2022-02-07 02:49:27 -03:00
}
2025-02-27 01:28:51 -03:00
[ addTask finish ] ;
2022-02-07 02:49:27 -03:00
NSInteger j = index + i ;
if ( xmlData ) {
for ( NSDictionary * entry in [ xmlData objectForKey : @ "entries" ] ) {
2022-06-28 23:26:55 -04:00
__block PlaylistEntry * pe ;
2022-10-30 20:48:59 -03:00
2022-06-28 23:26:55 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
pe = [ NSEntityDescription insertNewObjectForEntityForName : @ "PlaylistEntry" inManagedObjectContext : self -> playlistController . persistentContainer . viewContext ] ;
[ pe setValuesForKeysWithDictionary : entry ] ;
pe . index = index + i ;
pe . queuePosition = -1 ;
} ) ;
2022-02-07 02:49:27 -03:00
[ entries addObject : pe ] ;
+ + i ;
}
}
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self completeProgress ] ;
2022-02-07 02:49:27 -03:00
2007-03-08 22:16:06 -03:00
NSIndexSet * is = [ NSIndexSet indexSetWithIndexesInRange : NSMakeRange ( index , [ entries count ] ) ] ;
2022-02-07 02:49:27 -03:00
2022-06-28 23:26:55 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[ self -> playlistController insertObjects : entries atArrangedObjectIndexes : is ] ;
} ) ;
2013-10-10 05:43:04 -03:00
2022-02-07 02:49:27 -03:00
if ( xmlData && [ [ xmlData objectForKey : @ "queue" ] count ] ) {
2022-06-28 23:26:55 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[ self -> playlistController emptyQueueList : self ] ;
2022-02-07 02:49:27 -03:00
2022-06-28 23:26:55 -04:00
long i = 0 ;
for ( NSNumber * index in [ xmlData objectForKey : @ "queue" ] ) {
NSInteger indexVal = [ index intValue ] + j ;
PlaylistEntry * pe = [ entries objectAtIndex : indexVal ] ;
pe . queuePosition = i ;
pe . queued = YES ;
2022-02-07 02:49:27 -03:00
2022-06-28 23:26:55 -04:00
[ [ self -> playlistController queueList ] addObject : pe ] ;
2022-02-07 02:49:27 -03:00
2022-06-28 23:26:55 -04:00
+ + i ;
}
} ) ;
2022-02-07 02:49:27 -03:00
}
2025-02-27 01:28:51 -03:00
[ mainTask finish ] ;
2022-02-07 02:49:27 -03:00
// Clear the selection
2022-06-28 23:26:55 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[ self -> playlistController setSelectionIndexes : [ NSIndexSet indexSet ] ] ;
} ) ;
2022-02-07 02:49:27 -03:00
{
NSArray * arrayFirst = @ [ [ entries objectAtIndex : 0 ] ] ;
NSMutableArray * arrayRest = [ entries mutableCopy ] ;
[ arrayRest removeObjectAtIndex : 0 ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
metadataLoadInProgress = YES ;
2022-06-18 19:10:18 -04:00
[ self beginProgress : NSLocalizedString ( @ "ProgressActionLoadingMetadata" , @ "" ) ] ;
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionLoadingMetadata" , @ "" ) percentOfTotal : 50.0 ] ;
2022-02-07 02:49:27 -03:00
[ self performSelectorOnMainThread : @ selector ( syncLoadInfoForEntries : ) withObject : arrayFirst waitUntilDone : YES ] ;
progressstep = 100.0 / ( double ) ( [ entries count ] ) ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = progressstep ;
[ self setProgressJobStatus : progress ] ;
2022-02-07 02:49:27 -03:00
if ( [ arrayRest count ] )
[ self performSelectorInBackground : @ selector ( loadInfoForEntries : ) withObject : arrayRest ] ;
2022-06-16 10:14:33 -04:00
else {
2022-06-28 23:26:55 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[ self -> playlistController commitPersistentStore ] ;
} ) ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self completeProgress ] ;
2022-06-16 10:14:33 -04:00
}
2022-02-07 02:49:27 -03:00
return entries ;
}
2008-03-02 23:25:52 -03:00
}
2022-06-21 02:22:07 -04:00
NSURL * _Nullable urlForPath ( NSString * _Nullable path ) ;
2022-02-07 02:49:27 -03:00
- ( void ) loadInfoForEntries : ( NSArray * ) entries {
2022-06-21 02:22:07 -04:00
NSMutableDictionary * queueThisJob = [ [ NSMutableDictionary alloc ] init ] ;
for ( PlaylistEntry * pe in entries ) {
if ( ! pe || ! pe . urlString || pe . deLeted || pe . metadataLoaded ) continue ;
NSString * path = pe . urlString ;
NSMutableArray * entrySet = [ queueThisJob objectForKey : path ] ;
if ( ! entrySet ) {
entrySet = [ [ NSMutableArray alloc ] init ] ;
[ entrySet addObject : pe ] ;
[ queueThisJob setObject : entrySet forKey : path ] ;
} else {
[ entrySet addObject : pe ] ;
}
}
@ synchronized ( queuedURLs ) {
if ( [ queuedURLs count ] ) {
for ( NSString * key in [ queueThisJob allKeys ] ) {
if ( [ queuedURLs objectForKey : key ] ) {
[ queueThisJob removeObjectForKey : key ] ;
}
}
}
}
if ( ! [ queueThisJob count ] ) {
2022-06-22 22:05:27 -04:00
size_t count ;
@ synchronized ( queuedURLs ) {
count = [ queuedURLs count ] ;
}
if ( ! count ) {
[ playlistController performSelectorOnMainThread : @ selector ( updateTotalTime ) withObject : nil waitUntilDone : NO ] ;
[ self completeProgress ] ;
metadataLoadInProgress = NO ;
}
2022-06-21 02:22:07 -04:00
return ;
}
size_t count = 0 ;
do {
@ synchronized ( queuedURLs ) {
count = [ queuedURLs count ] ;
}
if ( count ) usleep ( 5000 ) ;
} while ( count > 0 ) ;
@ synchronized ( queuedURLs ) {
[ queuedURLs addEntriesFromDictionary : queueThisJob ] ;
}
2022-02-07 02:49:27 -03:00
NSMutableIndexSet * update_indexes = [ [ NSMutableIndexSet alloc ] init ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
__block double progress = 0.0 ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
double progressstep ;
2022-02-07 02:49:27 -03:00
2022-06-21 02:22:07 -04:00
if ( metadataLoadInProgress && [ queueThisJob count ] ) {
progressstep = 100.0 / ( double ) ( [ queueThisJob count ] + 1 ) ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = progressstep ;
2022-06-21 02:22:07 -04:00
} else if ( [ queueThisJob count ] ) {
2022-06-18 19:10:18 -04:00
[ self beginProgress : NSLocalizedString ( @ "ProgressActionLoadingMetadata" , @ "" ) ] ;
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionLoadingMetadata" , @ "" ) percentOfTotal : 50.0 ] ;
2022-02-07 02:49:27 -03:00
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progressstep = 100.0 / ( double ) ( [ entries count ] ) ;
progress = 0.0 ;
}
2022-02-07 02:49:27 -03:00
NSLock * outLock = [ [ NSLock alloc ] init ] ;
NSMutableArray * outArray = [ [ NSMutableArray alloc ] init ] ;
2022-02-17 02:38:43 -03:00
RedundantPlaylistDataStore * dataStore = [ [ RedundantPlaylistDataStore alloc ] init ] ;
2022-02-07 02:49:27 -03:00
__block NSLock * weakLock = outLock ;
__block NSMutableArray * weakArray = outArray ;
2022-02-17 02:38:43 -03:00
__block RedundantPlaylistDataStore * weakDataStore = dataStore ;
2022-02-07 02:49:27 -03:00
2022-06-21 02:22:07 -04:00
__block NSMutableDictionary * uniquePathsEntries = [ [ NSMutableDictionary alloc ] init ] ;
2022-02-07 02:49:27 -03:00
2025-02-27 01:28:51 -03:00
__block id < SentrySpan > mainTask = [ SentrySDK startTransactionWithName : @ "Loading tags" operation : @ "Main tag operation" ] ;
2022-06-21 02:22:07 -04:00
{
2025-03-09 19:05:03 -03:00
__block NSLock * blockLock = [ [ NSLock alloc ] init ] ;
2025-03-09 22:30:06 -03:00
__block NSMutableArray * blockInputs = [ [ queueThisJob allKeys ] mutableCopy ] ;
2025-03-09 19:05:03 -03:00
for ( size_t i = 0 , j = [ blockInputs count ] ; i < j ; + + i ) {
2022-02-07 02:49:27 -03:00
NSBlockOperation * op = [ [ NSBlockOperation alloc ] init ] ;
[ op addExecutionBlock : ^ {
2023-10-03 08:55:09 -03:00
@ autoreleasepool {
2025-03-09 19:05:03 -03:00
[ blockLock lock ] ;
NSString * key = [ blockInputs objectAtIndex : 0 ] ;
[ blockInputs removeObjectAtIndex : 0 ] ;
[ blockLock unlock ] ;
NSURL * url = urlForPath ( key ) ;
2025-02-27 01:28:51 -03:00
NSString * message = [ NSString stringWithFormat : @ "Loading metadata for %@" , url ] ;
DLog ( @ "%@" , message ) ;
id < SentrySpan > childTask = nil ;
if ( mainTask ) {
childTask = [ mainTask startChildWithOperation : @ "Load single tag" description : message ] ;
}
2022-02-07 02:49:27 -03:00
2025-02-27 01:28:51 -03:00
@ try {
NSDictionary * entryProperties = [ AudioPropertiesReader propertiesForURL : url ] ;
if ( entryProperties = = nil )
return ;
NSDictionary * entryMetadata = [ AudioMetadataReader metadataForURL : url ] ;
NSDictionary * entryInfo = [ NSDictionary dictionaryByMerging : entryProperties with : entryMetadata ] ;
[ weakLock lock ] ;
@ autoreleasepool {
entryInfo = [ weakDataStore coalesceEntryInfo : entryInfo ] ;
}
[ weakArray addObject : key ] ;
[ weakArray addObject : entryInfo ] ;
[ uniquePathsEntries setObject : [ [ NSMutableArray alloc ] init ] forKey : key ] ;
progress + = progressstep ;
[ self setProgressJobStatus : progress ] ;
[ weakLock unlock ] ;
if ( childTask ) {
[ childTask finish ] ;
}
}
2025-03-05 20:24:10 -03:00
@ catch ( NSException * e ) {
DLog ( @ "Exception thrown while reading tags: %@" , e ) ;
2025-03-09 19:05:03 -03:00
if ( e ) {
[ SentrySDK captureException : e ] ;
} else {
[ SentrySDK captureMessage : [ NSString stringWithFormat : @ "Null exception caught while reading tags for URL: %@" , url ] ] ;
}
2025-02-27 01:28:51 -03:00
if ( childTask ) {
[ childTask finishWithStatus : kSentrySpanStatusInternalError ] ;
}
2023-10-03 08:55:09 -03:00
}
}
2022-02-07 02:49:27 -03:00
} ] ;
[ queue addOperation : op ] ;
2022-06-21 02:22:07 -04:00
}
2022-02-07 02:49:27 -03:00
}
[ queue waitUntilAllOperationsAreFinished ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progress = 0.0 ;
[ self completeProgressJob ] ;
2022-06-18 19:10:18 -04:00
[ self beginProgressJob : NSLocalizedString ( @ "ProgressSubActionMetadataApply" , @ "" ) percentOfTotal : 50.0 ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
2025-02-27 01:28:51 -03:00
id < SentrySpan > finalTask = [ mainTask startChildWithOperation : @ "Apply tags to storage" ] ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
progressstep = 200.0 / ( double ) ( [ outArray count ] ) ;
2022-02-07 02:49:27 -03:00
2022-06-21 02:22:07 -04:00
NSManagedObjectContext * moc = playlistController . persistentContainer . viewContext ;
NSPredicate * hasUrlPredicate = [ NSPredicate predicateWithFormat : @ "urlString != nil && urlString != %@" , @ "" ] ;
NSPredicate * deletedPredicate = [ NSPredicate predicateWithFormat : @ "deLeted == NO || deLeted == nil" ] ;
NSCompoundPredicate * predicate = [ NSCompoundPredicate andPredicateWithSubpredicates : @ [ deletedPredicate , hasUrlPredicate ] ] ;
2022-10-30 20:48:59 -03:00
[ moc performBlockAndWait : ^ {
NSFetchRequest * request = [ NSFetchRequest fetchRequestWithEntityName : @ "PlaylistEntry" ] ;
request . predicate = predicate ;
2022-06-21 02:22:07 -04:00
2022-10-30 20:48:59 -03:00
NSError * error ;
NSArray * results = [ moc executeFetchRequest : request error : & error ] ;
2022-06-21 02:22:07 -04:00
2022-10-30 20:48:59 -03:00
if ( results && [ results count ] > 0 ) {
for ( PlaylistEntry * pe in results ) {
NSMutableArray * entrySet = [ uniquePathsEntries objectForKey : pe . urlString ] ;
if ( entrySet ) {
[ entrySet addObject : pe ] ;
}
2022-06-21 02:22:07 -04:00
}
}
2022-10-30 20:48:59 -03:00
} ] ;
2022-06-21 02:22:07 -04:00
for ( size_t i = 0 , j = [ outArray count ] ; i < j ; i + = 2 ) {
__block NSString * entryKey = [ outArray objectAtIndex : i ] ;
2022-02-07 02:49:27 -03:00
__block NSDictionary * entryInfo = [ outArray objectAtIndex : i + 1 ] ;
2022-06-21 02:22:07 -04:00
__block NSMutableIndexSet * weakUpdateIndexes = update_indexes ;
2022-06-25 05:40:05 -04:00
PlaylistController * playlistController = self -> playlistController ;
2022-02-07 02:49:27 -03:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
2022-06-21 02:22:07 -04:00
NSArray * entrySet = [ uniquePathsEntries objectForKey : entryKey ] ;
if ( entrySet ) {
2022-06-25 05:40:05 -04:00
if ( [ entrySet count ] > 0 ) {
[ playlistController firstSawTrack : entrySet [ 0 ] ] ;
}
2022-06-21 02:22:07 -04:00
for ( PlaylistEntry * pe in entrySet ) {
[ pe setMetadata : entryInfo ] ;
if ( pe . index >= 0 && pe . index < NSNotFound ) {
[ weakUpdateIndexes addIndex : pe . index ] ;
}
}
2022-06-15 00:27:51 -04:00
}
2022-06-21 02:22:07 -04:00
2022-02-07 02:49:27 -03:00
progress + = progressstep ;
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self setProgressJobStatus : progress ] ;
2022-02-07 02:49:27 -03:00
} ) ;
}
2022-06-22 22:11:06 -04:00
dispatch_sync _reentrant ( dispatch_get _main _queue ( ) , ^ {
[ self -> playlistController commitPersistentStore ] ;
} ) ;
2022-06-16 10:14:33 -04:00
2022-02-07 02:49:27 -03:00
[ 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 ) ] ] ;
} ) ;
}
2022-06-21 02:22:07 -04:00
@ synchronized ( queuedURLs ) {
[ queuedURLs removeObjectsForKeys : [ queueThisJob allKeys ] ] ;
}
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
[ self completeProgress ] ;
metadataLoadInProgress = NO ;
2025-02-27 01:28:51 -03:00
[ finalTask finish ] ;
[ mainTask finish ] ;
2021-05-07 20:19:10 -04:00
}
[Job Queue] Overhauled long action handling
Long actions, such as file opening, playlist loading, metadata loading
and refreshing, etc, are now handled through NSProgress. Additionally,
a new status bar change displays the progress of the task instead of
the total duration of the playlist. Finally, app quit is blocked by a
running task, and if the app is quit while a task is running, it will
be delayed until the task completes, at which time the app will
terminate cleanly.
Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-06-15 04:01:45 -04:00
2019-12-06 00:04:46 -03:00
// To be called on main thread only
2022-02-07 02:49:27 -03:00
- ( void ) syncLoadInfoForEntries : ( NSArray * ) entries {
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 ;
}
if ( ! i ) {
[ self -> playlistController updateTotalTime ] ;
return ;
}
2025-02-27 01:28:51 -03:00
__block id < SentrySpan > mainTask = [ SentrySDK startTransactionWithName : @ "Load tags synchronously" operation : @ "Main task" ] ;
2022-02-07 02:49:27 -03:00
[ load_info _indexes enumerateIndexesUsingBlock : ^ ( NSUInteger idx , BOOL * _Nonnull stop ) {
PlaylistEntry * pe = [ entries objectAtIndex : idx ] ;
2022-06-16 10:14:33 -04:00
DLog ( @ "Loading metadata for %@" , pe . url ) ;
2025-02-27 01:28:51 -03:00
id < SentrySpan > childTask = nil ;
if ( mainTask ) {
childTask = [ mainTask startChildWithOperation : @ "Load single tag" description : [ NSString stringWithFormat : @ "Loading tag for: %@" , pe . urlString ] ] ;
}
2022-02-07 02:49:27 -03:00
2025-02-27 01:28:51 -03:00
@ try {
NSDictionary * entryProperties = [ AudioPropertiesReader propertiesForURL : pe . url ] ;
if ( entryProperties = = nil )
return ;
NSDictionary * entryInfo = [ NSDictionary dictionaryByMerging : entryProperties with : [ AudioMetadataReader metadataForURL : pe . url ] ] ;
[ pe setMetadata : entryInfo ] ;
[ playlistController firstSawTrack : pe ] ;
if ( childTask ) {
[ childTask finish ] ;
}
}
2025-03-05 20:24:10 -03:00
@ catch ( NSException * e ) {
DLog ( @ "Exception thrown while reading tag synchronously: %@" , e ) ;
2025-03-09 19:03:31 -03:00
if ( e ) {
[ SentrySDK captureException : e ] ;
} else {
[ SentrySDK captureMessage : [ NSString stringWithFormat : @ "Null exception caught while reading tags for URL: %@" , pe . url ] ] ;
}
2025-02-27 01:28:51 -03:00
if ( childTask ) {
[ childTask finishWithStatus : kSentrySpanStatusInternalError ] ;
}
}
2022-02-07 02:49:27 -03:00
} ] ;
2025-02-27 01:28:51 -03:00
[ mainTask finish ] ;
2022-02-07 02:49:27 -03:00
[ self -> playlistController updateTotalTime ] ;
{
unsigned long columns = [ [ [ self -> playlistView documentView ] tableColumns ] count ] ;
[ self -> playlistView . documentView reloadDataForRowIndexes : update_indexes columnIndexes : [ NSIndexSet indexSetWithIndexesInRange : NSMakeRange ( 0 , columns ) ] ] ;
}
2019-12-06 00:04:46 -03:00
}
2022-02-07 02:49:27 -03:00
- ( void ) clear : ( id ) sender {
2009-08-16 12:49:34 -04:00
[ playlistController clear : sender ] ;
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) addURLs : ( NSArray * ) urls sort : ( BOOL ) sort {
2016-06-30 01:10:29 -04:00
return [ self insertURLs : urls atIndex : ( int ) [ [ playlistController content ] count ] sort : sort ] ;
2007-03-08 22:16:06 -03:00
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) addURL : ( NSURL * ) url {
2022-01-18 23:12:57 -03:00
return [ self insertURLs : @ [ url ] atIndex : ( int ) [ [ playlistController content ] count ] sort : NO ] ;
2007-05-27 12:14:29 -04:00
}
2022-06-16 10:14:33 -04:00
- ( BOOL ) addDataStore {
2022-07-08 09:26:28 -04:00
BOOL dataMigrated = [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "metadataMigrated" ] ;
2022-06-16 10:14:33 -04:00
NSPersistentContainer * pc = playlistController . persistentContainer ;
if ( pc ) {
NSManagedObjectContext * moc = pc . viewContext ;
NSFetchRequest * request = [ NSFetchRequest fetchRequestWithEntityName : @ "AlbumArtwork" ] ;
NSError * error = nil ;
NSArray * results = [ moc executeFetchRequest : request error : & error ] ;
if ( ! results ) {
ALog ( @ "Error fetching AlbumArtwork objects: %@\n%@" , [ error localizedDescription ] , [ error userInfo ] ) ;
abort ( ) ;
}
for ( AlbumArtwork * art in results ) {
2022-06-21 03:01:07 -04:00
[ kArtworkDictionary setObject : art forKey : art . artHash ] ;
2022-06-16 10:14:33 -04:00
}
request = [ NSFetchRequest fetchRequestWithEntityName : @ "PlaylistEntry" ] ;
2022-06-17 20:20:45 -04:00
NSSortDescriptor * sortDescriptor = [ [ NSSortDescriptor alloc ] initWithKey : @ "index" ascending : YES ] ;
request . sortDescriptors = @ [ sortDescriptor ] ;
2022-06-16 10:14:33 -04:00
results = [ moc executeFetchRequest : request error : & error ] ;
if ( ! results ) {
ALog ( @ "Error fetching PlaylistEntry objects: %@\n%@" , [ error localizedDescription ] , [ error userInfo ] ) ;
abort ( ) ;
}
2022-10-12 02:58:36 -03:00
if ( [ results count ] = = 0 ) {
return NO ;
}
2022-06-16 10:14:33 -04:00
NSMutableArray * resultsCopy = [ results mutableCopy ] ;
2022-10-12 02:58:36 -03:00
2022-06-16 10:14:33 -04:00
NSMutableIndexSet * pruneSet = [ [ NSMutableIndexSet alloc ] init ] ;
NSUInteger index = 0 ;
for ( PlaylistEntry * pe in resultsCopy ) {
2022-06-24 03:34:30 -04:00
if ( pe . deLeted || ! pe . urlString || [ pe . urlString length ] < 1 ) {
2022-06-16 10:14:33 -04:00
[ pruneSet addIndex : index ] ;
[ moc deleteObject : pe ] ;
}
+ + index ;
}
[ resultsCopy removeObjectsAtIndexes : pruneSet ] ;
2022-07-08 09:26:28 -04:00
if ( ! dataMigrated ) {
for ( PlaylistEntry * pe in resultsCopy ) {
pe . metadataLoaded = NO ;
}
}
if ( [ pruneSet count ] || ! dataMigrated ) {
2022-06-16 10:14:33 -04:00
[ playlistController commitPersistentStore ] ;
}
2022-07-08 09:26:28 -04:00
2022-06-24 03:34:30 -04:00
results = [ NSArray arrayWithArray : resultsCopy ] ;
2022-06-16 10:14:33 -04:00
{
NSIndexSet * is = [ NSIndexSet indexSetWithIndexesInRange : NSMakeRange ( 0 , [ results count ] ) ] ;
[ playlistController insertObjectsUnsynced : results atArrangedObjectIndexes : is ] ;
}
2022-06-19 05:05:26 -04:00
[ playlistController readQueueFromDataStore ] ;
[ playlistController readShuffleListFromDataStore ] ;
2022-06-16 10:14:33 -04:00
2022-07-15 09:12:57 -04:00
if ( ! dataMigrated && [ results count ] ) {
[ self performSelectorInBackground : @ selector ( loadInfoForEntries : ) withObject : results ] ;
}
2022-07-08 09:26:28 -04:00
[ [ NSUserDefaults standardUserDefaults ] setBool : YES forKey : @ "metadataMigrated" ] ;
2022-06-16 10:14:33 -04:00
return YES ;
}
return NO ;
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) addDatabase {
SQLiteStore * store = [ SQLiteStore sharedStore ] ;
2022-06-18 21:41:14 -04:00
int64_t count = [ store playlistGetCountCached ] ;
2022-02-07 02:49:27 -03:00
NSInteger i = 0 ;
NSMutableArray * entries = [ NSMutableArray arrayWithCapacity : count ] ;
for ( i = 0 ; i < count ; + + i ) {
PlaylistEntry * pe = [ store playlistGetCachedItem : i ] ;
pe . queuePosition = -1 ;
[ entries addObject : pe ] ;
}
NSIndexSet * is = [ NSIndexSet indexSetWithIndexesInRange : NSMakeRange ( 0 , [ entries count ] ) ] ;
[ playlistController insertObjectsUnsynced : entries atArrangedObjectIndexes : is ] ;
count = [ store queueGetCount ] ;
if ( count ) {
NSMutableIndexSet * refreshSet = [ [ NSMutableIndexSet alloc ] init ] ;
[ playlistController emptyQueueListUnsynced ] ;
for ( i = 0 ; i < count ; + + i ) {
NSInteger indexVal = [ store queueGetEntry : i ] ;
PlaylistEntry * pe = [ entries objectAtIndex : indexVal ] ;
pe . queuePosition = i ;
pe . queued = YES ;
[ [ playlistController queueList ] addObject : pe ] ;
[ refreshSet addIndex : [ pe index ] ] ;
}
// Refresh entire row to refresh tooltips
unsigned long columns = [ [ playlistView . documentView tableColumns ] count ] ;
[ playlistView . documentView reloadDataForRowIndexes : refreshSet columnIndexes : [ NSIndexSet indexSetWithIndexesInRange : NSMakeRange ( 0 , columns ) ] ] ;
}
// Clear the selection
[ playlistController setSelectionIndexes : [ NSIndexSet indexSet ] ] ;
if ( [ entries count ] ) {
[ self performSelectorInBackground : @ selector ( loadInfoForEntries : ) withObject : entries ] ;
}
return entries ;
2021-12-24 06:01:21 -03:00
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) acceptableFileTypes {
2007-10-08 21:20:46 -04:00
return [ [ self acceptableContainerTypes ] arrayByAddingObjectsFromArray : [ AudioPlayer fileTypes ] ] ;
2007-03-08 22:16:06 -03:00
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) acceptablePlaylistTypes {
2022-01-18 23:12:57 -03:00
return @ [ @ "m3u" , @ "pls" ] ;
2007-10-13 00:53:48 -04:00
}
2022-02-07 02:49:27 -03:00
- ( NSArray * ) acceptableContainerTypes {
2007-10-08 21:20:46 -04:00
return [ AudioPlayer containerTypes ] ;
2007-03-06 22:26:50 -03:00
}
2022-02-07 02:49:27 -03:00
- ( void ) willInsertURLs : ( NSArray * ) urls origin : ( URLOrigin ) origin {
2009-02-28 19:22:33 -03:00
[ playlistController willInsertURLs : urls origin : origin ] ;
2008-05-09 17:24:49 -04:00
}
2022-02-07 02:49:27 -03:00
- ( void ) didInsertURLs : ( NSArray * ) urls origin : ( URLOrigin ) origin {
2009-02-28 19:22:33 -03:00
[ playlistController didInsertURLs : urls origin : origin ] ;
2008-05-09 17:24:49 -04:00
}
2022-07-06 17:28:14 -04:00
- ( void ) addURLsInBackground : ( NSDictionary * ) input {
[ playlistController addURLsInBackground : input ] ;
}
- ( void ) insertURLsInBackground : ( NSDictionary * ) input {
[ playlistController insertURLsInBackground : input ] ;
}
2007-03-06 22:26:50 -03:00
@ end