[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>
swiftingly
Christopher Snowhill 2022-06-15 01:01:45 -07:00
parent 6c7a3e581c
commit 92573ec088
14 changed files with 447 additions and 84 deletions

View File

@ -21,6 +21,8 @@
#import "ColorToValueTransformer.h"
#import "TotalTimeTransformer.h"
#import "Shortcuts.h"
#import <MASShortcut/Shortcut.h>
@ -53,8 +55,11 @@ void *kAppControllerContext = &kAppControllerContext;
NSValueTransformer *colorToValueTransformer = [[ColorToValueTransformer alloc] init];
[NSValueTransformer setValueTransformer:colorToValueTransformer
forName:@"ColorToValueTransformer"];
}
NSValueTransformer *totalTimeTransformer = [[TotalTimeTransformer alloc] init];
[NSValueTransformer setValueTransformer:totalTimeTransformer
forName:@"TotalTimeTransformer"];
}
- (id)init {
self = [super init];
if(self) {
@ -307,6 +312,12 @@ void *kAppControllerContext = &kAppControllerContext;
miniWindow.title = title;
mainWindow.title = title;
}
} else if([keyPath isEqualToString:@"finished"]) {
NSProgress *progress = (NSProgress *)object;
if([progress isFinished]) {
playbackController.progressOverall = nil;
[NSApp terminate:nil];
}
}
}
@ -324,6 +335,15 @@ void *kAppControllerContext = &kAppControllerContext;
[expandedNodes removeObject:url];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
if(playbackController.progressOverall) {
[playbackController.progressOverall addObserver:self forKeyPath:@"finished" options:0 context:kAppControllerContext];
return NSTerminateLater;
} else {
return NSTerminateNow;
}
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
CogStatus currentStatus = [playbackController playbackStatus];
NSInteger lastTrackPlaying = -1;

View File

