- Retrieve profile paths properly instead of hard coding

- Display playlist total duration in units up to weeks and down to just seconds, and only pluralize units as necessary
- Major change: Implemented a SQLite disk backed playlist, track data, and queue storage system, which will be synchronized from the player in real time, and will hopefully survive system or app crashes. Existing plist playlist will be imported on first run, and removed on shutdown.
CQTexperiment
Christopher Snowhill 2021-12-24 01:01:21 -08:00
parent fd75e1b260
commit 2445cc94a9
12 changed files with 2206 additions and 50 deletions

View File

@ -150,11 +150,19 @@ void* kAppControllerContext = &kAppControllerContext;
(void) [spotlightWindowController init]; (void) [spotlightWindowController init];
[[playlistController undoManager] disableUndoRegistration]; [[playlistController undoManager] disableUndoRegistration];
NSString *basePath = [@"~/Library/Application Support/Cog/" stringByExpandingTildeInPath]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
NSString *dbFilename = @"Default.sqlite";
NSString *oldFilename = @"Default.m3u"; NSString *oldFilename = @"Default.m3u";
NSString *newFilename = @"Default.xml"; NSString *newFilename = @"Default.xml";
if ([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:newFilename]]) if ([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:dbFilename]])
{
[playlistLoader addDatabase];
}
else if ([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:newFilename]])
{ {
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:newFilename]]]; [playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:newFilename]]];
} }
@ -346,10 +354,9 @@ void* kAppControllerContext = &kAppControllerContext;
[playbackController stop:self]; [playbackController stop:self];
NSFileManager *fileManager = [NSFileManager defaultManager]; NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *folder = @"~/Library/Application Support/Cog/"; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *folder = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
folder = [folder stringByExpandingTildeInPath];
if ([fileManager fileExistsAtPath: folder] == NO) if ([fileManager fileExistsAtPath: folder] == NO)
{ {
[fileManager createDirectoryAtPath: folder withIntermediateDirectories:NO attributes:nil error:nil]; [fileManager createDirectoryAtPath: folder withIntermediateDirectories:NO attributes:nil error:nil];
@ -359,11 +366,11 @@ void* kAppControllerContext = &kAppControllerContext;
NSString * fileName = @"Default.xml"; NSString * fileName = @"Default.xml";
[playlistLoader saveXml:[folder stringByAppendingPathComponent: fileName]]; NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error];
fileName = @"Default.m3u"; fileName = @"Default.m3u";
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error]; [[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error];
DLog(@"Saving expanded nodes: %@", [expandedNodes description]); DLog(@"Saving expanded nodes: %@", [expandedNodes description]);

View File

@ -109,8 +109,11 @@ static PluginController *sharedPluginController = nil;
- (void)loadPlugins - (void)loadPlugins
{ {
[self loadPluginsAtPath:[[NSBundle mainBundle] builtInPlugInsPath]]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
[self loadPluginsAtPath:[@"~/Library/Application Support/Cog/Plugins" stringByExpandingTildeInPath]]; NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
[self loadPluginsAtPath:[[NSBundle mainBundle] builtInPlugInsPath]];
[self loadPluginsAtPath:[basePath stringByAppendingPathComponent:@"Plugins"]];
} }
- (void)setupContainer:(NSString *)className - (void)setupContainer:(NSString *)className

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19158" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19158"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<objects> <objects>

View File

@ -113,6 +113,8 @@
836D28A818086386005B7299 /* MiniModeMenuTitleTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 836D28A718086386005B7299 /* MiniModeMenuTitleTransformer.m */; }; 836D28A818086386005B7299 /* MiniModeMenuTitleTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 836D28A718086386005B7299 /* MiniModeMenuTitleTransformer.m */; };
836F5BF91A357A01002730CC /* sidplay.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8314D6411A354DFF00EEE8E6 /* sidplay.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 836F5BF91A357A01002730CC /* sidplay.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8314D6411A354DFF00EEE8E6 /* sidplay.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
836FB5A718206F2500B3AD2D /* Hively.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 836FB5471820538800B3AD2D /* Hively.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 836FB5A718206F2500B3AD2D /* Hively.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 836FB5471820538800B3AD2D /* Hively.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8370D73C277419F700245CE0 /* SQLiteStore.m */; };
8370D73F2775AE1300245CE0 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 8370D73E2775AE1300245CE0 /* libsqlite3.tbd */; };
8384914018083E4E00E7332D /* filetype.icns in Resources */ = {isa = PBXBuildFile; fileRef = 8384913D18083E4E00E7332D /* filetype.icns */; }; 8384914018083E4E00E7332D /* filetype.icns in Resources */ = {isa = PBXBuildFile; fileRef = 8384913D18083E4E00E7332D /* filetype.icns */; };
8384915918083EAB00E7332D /* infoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8384914318083EAB00E7332D /* infoTemplate.pdf */; }; 8384915918083EAB00E7332D /* infoTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 8384914318083EAB00E7332D /* infoTemplate.pdf */; };
8384915A18083EAB00E7332D /* missingArt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384914418083EAB00E7332D /* missingArt@2x.png */; }; 8384915A18083EAB00E7332D /* missingArt@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 8384914418083EAB00E7332D /* missingArt@2x.png */; };
@ -843,6 +845,9 @@
836D28A718086386005B7299 /* MiniModeMenuTitleTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MiniModeMenuTitleTransformer.m; path = Window/MiniModeMenuTitleTransformer.m; sourceTree = "<group>"; }; 836D28A718086386005B7299 /* MiniModeMenuTitleTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MiniModeMenuTitleTransformer.m; path = Window/MiniModeMenuTitleTransformer.m; sourceTree = "<group>"; };
836F6B2518BDB80D0095E648 /* vgmstream.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = vgmstream.xcodeproj; path = Plugins/vgmstream/vgmstream.xcodeproj; sourceTree = "<group>"; }; 836F6B2518BDB80D0095E648 /* vgmstream.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = vgmstream.xcodeproj; path = Plugins/vgmstream/vgmstream.xcodeproj; sourceTree = "<group>"; };
836FB5421820538700B3AD2D /* Hively.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Hively.xcodeproj; path = Plugins/Hively/Hively.xcodeproj; sourceTree = "<group>"; }; 836FB5421820538700B3AD2D /* Hively.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Hively.xcodeproj; path = Plugins/Hively/Hively.xcodeproj; sourceTree = "<group>"; };
8370D739277419D200245CE0 /* SQLiteStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQLiteStore.h; sourceTree = "<group>"; };
8370D73C277419F700245CE0 /* SQLiteStore.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SQLiteStore.m; sourceTree = "<group>"; };
8370D73E2775AE1300245CE0 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; };
8375B05117FFEA400092A79F /* OpusPlugin.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OpusPlugin.xcodeproj; path = Plugins/Opus/OpusPlugin.xcodeproj; sourceTree = "<group>"; }; 8375B05117FFEA400092A79F /* OpusPlugin.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OpusPlugin.xcodeproj; path = Plugins/Opus/OpusPlugin.xcodeproj; sourceTree = "<group>"; };
8384912518080F2D00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; }; 8384912518080F2D00E7332D /* Logging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Logging.h; sourceTree = "<group>"; };
8384913D18083E4E00E7332D /* filetype.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = filetype.icns; sourceTree = "<group>"; }; 8384913D18083E4E00E7332D /* filetype.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = filetype.icns; sourceTree = "<group>"; };
@ -940,6 +945,7 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
8370D73F2775AE1300245CE0 /* libsqlite3.tbd in Frameworks */,
ED69CBC725BE32C00090B90D /* MASShortcut.framework in Frameworks */, ED69CBC725BE32C00090B90D /* MASShortcut.framework in Frameworks */,
8355D6B8180613FB00D05687 /* Security.framework in Frameworks */, 8355D6B8180613FB00D05687 /* Security.framework in Frameworks */,
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
@ -1081,6 +1087,8 @@
177EC01B0B8BC2CF0000BC8C /* TrackingCell.m */, 177EC01B0B8BC2CF0000BC8C /* TrackingCell.m */,
177EC01C0B8BC2CF0000BC8C /* TrackingSlider.h */, 177EC01C0B8BC2CF0000BC8C /* TrackingSlider.h */,
177EC01D0B8BC2CF0000BC8C /* TrackingSlider.m */, 177EC01D0B8BC2CF0000BC8C /* TrackingSlider.m */,
8370D739277419D200245CE0 /* SQLiteStore.h */,
8370D73C277419F700245CE0 /* SQLiteStore.m */,
); );
path = Utils; path = Utils;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1381,6 +1389,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = { 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
8370D73E2775AE1300245CE0 /* libsqlite3.tbd */,
83AB9031237CEFD300A433D5 /* MediaPlayer.framework */, 83AB9031237CEFD300A433D5 /* MediaPlayer.framework */,
8355D6B7180613FB00D05687 /* Security.framework */, 8355D6B7180613FB00D05687 /* Security.framework */,
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
@ -2236,6 +2245,7 @@
179D03220E0CB2500064A77A /* FileNode.m in Sources */, 179D03220E0CB2500064A77A /* FileNode.m in Sources */,
179D03230E0CB2500064A77A /* FileTreeDataSource.m in Sources */, 179D03230E0CB2500064A77A /* FileTreeDataSource.m in Sources */,
179D03240E0CB2500064A77A /* FileTreeController.m in Sources */, 179D03240E0CB2500064A77A /* FileTreeController.m in Sources */,
8370D73D277419F700245CE0 /* SQLiteStore.m in Sources */,
179D03260E0CB2500064A77A /* PathNode.m in Sources */, 179D03260E0CB2500064A77A /* PathNode.m in Sources */,
179D03270E0CB2500064A77A /* PathWatcher.m in Sources */, 179D03270E0CB2500064A77A /* PathWatcher.m in Sources */,
179D03280E0CB2500064A77A /* SmartFolderNode.m in Sources */, 179D03280E0CB2500064A77A /* SmartFolderNode.m in Sources */,

View File

@ -106,6 +106,7 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
// queue methods // queue methods
- (IBAction)toggleQueued:(id)sender; - (IBAction)toggleQueued:(id)sender;
- (IBAction)emptyQueueList:(id)sender; - (IBAction)emptyQueueList:(id)sender;
- (void)emptyQueueListUnsynced;
- (NSMutableArray *)queueList; - (NSMutableArray *)queueList;
// reload metadata of selection // reload metadata of selection
@ -114,4 +115,6 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
- (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet - (void)moveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet
toIndex:(NSUInteger)insertIndex; toIndex:(NSUInteger)insertIndex;
- (void)insertObjectsUnsynced:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes;
@end @end

View File

@ -16,6 +16,7 @@
#import "SpotlightWindowController.h" #import "SpotlightWindowController.h"
#import "StatusImageTransformer.h" #import "StatusImageTransformer.h"
#import "ToggleQueueTitleTransformer.h" #import "ToggleQueueTitleTransformer.h"
#import "SQLiteStore.h"
#import "Logging.h" #import "Logging.h"
@ -119,10 +120,16 @@
- (void)updatePlaylistIndexes { - (void)updatePlaylistIndexes {
NSArray *arranged = [self arrangedObjects]; NSArray *arranged = [self arrangedObjects];
NSUInteger n = [arranged count]; NSUInteger n = [arranged count];
BOOL updated = NO;
for (NSUInteger i = 0; i < n; i++) { for (NSUInteger i = 0; i < n; i++) {
PlaylistEntry *pe = arranged[i]; PlaylistEntry *pe = arranged[i];
if (pe.index != i) // Make sure we don't get into some kind of crazy observing loop... if (pe.index != i) { // Make sure we don't get into some kind of crazy observing loop...
pe.index = (int) i; pe.index = i;
updated = YES;
}
}
if (updated) {
[[SQLiteStore sharedStore] syncPlaylistEntries:arranged];
} }
} }
@ -130,6 +137,7 @@
double tt = 0; double tt = 0;
ldiv_t hoursAndMinutes; ldiv_t hoursAndMinutes;
ldiv_t daysAndHours; ldiv_t daysAndHours;
ldiv_t weeksAndDays;
for (PlaylistEntry *pe in [self arrangedObjects]) { for (PlaylistEntry *pe in [self arrangedObjects]) {
if (!isnan([pe.length doubleValue])) tt += [pe.length doubleValue]; if (!isnan([pe.length doubleValue])) tt += [pe.length doubleValue];
@ -140,13 +148,52 @@
if (hoursAndMinutes.quot >= 24) { if (hoursAndMinutes.quot >= 24) {
daysAndHours = ldiv(hoursAndMinutes.quot, 24); daysAndHours = ldiv(hoursAndMinutes.quot, 24);
[self setTotalTime:[NSString stringWithFormat:@"%ld days %ld hours %ld minutes %ld seconds", if (daysAndHours.quot >= 7) {
daysAndHours.quot, daysAndHours.rem, weeksAndDays = ldiv(daysAndHours.quot, 7);
hoursAndMinutes.rem, sec % 60]]; [self setTotalTime:[NSString stringWithFormat:@"%ld week%@ %ld day%@ %ld hour%@ %ld minute%@ %ld second%@",
weeksAndDays.quot,
weeksAndDays.quot != 1 ? @"s" : @"",
weeksAndDays.rem,
weeksAndDays.rem != 1 ? @"s" : @"",
daysAndHours.rem,
daysAndHours.rem != 1 ? @"s" : @"",
hoursAndMinutes.rem,
hoursAndMinutes.rem != 1 ? @"s" : @"",
sec % 60,
(sec % 60) != 1 ? @"s" : @""]];
} else {
[self setTotalTime:[NSString stringWithFormat:@"%ld day%@ %ld hour%@ %ld minute%@ %ld second%@",
daysAndHours.quot,
daysAndHours.quot != 1 ? @"s" : @"",
daysAndHours.rem,
daysAndHours.rem != 1 ? @"s" : @"",
hoursAndMinutes.rem,
hoursAndMinutes.rem != 1 ? @"s" : @"",
sec % 60,
(sec % 60) != 1 ? @"s" : @""]];
}
} else { } else {
[self setTotalTime:[NSString stringWithFormat:@"%ld hours %ld minutes %ld seconds", if (hoursAndMinutes.quot > 0) {
hoursAndMinutes.quot, hoursAndMinutes.rem, [self setTotalTime:[NSString stringWithFormat:@"%ld hour%@ %ld minute%@ %ld second%@",
sec % 60]]; hoursAndMinutes.quot,
hoursAndMinutes.quot != 1 ? @"s" : @"",
hoursAndMinutes.rem,
hoursAndMinutes.rem != 1 ? @"s" : @"",
sec % 60,
(sec % 60) != 1 ? @"s" : @""]];
} else {
if (hoursAndMinutes.rem > 0) {
[self setTotalTime:[NSString stringWithFormat:@"%ld minute%@ %ld second%@",
hoursAndMinutes.rem,
hoursAndMinutes.rem != 1 ? @"s" : @"",
sec % 60,
(sec % 60) != 1 ? @"s" : @""]];
} else {
[self setTotalTime:[NSString stringWithFormat:@"%ld second%@",
sec,
sec != 1 ? @"s" : @""]];
}
}
} }
} }
@ -169,6 +216,10 @@
toIndex:(NSUInteger)insertIndex { toIndex:(NSUInteger)insertIndex {
[super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex]; [super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
#if 0 // syncPlaylistEntries is already called for rearrangement
[[SQLiteStore sharedStore] playlistMoveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
#endif
[playbackController playlistDidChange:self]; [playbackController playlistDidChange:self];
} }
@ -298,12 +349,9 @@
NSMutableIndexSet *disarrangedIndexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *disarrangedIndexes = [[NSMutableIndexSet alloc] init];
NSUInteger index = [indexes firstIndex]; [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
while (index != NSNotFound) { [disarrangedIndexes addIndex:[[self content] indexOfObject:[[self arrangedObjects] objectAtIndex:idx]]];
[disarrangedIndexes }];
addIndex:[[self content] indexOfObject:[[self arrangedObjects] objectAtIndex:index]]];
index = [indexes indexGreaterThanIndex:index];
}
return disarrangedIndexes; return disarrangedIndexes;
} }
@ -323,12 +371,9 @@
NSMutableIndexSet *rearrangedIndexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *rearrangedIndexes = [[NSMutableIndexSet alloc] init];
NSUInteger index = [indexes firstIndex]; [indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
while (index != NSNotFound) { [rearrangedIndexes addIndex:[[self arrangedObjects] indexOfObject:[[self content] objectAtIndex:idx]]];
[rearrangedIndexes }];
addIndex:[[self arrangedObjects] indexOfObject:[[self content] objectAtIndex:index]]];
index = [indexes indexGreaterThanIndex:index];
}
return rearrangedIndexes; return rearrangedIndexes;
} }
@ -338,15 +383,23 @@
[self rearrangeObjects]; [self rearrangeObjects];
} }
- (void)insertObjectsUnsynced:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes {
[super insertObjects:objects atArrangedObjectIndexes:indexes];
if ([self shuffle] != ShuffleOff) [self resetShuffleList];
}
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes { - (void)insertObjects:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes {
[[[self undoManager] prepareWithInvocationTarget:self] [[[self undoManager] prepareWithInvocationTarget:self]
removeObjectsAtIndexes:[self disarrangeIndexes:indexes]]; removeObjectsAtIndexes:[self disarrangeIndexes:indexes]];
NSString *actionName = NSString *actionName =
[NSString stringWithFormat:@"Adding %lu entries", (unsigned long) [objects count]]; [NSString stringWithFormat:@"Adding %lu entries", (unsigned long) [objects count]];
[[self undoManager] setActionName:actionName]; [[self undoManager] setActionName:actionName];
[[SQLiteStore sharedStore] playlistInsertTracks:objects atObjectIndexes:indexes];
[super insertObjects:objects atArrangedObjectIndexes:indexes]; [super insertObjects:objects atArrangedObjectIndexes:indexes];
if ([self shuffle] != ShuffleOff) [self resetShuffleList]; if ([self shuffle] != ShuffleOff) [self resetShuffleList];
} }
@ -389,6 +442,8 @@
} }
currentEntry.index = -i - 1; currentEntry.index = -i - 1;
} }
[[SQLiteStore sharedStore] playlistRemoveTracksAtIndexes:unarrangedIndexes];
[super removeObjectsAtArrangedObjectIndexes:indexes]; [super removeObjectsAtArrangedObjectIndexes:indexes];
@ -563,6 +618,7 @@
if ([queueList count] > 0) { if ([queueList count] > 0) {
pe = queueList[0]; pe = queueList[0];
[queueList removeObjectAtIndex:0]; [queueList removeObjectAtIndex:0];
[[SQLiteStore sharedStore] queueRemoveItem:0];
pe.queued = NO; pe.queued = NO;
[pe setQueuePosition:-1]; [pe setQueuePosition:-1];
@ -863,6 +919,11 @@
} }
- (IBAction)emptyQueueList:(id)sender { - (IBAction)emptyQueueList:(id)sender {
[self emptyQueueListUnsynced];
[[SQLiteStore sharedStore] queueEmpty];
}
- (void)emptyQueueListUnsynced {
for (PlaylistEntry *queueItem in queueList) { for (PlaylistEntry *queueItem in queueList) {
queueItem.queued = NO; queueItem.queued = NO;
[queueItem setQueuePosition:-1]; [queueItem setQueuePosition:-1];
@ -872,17 +933,23 @@
} }
- (IBAction)toggleQueued:(id)sender { - (IBAction)toggleQueued:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
for (PlaylistEntry *queueItem in [self selectedObjects]) { for (PlaylistEntry *queueItem in [self selectedObjects]) {
if (queueItem.queued) { if (queueItem.queued) {
queueItem.queued = NO; queueItem.queued = NO;
queueItem.queuePosition = -1; queueItem.queuePosition = -1;
[queueList removeObject:queueItem]; [queueList removeObject:queueItem];
[store queueRemovePlaylistItems:[NSArray arrayWithObject:queueItem]];
} else { } else {
queueItem.queued = YES; queueItem.queued = YES;
queueItem.queuePosition = (int) [queueList count]; queueItem.queuePosition = (int) [queueList count];
[queueList addObject:queueItem]; [queueList addObject:queueItem];
[store queueAddItem:[queueItem index]];
} }
DLog(@"TOGGLE QUEUED: %i", queueItem.queued); DLog(@"TOGGLE QUEUED: %i", queueItem.queued);
@ -895,11 +962,14 @@
} }
- (IBAction)removeFromQueue:(id)sender { - (IBAction)removeFromQueue:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
for (PlaylistEntry *queueItem in [self selectedObjects]) { for (PlaylistEntry *queueItem in [self selectedObjects]) {
queueItem.queued = NO; queueItem.queued = NO;
queueItem.queuePosition = -1; queueItem.queuePosition = -1;
[queueList removeObject:queueItem]; [queueList removeObject:queueItem];
[store queueRemovePlaylistItems:[NSArray arrayWithObject:queueItem]];
} }
int i = 0; int i = 0;
@ -909,11 +979,14 @@
} }
- (IBAction)addToQueue:(id)sender { - (IBAction)addToQueue:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
for (PlaylistEntry *queueItem in [self selectedObjects]) { for (PlaylistEntry *queueItem in [self selectedObjects]) {
queueItem.queued = YES; queueItem.queued = YES;
queueItem.queuePosition = (int) [queueList count]; queueItem.queuePosition = (int) [queueList count];
[queueList addObject:queueItem]; [queueList addObject:queueItem];
[store queueAddItem:[queueItem index]];
} }
int i = 0; int i = 0;

View File

@ -11,6 +11,7 @@
@interface PlaylistEntry : NSObject { @interface PlaylistEntry : NSObject {
NSInteger index; NSInteger index;
NSInteger shuffleIndex; NSInteger shuffleIndex;
NSInteger dbIndex;
BOOL current; BOOL current;
BOOL removed; BOOL removed;
@ -82,6 +83,7 @@
@property NSInteger index; @property NSInteger index;
@property NSInteger shuffleIndex; @property NSInteger shuffleIndex;
@property NSInteger dbIndex;
@property(readonly) NSString *status; @property(readonly) NSString *status;
@property(readonly) NSString *statusMessage; @property(readonly) NSString *statusMessage;

View File

@ -13,6 +13,7 @@
@synthesize index; @synthesize index;
@synthesize shuffleIndex; @synthesize shuffleIndex;
@synthesize dbIndex;
@synthesize current; @synthesize current;
@synthesize removed; @synthesize removed;

View File

@ -37,6 +37,8 @@ typedef enum {
- (NSArray*)addURL:(NSURL *)url; - (NSArray*)addURL:(NSURL *)url;
- (NSArray*)insertURLs:(NSArray *)urls atIndex:(NSInteger)index sort:(BOOL)sort; - (NSArray*)insertURLs:(NSArray *)urls atIndex:(NSInteger)index sort:(BOOL)sort;
- (NSArray*)addDatabase;
// Save playlist, auto-determines type based on extension. Uses m3u if it cannot be determined. // Save playlist, auto-determines type based on extension. Uses m3u if it cannot be determined.
- (BOOL)save:(NSString *)filename; - (BOOL)save:(NSString *)filename;
- (BOOL)save:(NSString *)filename asType:(PlaylistType)type; - (BOOL)save:(NSString *)filename asType:(PlaylistType)type;

View File

@ -28,6 +28,8 @@
#import "NSString+FinderCompare.h" #import "NSString+FinderCompare.h"
#import "SQLiteStore.h"
#import "Logging.h" #import "Logging.h"
@implementation PlaylistLoader @implementation PlaylistLoader
@ -438,7 +440,7 @@ NSMutableDictionary * dictionaryWithPropertiesOfObject(id obj, NSArray * filterL
++i; ++i;
} }
} }
NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, [entries count])]; NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, [entries count])];
[playlistController insertObjects:entries atArrangedObjectIndexes:is]; [playlistController insertObjects:entries atArrangedObjectIndexes:is];
@ -488,6 +490,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableIndexSet *update_indexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *update_indexes = [[NSMutableIndexSet alloc] init];
long i, j; long i, j;
NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init];
SQLiteStore *store = [SQLiteStore sharedStore];
i = 0; i = 0;
j = 0; j = 0;
@ -549,6 +553,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
__block NSDictionary *entryInfo = [outArray objectAtIndex:i + 1]; __block NSDictionary *entryInfo = [outArray objectAtIndex:i + 1];
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{ dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[weakPe setMetadata:entryInfo]; [weakPe setMetadata:entryInfo];
[store trackUpdate:weakPe];
}); });
} }
@ -559,7 +564,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
__block NSIndexSet *weakIndexSet = update_indexes; __block NSIndexSet *weakIndexSet = update_indexes;
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{ dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
unsigned long columns = [[[weakPlaylistView documentView] tableColumns] count]; unsigned long columns = [[[weakPlaylistView documentView] tableColumns] count];
[weakPlaylistView.documentView reloadDataForRowIndexes:weakIndexSet columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,columns-1)]]; [weakPlaylistView.documentView reloadDataForRowIndexes:weakIndexSet columnIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,columns)]];
}); });
} }
} }
@ -570,6 +575,8 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
long i, j; long i, j;
NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init];
SQLiteStore *store = [SQLiteStore sharedStore];
i = 0; i = 0;
j = 0; j = 0;
for (PlaylistEntry *pe in entries) for (PlaylistEntry *pe in entries)
@ -591,20 +598,21 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} }
[load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) [load_info_indexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop)
{ {
PlaylistEntry *pe = [entries objectAtIndex:idx]; PlaylistEntry *pe = [entries objectAtIndex:idx];
NSMutableDictionary *entryInfo = [NSMutableDictionary dictionaryWithCapacity:20]; NSMutableDictionary *entryInfo = [NSMutableDictionary dictionaryWithCapacity:20];
NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:pe.URL]; NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:pe.URL];
if (entryProperties == nil) if (entryProperties == nil)
return; return;
[entryInfo addEntriesFromDictionary:entryProperties]; [entryInfo addEntriesFromDictionary:entryProperties];
[entryInfo addEntriesFromDictionary:[AudioMetadataReader metadataForURL:pe.URL]]; [entryInfo addEntriesFromDictionary:[AudioMetadataReader metadataForURL:pe.URL]];
[pe setMetadata:entryInfo]; [pe setMetadata:entryInfo];
}]; [store trackUpdate:pe];
}];
[self->playlistController updateTotalTime]; [self->playlistController updateTotalTime];
@ -629,6 +637,58 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return [self insertURLs:[NSArray arrayWithObject:url] atIndex:(int)[[playlistController content] count] sort:NO]; return [self insertURLs:[NSArray arrayWithObject:url] atIndex:(int)[[playlistController content] count] sort:NO];
} }
- (NSArray*)addDatabase
{
SQLiteStore *store = [SQLiteStore sharedStore];
int64_t count = [store playlistGetCount];
NSInteger i = 0;
NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
for (i = 0; i < count; ++i)
{
PlaylistEntry *pe = [store playlistGetItem: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)
{
[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];
}
}
//Clear the selection
[playlistController setSelectionIndexes:[NSIndexSet indexSet]];
NSArray* arrayFirst = [NSArray arrayWithObject:[entries objectAtIndex:0]];
NSMutableArray* arrayRest = [entries mutableCopy];
[arrayRest removeObjectAtIndex:0];
[self performSelectorOnMainThread:@selector(syncLoadInfoForEntries:) withObject:arrayFirst waitUntilDone:YES];
if ([arrayRest count])
[self performSelectorInBackground:@selector(loadInfoForEntries:) withObject:arrayRest];
return entries;
}
- (NSArray *)acceptableFileTypes - (NSArray *)acceptableFileTypes
{ {
return [[self acceptableContainerTypes] arrayByAddingObjectsFromArray:[AudioPlayer fileTypes]]; return [[self acceptableContainerTypes] arrayByAddingObjectsFromArray:[AudioPlayer fileTypes]];

55
Utils/SQLiteStore.h Normal file
View File

@ -0,0 +1,55 @@
//
// NSTableViewDataSource+sqlite.h
// Cog
//
// Created by Christopher Snowhill on 12/22/21.
//
#ifndef NSTableViewDataSource_sqlite_h
#import <Cocoa/Cocoa.h>
#import <sqlite3.h>
#import "PlaylistEntry.h"
@interface SQLiteStore : NSObject
{
@private NSString *g_databasePath;
@private sqlite3 *g_database;
@private sqlite3_stmt *stmt[38];
}
@property (nonatomic, readwrite) NSString *databasePath;
@property (nonatomic, assign, readwrite) sqlite3 *database;
+ (SQLiteStore *)sharedStore;
- (id)init;
- (void)dealloc;
- (void) trackUpdate:(PlaylistEntry *)track;
- (void)playlistInsertTracks:(NSArray *)tracks atIndex:(int64_t)index;
- (void)playlistInsertTracks:(NSArray *)tracks atObjectIndexes:(NSIndexSet *)indexes;
- (void)playlistRemoveTracks:(int64_t)index forCount:(int64_t)count;
- (void)playlistRemoveTracksAtIndexes:(NSIndexSet *)indexes;
- (PlaylistEntry *)playlistGetItem:(int64_t)index;
- (int64_t)playlistGetCount;
#if 0
- (void)playlistMoveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex;
#endif
- (void)syncPlaylistEntries:(NSArray *)entries;
- (void)queueAddItem:(int64_t)playlistIndex;
- (void)queueAddItems:(NSArray *)playlistIndexes;
- (void)queueRemoveItem:(int64_t)queueIndex;
- (void)queueRemovePlaylistItems:(NSArray *)playlistIndexes;
- (int64_t)queueGetEntry:(int64_t)queueIndex;
- (int64_t)queueGetCount;
- (void)queueEmpty;
@end
#define NSTableViewDataSource_sqlite_h
#endif /* NSTableViewDataSource_sqlite_h */

1940
Utils/SQLiteStore.m Normal file

File diff suppressed because it is too large Load Diff