@ -16,14 +16,22 @@ static NSString *DockIconPlaybackStatusObservationContext = @"DockIconPlaybackSt
- (void)startObserving {
[playbackController addObserver:self forKeyPath:@"playbackStatus" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
[playbackController addObserver:self forKeyPath:@"progressBarStatus" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
[playbackController addObserver:self forKeyPath:@"progressOverall" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld) context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.colorfulDockIcons" options:0 context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
}
- (void)stopObserving {
[playbackController removeObserver:self forKeyPath:@"playbackStatus"];
[playbackController removeObserver:self forKeyPath:@"progressBarStatus"];
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.colorfulDockIcons"];
[playbackController removeObserver:self forKeyPath:@"playbackStatus" context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
[playbackController removeObserver:self forKeyPath:@"progressOverall" context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.colorfulDockIcons" context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
}
- (void)startObservingProgress:(NSProgress *)progress {
[progress addObserver:self forKeyPath:@"fractionCompleted" options:0 context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
}
- (void)stopObservingProgress:(NSProgress *)progress {
[progress removeObserver:self forKeyPath:@"fractionCompleted" context:(__bridge void *_Nullable)(DockIconPlaybackStatusObservationContext)];
}
static NSString *getBadgeName(NSString *baseName, BOOL colorfulIcons) {
@ -151,12 +159,35 @@ static NSString *getBadgeName(NSString *baseName, BOOL colorfulIcons) {
NSInteger playbackStatus = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
[self refreshDockIcon:playbackStatus withProgress:-10];
} else if([keyPath isEqualToString:@"progressBarStatus"]) {
double progressStatus = [[change objectForKey:NSKeyValueChangeNewKey] doubleValue];
} else if([keyPath isEqualToString:@"progressOverall"]) {
double progressStatus = [lastProgressStatus doubleValue];
id objNew = [change objectForKey:NSKeyValueChangeNewKey];
id objOld = [change objectForKey:NSKeyValueChangeOldKey];
NSProgress *progressNew = nil, *progressOld = nil;
if(objNew && [objNew isKindOfClass:[NSProgress class]])
progressNew = (NSProgress *)objNew;
if(objOld && [objOld isKindOfClass:[NSProgress class]])
progressOld = (NSProgress *)objOld;
if(progressOld) {
[self stopObservingProgress:progressOld];
progressStatus = -1;
}
if(progressNew) {
[self startObservingProgress:progressNew];
progressStatus = progressNew.fractionCompleted * 100.0;
}
[self refreshDockIcon:-1 withProgress:progressStatus];
} else if([keyPath isEqualToString:@"values.colorfulDockIcons"]) {
[self refreshDockIcon:-1 withProgress:-10];
} else if([keyPath isEqualToString:@"fractionCompleted"]) {
double progressStatus = [(NSProgress *)object fractionCompleted];
[self refreshDockIcon:-1 withProgress:progressStatus * 100.0];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];

View File

@ -53,14 +53,16 @@ extern NSDictionary *makeRGInfo(PlaylistEntry *pe);
BOOL fading;
// progress bar display
double progressBarStatus;
NSProgress *progressOverall;
NSProgress *progressJob;
AudioUnit _eq;
}
@property CogStatus playbackStatus;
@property double progressBarStatus;
@property NSProgress *progressOverall;
@property NSProgress *progressJob;
- (IBAction)changeVolume:(id)sender;
- (IBAction)volumeDown:(id)sender;

View File

@ -30,7 +30,8 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation";
@synthesize playbackStatus;
@synthesize progressBarStatus;
@synthesize progressOverall;
@synthesize progressJob;
+ (NSSet *)keyPathsForValuesAffectingSeekable {
return [NSSet setWithObjects:@"playlistController.currentEntry", @"playlistController.currentEntry.seekable", nil];
@ -44,7 +45,8 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation";
seekable = NO;
fading = NO;
progressBarStatus = -1;
progressOverall = nil;
progressJob = nil;
audioPlayer = [[AudioPlayer alloc] init];
[audioPlayer setDelegate:self];

View File

@ -25,17 +25,17 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<splitView dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2123">
<rect key="frame" x="0.0" y="73" width="1000" height="327"/>
<rect key="frame" x="0.0" y="107" width="1000" height="293"/>
<subviews>
<scrollView fixedFrame="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="0.0" verticalLineScroll="24" verticalPageScroll="0.0" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="206" userLabel="Scroll View - Playlist View">
<rect key="frame" x="0.0" y="0.0" width="1000" height="344"/>
<rect key="frame" x="0.0" y="0.0" width="1000" height="293"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="KWC-Ti-8KY">
<rect key="frame" x="0.0" y="0.0" width="1000" height="344"/>
<rect key="frame" x="0.0" y="0.0" width="1000" height="293"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" autosaveName="Playlist" rowHeight="18" headerView="1517" viewBased="YES" id="207" customClass="PlaylistView">
<rect key="frame" x="0.0" y="0.0" width="1000" height="327"/>
<rect key="frame" x="0.0" y="0.0" width="1000" height="276"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="6"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -651,9 +651,15 @@
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="218" name="displayPatternValue1" keyPath="totalTime" id="1891">
<binding destination="218" name="displayPatternValue1" keyPath="totalTime" id="Kih-ky-A1h">
<dictionary key="options">
<string key="NSDisplayPattern">Total Duration: %{value1}@</string>
<string key="NSDisplayPattern">%{value1}@%{value2}@</string>
<string key="NSValueTransformerName">TotalTimeTransformer</string>
</dictionary>
</binding>
<binding destination="218" name="displayPatternValue2" keyPath="currentStatus" previousBinding="Kih-ky-A1h" id="5bQ-Qy-2KR">
<dictionary key="options">
<string key="NSDisplayPattern">%{value1}@%{value2}@</string>
</dictionary>
</binding>
</connections>

View File

@ -111,6 +111,7 @@
83489C6B2782F78700BDCEA2 /* libvgmPlayer.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 83489C542782F2DF00BDCEA2 /* libvgmPlayer.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8349270C27B4EFFC0009AB2B /* duplicateItemsTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8349270127B4EFFC0009AB2B /* duplicateItemsTemplate.pdf */; };
8349270D27B4EFFC0009AB2B /* deadItemsTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8349270B27B4EFFC0009AB2B /* deadItemsTemplate.pdf */; };
834B05EA2859C006000B7DC0 /* TotalTimeTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 834B05E92859C006000B7DC0 /* TotalTimeTransformer.m */; };
834D793F20E4EFEA00C4A5CC /* OpusPlugin.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 830B62B320E4EF89004A74B2 /* OpusPlugin.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
834D794020E4EFEF00C4A5CC /* VorbisPlugin.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8301F94520E4EEF70017B2DC /* VorbisPlugin.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
834F7F4320E4E4ED00228DAB /* AdPlug.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8303A30920E4E3D000951EF8 /* AdPlug.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@ -1004,6 +1005,8 @@
83489C4E2782F2DF00BDCEA2 /* libvgmPlayer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libvgmPlayer.xcodeproj; path = Plugins/libvgmPlayer/libvgmPlayer.xcodeproj; sourceTree = "<group>"; };
8349270127B4EFFC0009AB2B /* duplicateItemsTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = duplicateItemsTemplate.pdf; path = Images/duplicateItemsTemplate.pdf; sourceTree = "<group>"; };
8349270B27B4EFFC0009AB2B /* deadItemsTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; name = deadItemsTemplate.pdf; path = Images/deadItemsTemplate.pdf; sourceTree = "<group>"; };
834B05E82859C006000B7DC0 /* TotalTimeTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TotalTimeTransformer.h; path = Transformers/TotalTimeTransformer.h; sourceTree = "<group>"; };
834B05E92859C006000B7DC0 /* TotalTimeTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TotalTimeTransformer.m; path = Transformers/TotalTimeTransformer.m; sourceTree = "<group>"; };
8355D6B4180612F300D05687 /* NSData+MD5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+MD5.h"; sourceTree = "<group>"; };
8355D6B5180612F300D05687 /* NSData+MD5.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+MD5.m"; sourceTree = "<group>"; };
8355D6B7180613FB00D05687 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
@ -1539,6 +1542,8 @@
17E0D6140F520F87005B6FED /* StringToURLTransformer.h */,
17E0D6150F520F87005B6FED /* StringToURLTransformer.m */,
838F84FF25687C5C00C3E614 /* Cog-Bridging-Header.h */,
834B05E82859C006000B7DC0 /* TotalTimeTransformer.h */,
834B05E92859C006000B7DC0 /* TotalTimeTransformer.m */,
);
name = Transformers;
sourceTree = "<group>";
@ -2775,6 +2780,7 @@
17249F0F0D82E17700F33392 /* ToggleQueueTitleTransformer.m in Sources */,
179D031E0E0CB2500064A77A /* ContainedNode.m in Sources */,
179D031F0E0CB2500064A77A /* ContainerNode.m in Sources */,
834B05EA2859C006000B7DC0 /* TotalTimeTransformer.m in Sources */,
839DA7CF274A2D4C001B18E5 /* NSDictionary+Merge.m in Sources */,
179D03200E0CB2500064A77A /* DirectoryNode.m in Sources */,
179D03210E0CB2500064A77A /* FileIconCell.m in Sources */,

View File

@ -46,16 +46,20 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
NSMutableArray *queueList;
NSString *totalTime;
NSString *currentStatus;
PlaylistEntry *currentEntry;
PlaylistEntry *nextEntryAfterDeleted;
NSUndoManager *undoManager;
BOOL observersRegistered;
}
@property(nonatomic, retain) PlaylistEntry *_Nullable currentEntry;
@property(retain) NSString *_Nullable totalTime;
@property(retain) NSString *_Nullable currentStatus;
// Private Methods
- (void)updateTotalTime;

View File

@ -28,9 +28,12 @@
@synthesize currentEntry;
@synthesize totalTime;
@synthesize currentStatus;
static NSArray *cellIdentifiers = nil;
static void *playlistControllerContext = &playlistControllerContext;
+ (void)initialize {
cellIdentifiers = @[@"index", @"status", @"title", @"albumartist", @"artist",
@"album", @"length", @"year", @"genre", @"track", @"path",
@ -117,22 +120,93 @@ static NSArray *cellIdentifiers = nil;
[self addObserver:self
forKeyPath:@"arrangedObjects"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
context:nil];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fontSize" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:nil];
context:playlistControllerContext];
[playbackController addObserver:self
forKeyPath:@"progressOverall"
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionOld)
context:playlistControllerContext];
[[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fontSize" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial) context:playlistControllerContext];
observersRegistered = YES;
[self.tableView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
}
- (void)deinit {
if(observersRegistered) {
[self removeObserver:self forKeyPath:@"arrangedObjects" context:playlistControllerContext];
[playbackController removeObserver:self forKeyPath:@"progressOverall" context:playlistControllerContext];
[[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeyPath:@"values.fontSize" context:playlistControllerContext];
}
}
- (void)startObservingProgress:(NSProgress *)progress {
[progress addObserver:self forKeyPath:@"localizedDescription" options:0 context:playlistControllerContext];
[progress addObserver:self forKeyPath:@"fractionCompleted" options:0 context:playlistControllerContext];
}
- (void)stopObservingProgress:(NSProgress *)progress {
[progress removeObserver:self forKeyPath:@"localizedDescription" context:playlistControllerContext];
[progress removeObserver:self forKeyPath:@"fractionCompleted" context:playlistControllerContext];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if([keyPath isEqualToString:@"arrangedObjects"]) {
[self updatePlaylistIndexes];
if(context == playlistControllerContext) {
if([keyPath isEqualToString:@"arrangedObjects"]) {
[self updatePlaylistIndexes];
[self updateTotalTime];
[self.tableView reloadData];
} else if([keyPath isEqualToString:@"values.fontSize"]) {
[self updateRowSize];
} else if([keyPath isEqualToString:@"progressOverall"]) {
id objNew = [change objectForKey:NSKeyValueChangeNewKey];
id objOld = [change objectForKey:NSKeyValueChangeOldKey];
NSProgress *progressNew = nil, *progressOld = nil;
if(objNew && [objNew isKindOfClass:[NSProgress class]])
progressNew = (NSProgress *)objNew;
if(objOld && [objOld isKindOfClass:[NSProgress class]])
progressOld = (NSProgress *)objOld;
if(progressOld) {
[self stopObservingProgress:progressOld];
}
if(progressNew) {
[self startObservingProgress:progressNew];
}
[self updateProgressText];
} else if([keyPath isEqualToString:@"localizedDescription"] ||
[keyPath isEqualToString:@"fractionCompleted"]) {
[self updateProgressText];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
- (void)updateProgressText {
NSString *description = nil;
if(playbackController.progressOverall) {
if(playbackController.progressJob) {
description = [NSString stringWithFormat:@"%@ - %@", playbackController.progressOverall.localizedDescription, playbackController.progressJob.localizedDescription];
} else {
description = playbackController.progressOverall.localizedDescription;
}
}
if(description) {
[self setTotalTime:nil];
description = [description stringByAppendingFormat:@" - %.2f%% complete", playbackController.progressOverall.fractionCompleted * 100.0];
[self setCurrentStatus:description];
} else {
[self setCurrentStatus:nil];
[self updateTotalTime];
[self.tableView reloadData];
} else if([keyPath isEqualToString:@"values.fontSize"]) {
[self updateRowSize];
}
}
@ -144,13 +218,31 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
- (void)setProgressBarStatus:(double)status {
- (void)beginProgress:(NSString *)localizedDescription {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[self->playbackController setProgressBarStatus:status];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]];
self->playbackController.progressOverall = [NSProgress progressWithTotalUnitCount:100000];
self->playbackController.progressOverall.localizedDescription = localizedDescription;
});
}
- (void)completeProgress {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[self->playbackController.progressOverall setCompletedUnitCount:100000];
self->playbackController.progressOverall = 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)updatePlaylistIndexes {
NSArray *arranged = [self arrangedObjects];
NSUInteger n = [arranged count];
@ -163,10 +255,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
if(updated) {
[self beginProgress:NSLocalizedString(@"ProgressActionUpdatingIndexes", @"updating playlist indexes")];
[[SQLiteStore sharedStore] syncPlaylistEntries:arranged
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[self completeProgress];
}
}
@ -461,12 +555,16 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[super moveObjectsFromIndex:fromIndex toArrangedObjectIndexes:indexSet];
[self beginProgress:NSLocalizedString(@"ProgressActionMovingEntries", @"moving playlist entries")];
[[SQLiteStore sharedStore] playlistMoveObjectsFromIndex:fromIndex
toArrangedObjectIndexes:indexSet
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[self completeProgress];
[playbackController playlistDidChange:self];
}
@ -481,12 +579,16 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
[self beginProgress:NSLocalizedString(@"ProgressActionMovingEntries", @"moving playlist entries")];
[[SQLiteStore sharedStore] playlistMoveObjectsInArrangedObjectsFromIndexes:indexSet
toIndex:insertIndex
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[self completeProgress];
[playbackController playlistDidChange:self];
}
@ -669,14 +771,18 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
pe.deleted = NO;
}
[self beginProgress:NSLocalizedString(@"ProgressActionInsertingEntries", @"inserting playlist entries")];
[[SQLiteStore sharedStore] playlistInsertTracks:objects
atObjectIndexes:indexes
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[super insertObjects:objects atArrangedObjectIndexes:indexes];
[self completeProgress];
if([self shuffle] != ShuffleOff) [self resetShuffleList];
}
@ -696,14 +802,18 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
pe.trashURL = nil;
}
[self beginProgress:NSLocalizedString(@"ProgressActionUntrashingEntries", @"restoring playlist entries from the trash")];
[[SQLiteStore sharedStore] playlistInsertTracks:objects
atObjectIndexes:indexes
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[super insertObjects:objects atArrangedObjectIndexes:indexes];
[self completeProgress];
if([self shuffle] != ShuffleOff) [self resetShuffleList];
}
@ -759,15 +869,19 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
currentEntry.index = -i - 1;
}
[self beginProgress:NSLocalizedString(@"ProgressActionRemovingEntries", @"removing playlist entries")];
[[SQLiteStore sharedStore] playlistRemoveTracksAtIndexes:unarrangedIndexes
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[super removeObjectsAtArrangedObjectIndexes:indexes];
if([self shuffle] != ShuffleOff) [self resetShuffleList];
[self completeProgress];
[playbackController playlistDidChange:self];
}
@ -799,9 +913,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
[self beginProgress:NSLocalizedString(@"ProgressActionTrashingEntries", @"moving playlist entries to the trash")];
[[SQLiteStore sharedStore] playlistRemoveTracksAtIndexes:unarrangedIndexes
progressCall:^(double progress) {
[self setProgressBarStatus:progress];
[self setProgressStatus:progress];
}];
[super removeObjectsAtArrangedObjectIndexes:indexes];
@ -818,6 +934,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
pe.trashURL = removed;
}
}
[self completeProgress];
}
- (void)setSortDescriptors:(NSArray *)sortDescriptors {

View File

@ -26,6 +26,8 @@ typedef enum {
IBOutlet PlaybackController *playbackController;
NSOperationQueue *queue;
BOOL metadataLoadInProgress;
}
- (void)initDefaults;

View File

@ -261,12 +261,62 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
- (void)setProgressBarStatus:(double)status {
- (void)beginProgress:(NSString *)localizedDescription {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[self->playbackController setProgressBarStatus:status];
self->playbackController.progressOverall = [NSProgress progressWithTotalUnitCount:100000];
self->playbackController.progressOverall.localizedDescription = localizedDescription;
});
}
- (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];
}
}
- (NSArray *)insertURLs:(NSArray *)urls atIndex:(NSInteger)index sort:(BOOL)sort {
NSMutableSet *uniqueURLs = [NSMutableSet set];
@ -276,19 +326,23 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableArray *validURLs = [NSMutableArray array];
NSDictionary *xmlData = nil;
double progress = 0.0;
double progress;
if(!urls) {
[self setProgressBarStatus:-1];
[self completeProgress];
return @[];
}
[self beginProgress:NSLocalizedString(@"ProgressActionLoader", @"playlist loader inserting files")];
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderListingFiles", @"collecting files") percentOfTotal:20.0];
if(index < 0)
index = 0;
[self setProgressBarStatus:progress];
progress = 0.0;
double progressstep = [urls count] ? 20.0 / (double)([urls count]) : 0;
double progressstep = [urls count] ? 100.0 / (double)([urls count]) : 0;
NSURL *url;
for(url in urls) {
@ -309,12 +363,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
progress += progressstep;
[self setProgressBarStatus:progress];
[self setProgressJobStatus:progress];
}
progress = 20.0;
[self completeProgressJob];
[self setProgressBarStatus:progress];
progress = 0.0;
DLog(@"Expanded urls: %@", expandedURLs);
@ -326,7 +380,9 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
sortedURLs = expandedURLs;
}
progressstep = [sortedURLs count] ? 20.0 / (double)([sortedURLs count]) : 0;
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderFilteringContainerFiles", @"handling container file types") percentOfTotal:20.0];
progressstep = [sortedURLs count] ? 100.0 / (double)([sortedURLs count]) : 0;
for(url in sortedURLs) {
// Container vs non-container url
@ -349,17 +405,23 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
progress += progressstep;
[self setProgressBarStatus:progress];
[self setProgressJobStatus:progress];
}
progress = 40.0;
[self setProgressBarStatus:progress];
progress = 0.0;
[self completeProgressJob];
if([fileURLs count] > 0) {
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderFilteringFiles", @"eliminating unsupported file types") percentOfTotal:20.0];
} else {
[self setProgressStatus:60.0];
}
DLog(@"File urls: %@", fileURLs);
DLog(@"Contained urls: %@", containedURLs);
progressstep = [fileURLs count] ? 20.0 / (double)([fileURLs count]) : 0;
progressstep = [fileURLs count] ? 100.0 / (double)([fileURLs count]) : 0;
for(url in fileURLs) {
progress += progressstep;
@ -379,16 +441,24 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[uniqueURLs addObject:url];
}
[self setProgressBarStatus:progress];
[self setProgressJobStatus:progress];
}
progress = 60.0;
progress = 0.0;
[self setProgressBarStatus:progress];
if([fileURLs count] > 0) {
[self completeProgressJob];
}
if([containedURLs count] > 0) {
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderFilteringContainedFiles", @"eliminating unsupported file types from containers") percentOfTotal:20.0];
} else {
[self setProgressStatus:80.0];
}
DLog(@"Valid urls: %@", validURLs);
progressstep = [containedURLs count] ? 20.0 / (double)([containedURLs count]) : 0;
progressstep = [containedURLs count] ? 100.0 / (double)([containedURLs count]) : 0;
for(url in containedURLs) {
progress += progressstep;
@ -402,12 +472,13 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[validURLs addObject:url];
[self setProgressBarStatus:progress];
[self setProgressJobStatus:progress];
}
progress = 80.0;
[self setProgressBarStatus:progress];
progress = 0.0;
if([containedURLs count] > 0) {
[self completeProgressJob];
}
// Create actual entries
int count = (int)[validURLs count];
@ -415,11 +486,13 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
// no valid URLs, or they use an unsupported URL scheme
if(!count) {
[self setProgressBarStatus:-1];
[self completeProgress];
return @[];
}
progressstep = 20.0 / (double)(count);
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoaderAddingEntries", @"creating and adding playlist entries") percentOfTotal:20.0];
progressstep = 100.0 / (double)(count);
NSInteger i = 0;
NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
@ -436,7 +509,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
++i;
progress += progressstep;
[self setProgressBarStatus:progress];
[self setProgressJobStatus:progress];
}
NSInteger j = index + i;
@ -455,8 +528,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
progress = 100.0;
[self setProgressBarStatus:progress];
[self completeProgress];
NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, [entries count])];
@ -486,19 +558,21 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableArray *arrayRest = [entries mutableCopy];
[arrayRest removeObjectAtIndex:0];
progress = 0.0;
[self setProgressBarStatus:progress];
metadataLoadInProgress = YES;
[self beginProgress:NSLocalizedString(@"ProgressActionLoadingMetadata", @"loading metadata for tracks")];
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoadingMetadata", @"processing files") percentOfTotal:50.0];
[self performSelectorOnMainThread:@selector(syncLoadInfoForEntries:) withObject:arrayFirst waitUntilDone:YES];
progressstep = 100.0 / (double)([entries count]);
progress += progressstep;
[self setProgressBarStatus:progress];
progress = progressstep;
[self setProgressJobStatus:progress];
if([arrayRest count])
[self performSelectorInBackground:@selector(loadInfoForEntries:) withObject:arrayRest];
else
[self setProgressBarStatus:-1];
[self completeProgress];
return entries;
}
}
@ -510,17 +584,20 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
SQLiteStore *store = [SQLiteStore sharedStore];
__block double progress = [playbackController progressBarStatus];
__block double progress = 0.0;
if(progress < 0 || progress >= 100)
progress = 0;
double progressstep;
double progressRemaining = 100.0 - progress;
if(metadataLoadInProgress && [entries count]) {
progressstep = 100.0 / (double)([entries count] + 1);
progress = progressstep;
} else if([entries count]) {
[self beginProgress:NSLocalizedString(@"ProgressActionLoadingMetadata", @"loading metadata for tracks")];
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionLoadingMetadata", @"processing files") percentOfTotal:50.0];
// 50% for properties reading, 50% for applying them to the main thread
const double progressstep = [entries count] ? (progressRemaining / 2.0) / [entries count] : 0;
progressRemaining = progress + (progressRemaining / 2.0);
progressstep = 100.0 / (double)([entries count]);
progress = 0.0;
}
i = 0;
j = 0;
@ -537,6 +614,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(!i) {
[playlistController performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO];
[self completeProgress];
metadataLoadInProgress = NO;
return;
}
@ -555,11 +634,13 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSBlockOperation *op = [[NSBlockOperation alloc] init];
[op addExecutionBlock:^{
[weakLock lock];
progress += progressstep;
[weakLock unlock];
if(weakPe.deleted) return;
if(weakPe.deleted) {
[weakLock lock];
progress += progressstep;
[self setProgressJobStatus:progress];
[weakLock unlock];
return;
}
DLog(@"Loading metadata for %@", weakPe.URL);
@ -575,7 +656,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
entryInfo = [weakDataStore coalesceEntryInfo:entryInfo];
[weakArray addObject:weakPe];
[weakArray addObject:entryInfo];
[self setProgressBarStatus:progress];
progress += progressstep;
[self setProgressJobStatus:progress];
[weakLock unlock];
}];
@ -585,8 +667,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[queue waitUntilAllOperationsAreFinished];
progress = progressRemaining;
[self setProgressBarStatus:progress];
progress = 0.0;
[self completeProgressJob];
[self beginProgressJob:NSLocalizedString(@"ProgressSubActionMetadataApply", @"applying info to playlist storage") percentOfTotal:50.0];
progressstep = 200.0 / (double)([outArray count]);
for(i = 0, j = [outArray count]; i < j; i += 2) {
__block PlaylistEntry *weakPe = [outArray objectAtIndex:i];
@ -597,7 +683,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[store trackUpdate:weakPe];
}
progress += progressstep;
[self setProgressBarStatus:progress];
[self setProgressJobStatus:progress];
});
}
@ -612,8 +698,10 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
});
}
[self setProgressBarStatus:-1];
[self completeProgress];
metadataLoadInProgress = NO;
}
// To be called on main thread only
- (void)syncLoadInfoForEntries:(NSArray *)entries {
NSMutableIndexSet *update_indexes = [[NSMutableIndexSet alloc] init];

View File

@ -0,0 +1,16 @@
//
// TotalTimeTransformer.h
// Cog
//
// Created by Christopher Snowhill on 6/15/22.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface TotalTimeTransformer : NSValueTransformer
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,32 @@
//
// TotalTimeTransformer.m
// Cog
//
// Created by Christopher Snowhill on 6/15/22.
//
#import "TotalTimeTransformer.h"
@implementation TotalTimeTransformer
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return NO;
}
- (id)transformedValue:(id)value {
if(value == nil) return @"";
if([value isKindOfClass:[NSString class]]) {
NSString *string = (NSString *)value;
if([string length] > 0) {
return [@"Total Duration: " stringByAppendingString:string];
}
}
return @"";
}
@end

View File

@ -45,3 +45,21 @@
"CogTitle" = "Cog";
"PreferencesTitle" = "Preferences";
"ProgressActionUpdatingIndexes" = "updating playlist indexes";
"ProgressActionMovingEntries" = "moving playlist entries";
"ProgressActionInsertingEntries" = "inserting playlist entries";
"ProgressActionUntrashingEntries" = "restoring playlist entries from the trash";
"ProgressActionRemovingEntries" = "removing playlist entries";
"ProgressActionTrashingEntries" = "moving playlist entries to the trash";
"ProgressActionLoader" = "playlist loader inserting files";
"ProgressSubActionLoaderListingFiles" = "collecting files";
"ProgressSubActionLoaderFilteringContainerFiles" = "handling container file types";
"ProgressSubActionLoaderFilteringFiles" = "eliminating unsupported file types";
"ProgressSubActionLoaderFilteringContainedFiles" = "eliminating unsupported file types from containers";
"ProgressSubActionLoaderAddingEntries" = "creating and adding playlist entries";
"ProgressActionLoadingMetadata" = "loading metadata for tracks";
"ProgressSubActionLoadingMetadata" = "processing files";
"ProgressSubActionMetadataApply" = "applying info to playlist storage";

View File

@ -45,3 +45,21 @@
"CogTitle" = "Cog";
"PreferencesTitle" = "Preferences";
"ProgressActionUpdatingIndexes" = "updating playlist indexes";
"ProgressActionMovingEntries" = "moving playlist entries";
"ProgressActionInsertingEntries" = "inserting playlist entries";
"ProgressActionUntrashingEntries" = "restoring playlist entries from the trash";
"ProgressActionRemovingEntries" = "removing playlist entries";
"ProgressActionTrashingEntries" = "moving playlist entries to the trash";
"ProgressActionLoader" = "playlist loader inserting files";
"ProgressSubActionLoaderListingFiles" = "collecting files";
"ProgressSubActionLoaderFilteringContainerFiles" = "handling container file types";
"ProgressSubActionLoaderFilteringFiles" = "eliminating unsupported file types";
"ProgressSubActionLoaderFilteringContainedFiles" = "eliminating unsupported file types from containers";
"ProgressSubActionLoaderAddingEntries" = "creating and adding playlist entries";
"ProgressActionLoadingMetadata" = "loading metadata for tracks";
"ProgressSubActionLoadingMetadata" = "processing files";
"ProgressSubActionMetadataApply" = "applying info to playlist storage";