[Playlist Storage] Rewrite to use Core Data

Completely rewrite the playlist storage once again, this time with a
much faster Core Data implementation. It still uses a little magic for
Album Artwork consolidation, but string consolidation doesn't seem to be
needed to reduce the disk storage size. Works much faster than my silly
implementation, too.

Old implementations are still kept for backwards compatibility with
existing playlists.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
swiftingly
Christopher Snowhill 2022-06-16 07:14:33 -07:00
parent 0997ca2c93
commit 4c95c943ef
19 changed files with 683 additions and 776 deletions

View File

@ -11,6 +11,7 @@
#import "PlaylistEntry.h" #import "PlaylistEntry.h"
#import "PlaylistLoader.h" #import "PlaylistLoader.h"
#import "PlaylistView.h" #import "PlaylistView.h"
#import "SQLiteStore.h"
#import "SpotlightWindowController.h" #import "SpotlightWindowController.h"
#import "StringToURLTransformer.h" #import "StringToURLTransformer.h"
#import <CogAudio/Status.h> #import <CogAudio/Status.h>
@ -167,12 +168,16 @@ void *kAppControllerContext = &kAppControllerContext;
NSString *oldFilename = @"Default.m3u"; NSString *oldFilename = @"Default.m3u";
NSString *newFilename = @"Default.xml"; NSString *newFilename = @"Default.xml";
if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:dbFilename]]) { BOOL dataStorePresent = [playlistLoader addDataStore];
[playlistLoader addDatabase];
} else if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:newFilename]]) { if(!dataStorePresent) {
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:newFilename]]]; if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:dbFilename]]) {
} else { [playlistLoader addDatabase];
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:oldFilename]]]; } else if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:newFilename]]) {
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:newFilename]]];
} else {
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:oldFilename]]];
}
} }
[[playlistController undoManager] enableUndoRegistration]; [[playlistController undoManager] enableUndoRegistration];
@ -375,9 +380,37 @@ void *kAppControllerContext = &kAppControllerContext;
[playlistController clearFilterPredicate:self]; [playlistController clearFilterPredicate:self];
NSString *fileName = @"Default.xml"; NSMutableDictionary<NSString *, AlbumArtwork *> *artLeftovers = [playlistController.persistentArtStorage mutableCopy];
NSManagedObjectContext *moc = playlistController.persistentContainer.viewContext;
for(PlaylistEntry *pe in playlistController.arrangedObjects) {
if(pe.deLeted) {
[moc deleteObject:pe];
continue;
}
if([artLeftovers objectForKey:pe.artHash]) {
[artLeftovers removeObjectForKey:pe.artHash];
}
}
for(NSString *key in artLeftovers) {
[moc deleteObject:[artLeftovers objectForKey:key]];
}
[playlistController commitPersistentStore];
if([SQLiteStore databaseStarted]) {
[[SQLiteStore sharedStore] shutdown];
}
NSError *error; NSError *error;
NSString *fileName = @"Default.sqlite";
[[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error];
fileName = @"Default.xml";
[[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error]; [[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error];
fileName = @"Default.m3u"; fileName = @"Default.m3u";

View File

@ -160,16 +160,16 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation";
NSDictionary *makeRGInfo(PlaylistEntry *pe) { NSDictionary *makeRGInfo(PlaylistEntry *pe) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary]; NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
if([pe replayGainAlbumGain] != 0) if(pe.replayGainAlbumGain != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainAlbumGain]] forKey:@"replayGainAlbumGain"]; [dictionary setObject:[NSNumber numberWithFloat:pe.replayGainAlbumGain] forKey:@"replayGainAlbumGain"];
if([pe replayGainAlbumPeak] != 0) if(pe.replayGainAlbumPeak != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainAlbumPeak]] forKey:@"replayGainAlbumPeak"]; [dictionary setObject:[NSNumber numberWithFloat:pe.replayGainAlbumPeak] forKey:@"replayGainAlbumPeak"];
if([pe replayGainTrackGain] != 0) if(pe.replayGainTrackGain != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainTrackGain]] forKey:@"replayGainTrackGain"]; [dictionary setObject:[NSNumber numberWithFloat:pe.replayGainTrackGain] forKey:@"replayGainTrackGain"];
if([pe replayGainTrackPeak] != 0) if(pe.replayGainTrackPeak != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainTrackPeak]] forKey:@"replayGainTrackPeak"]; [dictionary setObject:[NSNumber numberWithFloat:pe.replayGainTrackPeak] forKey:@"replayGainTrackPeak"];
if([pe volume] != 1) if(pe.volume != 1)
[dictionary setObject:[NSNumber numberWithFloat:[pe volume]] forKey:@"volume"]; [dictionary setObject:[NSNumber numberWithFloat:pe.volume] forKey:@"volume"];
return dictionary; return dictionary;
} }
@ -196,7 +196,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
return; return;
BOOL loadData = YES; BOOL loadData = YES;
NSString *urlScheme = [[pe URL] scheme]; NSString *urlScheme = [pe.url scheme];
if([urlScheme isEqualToString:@"http"] || if([urlScheme isEqualToString:@"http"] ||
[urlScheme isEqualToString:@"https"]) [urlScheme isEqualToString:@"https"])
loadData = NO; loadData = NO;
@ -222,9 +222,9 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
[self sendMetaData]; [self sendMetaData];
double seekTime = [pe seekable] ? [offset doubleValue] : 0.0; double seekTime = pe.seekable ? [offset doubleValue] : 0.0;
[audioPlayer play:[pe URL] withUserInfo:pe withRGInfo:makeRGInfo(pe) startPaused:paused andSeekTo:seekTime]; [audioPlayer play:pe.url withUserInfo:pe withRGInfo:makeRGInfo(pe) startPaused:paused andSeekTo:seekTime];
} }
- (IBAction)next:(id)sender { - (IBAction)next:(id)sender {
@ -574,7 +574,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
} }
if(pe) if(pe)
[player setNextStream:[pe URL] withUserInfo:pe withRGInfo:makeRGInfo(pe)]; [player setNextStream:pe.url withUserInfo:pe withRGInfo:makeRGInfo(pe)];
else else
[player setNextStream:nil]; [player setNextStream:nil];
} }
@ -674,7 +674,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
- (void)audioPlayer:(AudioPlayer *)player restartPlaybackAtCurrentPosition:(id)userInfo { - (void)audioPlayer:(AudioPlayer *)player restartPlaybackAtCurrentPosition:(id)userInfo {
PlaylistEntry *pe = [playlistController currentEntry]; PlaylistEntry *pe = [playlistController currentEntry];
BOOL paused = playbackStatus == CogStatusPaused; BOOL paused = playbackStatus == CogStatusPaused;
[player play:[pe URL] withUserInfo:pe withRGInfo:makeRGInfo(pe) startPaused:paused andSeekTo:[pe seekable] ? [pe currentPosition] : 0.0]; [player play:pe.url withUserInfo:pe withRGInfo:makeRGInfo(pe) startPaused:paused andSeekTo:pe.seekable ? pe.currentPosition : 0.0];
} }
- (void)audioPlayer:(AudioPlayer *)player pushInfo:(NSDictionary *)info toTrack:(id)userInfo { - (void)audioPlayer:(AudioPlayer *)player pushInfo:(NSDictionary *)info toTrack:(id)userInfo {
@ -768,7 +768,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
if([entry year]) { if([entry year]) {
// If PlaylistEntry can represent a full date like some tag formats can do, change it // If PlaylistEntry can represent a full date like some tag formats can do, change it
NSCalendar *calendar = [NSCalendar currentCalendar]; NSCalendar *calendar = [NSCalendar currentCalendar];
NSDate *releaseYear = [calendar dateWithEra:1 year:[[entry year] intValue] month:0 day:0 hour:0 minute:0 second:0 nanosecond:0]; NSDate *releaseYear = [calendar dateWithEra:1 year:entry.year month:0 day:0 hour:0 minute:0 second:0 nanosecond:0];
[songInfo setObject:releaseYear forKey:MPMediaItemPropertyReleaseDate]; [songInfo setObject:releaseYear forKey:MPMediaItemPropertyReleaseDate];
} }
[songInfo setObject:[NSNumber numberWithFloat:[entry currentPosition]] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; [songInfo setObject:[NSNumber numberWithFloat:[entry currentPosition]] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];

View File

@ -10,7 +10,6 @@
#import <UserNotifications/UserNotifications.h> #import <UserNotifications/UserNotifications.h>
#import "PlaybackController.h" #import "PlaybackController.h"
#import "PlaylistEntry.h"
@class AudioScrobbler; @class AudioScrobbler;

View File

@ -9,6 +9,8 @@
#import "AudioScrobbler.h" #import "AudioScrobbler.h"
#import "PlaylistEntry.h"
NSString *TrackNotification = @"com.apple.iTunes.playerInfo"; NSString *TrackNotification = @"com.apple.iTunes.playerInfo";
NSString *TrackArtist = @"Artist"; NSString *TrackArtist = @"Artist";
@ -112,17 +114,17 @@ didReceiveNotificationResponse:(UNNotificationResponse *)response
- (NSDictionary *)fillNotificationDictionary:(PlaylistEntry *)pe status:(TrackStatus)status { - (NSDictionary *)fillNotificationDictionary:(PlaylistEntry *)pe status:(TrackStatus)status {
NSMutableDictionary *dict = [NSMutableDictionary dictionary]; NSMutableDictionary *dict = [NSMutableDictionary dictionary];
if(pe == nil || pe.deleted || [pe URL] == nil) return dict; if(pe == nil || pe.deLeted || pe.url == nil) return dict;
[dict setObject:[[pe URL] absoluteString] forKey:TrackPath]; [dict setObject:[pe.url absoluteString] forKey:TrackPath];
if([pe title]) [dict setObject:[pe title] forKey:TrackTitle]; if(pe.title) [dict setObject:pe.title forKey:TrackTitle];
if([pe artist]) [dict setObject:[pe artist] forKey:TrackArtist]; if(pe.artist) [dict setObject:pe.artist forKey:TrackArtist];
if([pe album]) [dict setObject:[pe album] forKey:TrackAlbum]; if(pe.album) [dict setObject:pe.album forKey:TrackAlbum];
if([pe genre]) [dict setObject:[pe genre] forKey:TrackGenre]; if(pe.genre) [dict setObject:pe.genre forKey:TrackGenre];
if([pe track]) if(pe.track)
[dict setObject:[pe trackText] forKey:TrackNumber]; [dict setObject:pe.trackText forKey:TrackNumber];
if([pe length]) if(pe.length)
[dict setObject:[NSNumber numberWithInteger:(NSInteger)([[pe length] doubleValue] * 1000.0)] [dict setObject:[NSNumber numberWithInteger:(NSInteger)([pe.length doubleValue] * 1000.0)]
forKey:TrackLength]; forKey:TrackLength];
NSString *state = nil; NSString *state = nil;

View File

@ -123,7 +123,7 @@ escapeForLastFM(NSString *string) {
escapeForLastFM([pe album]), escapeForLastFM([pe album]),
@"", // TODO: MusicBrainz support @"", // TODO: MusicBrainz support
[[pe length] intValue], [[pe length] intValue],
escapeForLastFM([[pe URL] path])]]; escapeForLastFM([pe.url path])]];
} }
- (void)stop { - (void)stop {

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="19529" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="20037" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19529"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="20037"/>
<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>
@ -130,9 +130,9 @@ DQ
</textFieldCell> </textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<connections> <connections>
<binding destination="16" name="value" keyPath="arrangedObjects.length" id="105"> <binding destination="16" name="value" keyPath="arrangedObjects.lengthText" id="K4A-L7-jOn">
<dictionary key="options"> <dictionary key="options">
<integer key="NSConditionallySetsEditable" value="1"/> <bool key="NSConditionallySetsEditable" value="YES"/>
</dictionary> </dictionary>
</binding> </binding>
<binding destination="186" name="fontSize" keyPath="values.fontSize" id="208"/> <binding destination="186" name="fontSize" keyPath="values.fontSize" id="208"/>
@ -189,23 +189,13 @@ DQ
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
<sortDescriptor key="sortDescriptorPrototype" selector="compareTrackNumbers:" sortKey="spotlightTrack"/> <sortDescriptor key="sortDescriptorPrototype" selector="compareTrackNumbers:" sortKey="trackText"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<connections> <connections>
<binding destination="16" name="value" keyPath="arrangedObjects.spotlightTrack" id="195"> <binding destination="16" name="value" keyPath="arrangedObjects.trackText" id="VFy-Rw-QP2">
<dictionary key="options"> <dictionary key="options">
<integer key="NSAllowsEditingMultipleValuesSelection" value="1"/> <bool key="NSConditionallySetsEditable" value="YES"/>
<integer key="NSAlwaysPresentsApplicationModalAlerts" value="0"/> <bool key="NSCreatesSortDescriptor" value="NO"/>
<integer key="NSConditionallySetsEditable" value="1"/>
<integer key="NSConditionallySetsEnabled" value="0"/>
<integer key="NSContinuouslyUpdatesValue" value="0"/>
<integer key="NSCreatesSortDescriptor" value="0"/>
<string key="NSMultipleValuesPlaceholder"></string>
<string key="NSNoSelectionPlaceholder"></string>
<string key="NSNotApplicablePlaceholder"></string>
<string key="NSNullPlaceholder"></string>
<integer key="NSRaisesForNotApplicableKeys" value="1"/>
<integer key="NSValidatesImmediately" value="0"/>
</dictionary> </dictionary>
</binding> </binding>
<binding destination="186" name="fontSize" keyPath="values.fontSize" id="203"/> <binding destination="186" name="fontSize" keyPath="values.fontSize" id="203"/>
@ -288,18 +278,18 @@ DQ
</connections> </connections>
<point key="canvasLocation" x="17" y="109"/> <point key="canvasLocation" x="17" y="109"/>
</window> </window>
<arrayController objectClassName="SpotlightPlaylistEntry" preservesSelection="NO" selectsInsertedObjects="NO" avoidsEmptySelection="NO" clearsFilterPredicateOnInsertion="NO" id="16" customClass="SpotlightPlaylistController"> <arrayController objectClassName="PlaylistEntry" preservesSelection="NO" selectsInsertedObjects="NO" avoidsEmptySelection="NO" clearsFilterPredicateOnInsertion="NO" id="16" customClass="SpotlightPlaylistController">
<declaredKeys> <declaredKeys>
<string>title</string> <string>title</string>
<string>year</string> <string>year</string>
<string>artist</string> <string>artist</string>
<string>album</string> <string>album</string>
<string>genre</string> <string>genre</string>
<string>length</string> <string>lengthText</string>
<string>track</string> <string>track</string>
<string>spotlightTrack</string> <string>trackText</string>
</declaredKeys> </declaredKeys>
<classReference key="objectClass" className="SpotlightPlaylistEntry"/> <classReference key="objectClass" className="PlaylistEntry"/>
<connections> <connections>
<binding destination="-2" name="contentArray" keyPath="query.results" id="213"/> <binding destination="-2" name="contentArray" keyPath="query.results" id="213"/>
<outlet property="spotlightWindowController" destination="-2" id="160"/> <outlet property="spotlightWindowController" destination="-2" id="160"/>

View File

@ -151,6 +151,8 @@
8372C93D27C7895300E250C9 /* MAD.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8372C93027C785BE00E250C9 /* MAD.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 8372C93D27C7895300E250C9 /* MAD.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8372C93027C785BE00E250C9 /* MAD.bundle */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8377C66327B8CF6300E8BC0F /* SpectrumView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C66127B8CF6300E8BC0F /* SpectrumView.m */; }; 8377C66327B8CF6300E8BC0F /* SpectrumView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C66127B8CF6300E8BC0F /* SpectrumView.m */; };
8377C6B927B900F000E8BC0F /* SpectrumItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C6B827B900F000E8BC0F /* SpectrumItem.m */; }; 8377C6B927B900F000E8BC0F /* SpectrumItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C6B827B900F000E8BC0F /* SpectrumItem.m */; };
837DC92B285B05710005C58A /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 837DC92A285B05710005C58A /* CoreData.framework */; };
837DC931285B3F790005C58A /* DataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 837DC92F285B3F790005C58A /* DataModel.xcdatamodeld */; };
837E5ADF2852D5FD0020D205 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 837E5ACB2852D5B80020D205 /* Bugsnag.framework */; }; 837E5ADF2852D5FD0020D205 /* Bugsnag.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 837E5ACB2852D5B80020D205 /* Bugsnag.framework */; };
837E5AE02852D5FD0020D205 /* Bugsnag.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 837E5ACB2852D5B80020D205 /* Bugsnag.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 837E5AE02852D5FD0020D205 /* Bugsnag.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 837E5ACB2852D5B80020D205 /* Bugsnag.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8381A09227C5F72F00A1C530 /* SHA256Digest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8381A09127C5F72F00A1C530 /* SHA256Digest.m */; }; 8381A09227C5F72F00A1C530 /* SHA256Digest.m in Sources */ = {isa = PBXBuildFile; fileRef = 8381A09127C5F72F00A1C530 /* SHA256Digest.m */; };
@ -1062,6 +1064,8 @@
8377C66427B8CF7A00E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VisualizationController.h; path = Audio/Visualization/VisualizationController.h; sourceTree = "<group>"; }; 8377C66427B8CF7A00E8BC0F /* VisualizationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VisualizationController.h; path = Audio/Visualization/VisualizationController.h; sourceTree = "<group>"; };
8377C6B727B900F000E8BC0F /* SpectrumItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectrumItem.h; path = Visualization/SpectrumItem.h; sourceTree = "<group>"; }; 8377C6B727B900F000E8BC0F /* SpectrumItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectrumItem.h; path = Visualization/SpectrumItem.h; sourceTree = "<group>"; };
8377C6B827B900F000E8BC0F /* SpectrumItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SpectrumItem.m; path = Visualization/SpectrumItem.m; sourceTree = "<group>"; }; 8377C6B827B900F000E8BC0F /* SpectrumItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SpectrumItem.m; path = Visualization/SpectrumItem.m; sourceTree = "<group>"; };
837DC92A285B05710005C58A /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
837DC930285B3F790005C58A /* DataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DataModel.xcdatamodel; sourceTree = "<group>"; };
837E5ABB2852D5B80020D205 /* Bugsnag.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Bugsnag.xcodeproj; path = "ThirdParty/bugsnag-cocoa/Bugsnag.xcodeproj"; sourceTree = "<group>"; }; 837E5ABB2852D5B80020D205 /* Bugsnag.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Bugsnag.xcodeproj; path = "ThirdParty/bugsnag-cocoa/Bugsnag.xcodeproj"; sourceTree = "<group>"; };
8381A09027C5F72F00A1C530 /* SHA256Digest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SHA256Digest.h; sourceTree = "<group>"; }; 8381A09027C5F72F00A1C530 /* SHA256Digest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SHA256Digest.h; sourceTree = "<group>"; };
8381A09127C5F72F00A1C530 /* SHA256Digest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SHA256Digest.m; sourceTree = "<group>"; }; 8381A09127C5F72F00A1C530 /* SHA256Digest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SHA256Digest.m; sourceTree = "<group>"; };
@ -1167,6 +1171,7 @@
17BB5CED0B8A86010009ACB1 /* AudioToolbox.framework in Frameworks */, 17BB5CED0B8A86010009ACB1 /* AudioToolbox.framework in Frameworks */,
835FAC7F27BCDF5B00BA8562 /* libavif.a in Frameworks */, 835FAC7F27BCDF5B00BA8562 /* libavif.a in Frameworks */,
835FAC7E27BCDF5B00BA8562 /* libaom.a in Frameworks */, 835FAC7E27BCDF5B00BA8562 /* libaom.a in Frameworks */,
837DC92B285B05710005C58A /* CoreData.framework in Frameworks */,
17BB5CF90B8A86350009ACB1 /* AudioUnit.framework in Frameworks */, 17BB5CF90B8A86350009ACB1 /* AudioUnit.framework in Frameworks */,
17BB5CFA0B8A86350009ACB1 /* CoreAudio.framework in Frameworks */, 17BB5CFA0B8A86350009ACB1 /* CoreAudio.framework in Frameworks */,
838F851E256B4E5E00C3E614 /* Sparkle.framework in Frameworks */, 838F851E256B4E5E00C3E614 /* Sparkle.framework in Frameworks */,
@ -1608,6 +1613,7 @@
29B97317FDCFA39411CA2CEA /* Resources */ = { 29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
837DC92F285B3F790005C58A /* DataModel.xcdatamodeld */,
8316B3922839FFD5004CC392 /* Scenes.scnassets */, 8316B3922839FFD5004CC392 /* Scenes.scnassets */,
832C1252180BD1E2005507C1 /* Cog.help */, 832C1252180BD1E2005507C1 /* Cog.help */,
8E07AD280AAC9BE600A4B32F /* Preference Panes */, 8E07AD280AAC9BE600A4B32F /* Preference Panes */,
@ -1634,6 +1640,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = { 29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
837DC92A285B05710005C58A /* CoreData.framework */,
8370D73E2775AE1300245CE0 /* libsqlite3.tbd */, 8370D73E2775AE1300245CE0 /* libsqlite3.tbd */,
83AB9031237CEFD300A433D5 /* MediaPlayer.framework */, 83AB9031237CEFD300A433D5 /* MediaPlayer.framework */,
8355D6B7180613FB00D05687 /* Security.framework */, 8355D6B7180613FB00D05687 /* Security.framework */,
@ -2762,6 +2769,7 @@
1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */, 1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */,
8E9A30160BA792DC0091081B /* NSFileHandle+CreateFile.m in Sources */, 8E9A30160BA792DC0091081B /* NSFileHandle+CreateFile.m in Sources */,
179790E10C087AB7001D6996 /* OpenURLPanel.m in Sources */, 179790E10C087AB7001D6996 /* OpenURLPanel.m in Sources */,
837DC931285B3F790005C58A /* DataModel.xcdatamodeld in Sources */,
835FAC7927BCDF2A00BA8562 /* AVIFDecoder.m in Sources */, 835FAC7927BCDF2A00BA8562 /* AVIFDecoder.m in Sources */,
EDAAA41F25A665C000731773 /* PositionSliderToolbarItem.swift in Sources */, EDAAA41F25A665C000731773 /* PositionSliderToolbarItem.swift in Sources */,
1791FF900CB43A2C0070BC5C /* MediaKeysApplication.m in Sources */, 1791FF900CB43A2C0070BC5C /* MediaKeysApplication.m in Sources */,
@ -3293,6 +3301,19 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCVersionGroup section */
837DC92F285B3F790005C58A /* DataModel.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
837DC930285B3F790005C58A /* DataModel.xcdatamodel */,
);
currentVersion = 837DC930285B3F790005C58A /* DataModel.xcdatamodel */;
path = DataModel.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
};
/* End XCVersionGroup section */
}; };
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
} }

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="20086" systemVersion="21F79" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AlbumArtwork" representedClassName="AlbumArtwork" syncable="YES" codeGenerationType="class">
<attribute name="artData" optional="YES" attributeType="Binary"/>
<attribute name="artHash" optional="YES" attributeType="String"/>
</entity>
<entity name="PlaylistEntry" representedClassName="PlaylistEntry" syncable="YES" codeGenerationType="class">
<attribute name="album" optional="YES" attributeType="String"/>
<attribute name="albumartist" optional="YES" attributeType="String"/>
<attribute name="artHash" optional="YES" attributeType="String"/>
<attribute name="artist" optional="YES" attributeType="String"/>
<attribute name="bitrate" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="bitsPerSample" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channelConfig" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="channels" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="codec" optional="YES" attributeType="String"/>
<attribute name="cuesheet" optional="YES" attributeType="String"/>
<attribute name="current" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="currentPosition" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="dbIndex" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="deLeted" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="disc" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="encoding" optional="YES" attributeType="String"/>
<attribute name="endian" optional="YES" attributeType="String"/>
<attribute name="entryId" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="error" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="errorMessage" optional="YES" attributeType="String"/>
<attribute name="floatingPoint" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="genre" optional="YES" attributeType="String"/>
<attribute name="index" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="metadataLoaded" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="queued" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="queuePosition" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="rawTitle" optional="YES" attributeType="String"/>
<attribute name="removed" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="replayGainAlbumGain" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="replayGainAlbumPeak" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="replayGainTrackGain" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="replayGainTrackPeak" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="sampleRate" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="seekable" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="shuffleIndex" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="stopAfter" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="totalFrames" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="track" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="trashUrlString" optional="YES" attributeType="String"/>
<attribute name="unSigned" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="urlString" optional="YES" attributeType="String"/>
<attribute name="volume" optional="YES" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="year" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="PlaylistEntry" positionX="-36" positionY="9" width="128" height="674"/>
<element name="AlbumArtwork" positionX="0" positionY="207" width="128" height="59"/>
</elements>
</model>

View File

@ -6,10 +6,14 @@
// Copyright 2005 Vincent Spader All rights reserved. // Copyright 2005 Vincent Spader All rights reserved.
// //
#import "DNDArrayController.h"
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <CoreData/CoreData.h>
#import <Foundation/NSUndoManager.h> #import <Foundation/NSUndoManager.h>
#import "DNDArrayController.h"
@class AlbumArtwork;
@class PlaylistLoader; @class PlaylistLoader;
@class PlaylistEntry; @class PlaylistEntry;
@class SpotlightWindowController; @class SpotlightWindowController;
@ -61,7 +65,12 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
@property(retain) NSString *_Nullable totalTime; @property(retain) NSString *_Nullable totalTime;
@property(retain) NSString *_Nullable currentStatus; @property(retain) NSString *_Nullable currentStatus;
@property(strong, nonatomic, readonly) NSOperationQueue *_Nonnull persistentContainerQueue;
@property(strong, nonatomic, readonly) NSPersistentContainer *_Nonnull persistentContainer;
@property(strong, nonatomic, readonly) NSMutableDictionary<NSString *, AlbumArtwork *> *_Nonnull persistentArtStorage;
// Private Methods // Private Methods
- (void)commitPersistentStore;
- (void)updateTotalTime; - (void)updateTotalTime;
- (void)updatePlaylistIndexes; - (void)updatePlaylistIndexes;
- (IBAction)stopAfterCurrent:(id _Nullable)sender; - (IBAction)stopAfterCurrent:(id _Nullable)sender;

View File

@ -11,7 +11,6 @@
#import "PlaylistEntry.h" #import "PlaylistEntry.h"
#import "PlaylistLoader.h" #import "PlaylistLoader.h"
#import "RepeatTransformers.h" #import "RepeatTransformers.h"
#import "SQLiteStore.h"
#import "Shuffle.h" #import "Shuffle.h"
#import "ShuffleTransformers.h" #import "ShuffleTransformers.h"
#import "SpotlightWindowController.h" #import "SpotlightWindowController.h"
@ -32,6 +31,9 @@
static NSArray *cellIdentifiers = nil; static NSArray *cellIdentifiers = nil;
NSPersistentContainer *__persistentContainer = nil;
NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary = nil;
static void *playlistControllerContext = &playlistControllerContext; static void *playlistControllerContext = &playlistControllerContext;
+ (void)initialize { + (void)initialize {
@ -94,17 +96,31 @@ static void *playlistControllerContext = &playlistControllerContext;
- (id)initWithCoder:(NSCoder *)decoder { - (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder]; self = [super initWithCoder:decoder];
if(!self) return nil;
if(self) { shuffleList = [[NSMutableArray alloc] init];
shuffleList = [[NSMutableArray alloc] init]; queueList = [[NSMutableArray alloc] init];
queueList = [[NSMutableArray alloc] init];
undoManager = [[NSUndoManager alloc] init]; undoManager = [[NSUndoManager alloc] init];
[undoManager setLevelsOfUndo:UNDO_STACK_LIMIT]; [undoManager setLevelsOfUndo:UNDO_STACK_LIMIT];
[self initDefaults]; [self initDefaults];
}
_persistentContainer = [[NSPersistentContainer alloc] initWithName:@"DataModel"];
[self.persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *description, NSError *error) {
if(error != nil) {
ALog(@"Failed to load Core Data stack: %@", error);
abort();
}
}];
__persistentContainer = self.persistentContainer;
self.persistentContainer.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
_persistentArtStorage = [[NSMutableDictionary alloc] init];
__artworkDictionary = self.persistentArtStorage;
return self; return self;
} }
@ -218,31 +234,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} }
} }
- (void)beginProgress:(NSString *)localizedDescription { - (void)commitPersistentStore {
while(playbackController.progressOverall) { NSError *error = nil;
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; [self.persistentContainer.viewContext save:&error];
} if(error) {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{ ALog(@"Error committing playlist storage: %@", [error localizedDescription]);
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];
} }
} }
@ -258,12 +254,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} }
} }
if(updated) { if(updated) {
[self beginProgress:NSLocalizedString(@"ProgressActionUpdatingIndexes", @"updating playlist indexes")]; [self commitPersistentStore];
[[SQLiteStore sharedStore] syncPlaylistEntries:arranged
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[self completeProgress];
} }
} }
@ -347,59 +338,59 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
switch(index) { switch(index) {
case 0: case 0:
cellText = [NSString stringWithFormat:@"%ld", [pe index] + 1]; cellText = [NSString stringWithFormat:@"%lld", pe.index + 1];
cellTextAlignment = NSTextAlignmentRight; cellTextAlignment = NSTextAlignmentRight;
break; break;
case 1: case 1:
cellImage = [statusImageTransformer transformedValue:[pe status]]; cellImage = [statusImageTransformer transformedValue:pe.status];
break; break;
case 2: case 2:
if([pe title]) cellText = [pe title]; if([pe title]) cellText = pe.title;
break; break;
case 3: case 3:
if([pe albumartist]) cellText = [pe albumartist]; if([pe albumartist]) cellText = pe.albumartist;
break; break;
case 4: case 4:
if([pe artist]) cellText = [pe artist]; if([pe artist]) cellText = pe.artist;
break; break;
case 5: case 5:
if([pe album]) cellText = [pe album]; if([pe album]) cellText = pe.album;
break; break;
case 6: case 6:
cellText = [pe lengthText]; cellText = pe.lengthText;
cellTextAlignment = NSTextAlignmentRight; cellTextAlignment = NSTextAlignmentRight;
break; break;
case 7: case 7:
if([pe year]) cellText = [pe yearText]; if([pe year]) cellText = pe.yearText;
cellTextAlignment = NSTextAlignmentRight; cellTextAlignment = NSTextAlignmentRight;
break; break;
case 8: case 8:
if([pe genre]) cellText = [pe genre]; if([pe genre]) cellText = pe.genre;
break; break;
case 9: case 9:
if([pe track]) cellText = [pe trackText]; if([pe track]) cellText = pe.trackText;
cellTextAlignment = NSTextAlignmentRight; cellTextAlignment = NSTextAlignmentRight;
break; break;
case 10: case 10:
if([pe path]) cellText = [pe path]; if([pe path]) cellText = pe.path;
break; break;
case 11: case 11:
if([pe filename]) cellText = [pe filename]; if([pe filename]) cellText = pe.filename;
break; break;
case 12: case 12:
if([pe codec]) cellText = [pe codec]; if([pe codec]) cellText = pe.codec;
break; break;
} }
} }
@ -558,16 +549,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[super moveObjectsFromIndex:fromIndex toArrangedObjectIndexes:indexSet]; [super moveObjectsFromIndex:fromIndex toArrangedObjectIndexes:indexSet];
[self beginProgress:NSLocalizedString(@"ProgressActionMovingEntries", @"moving playlist entries")];
[[SQLiteStore sharedStore] playlistMoveObjectsFromIndex:fromIndex
toArrangedObjectIndexes:indexSet
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[self completeProgress];
[playbackController playlistDidChange:self]; [playbackController playlistDidChange:self];
} }
@ -582,16 +563,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex]; [super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
[self beginProgress:NSLocalizedString(@"ProgressActionMovingEntries", @"moving playlist entries")];
[[SQLiteStore sharedStore] playlistMoveObjectsInArrangedObjectsFromIndexes:indexSet
toIndex:insertIndex
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[self completeProgress];
[playbackController playlistDidChange:self]; [playbackController playlistDidChange:self];
} }
@ -608,9 +579,9 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[filenames addObject:[[song path] stringByExpandingTildeInPath]]; [filenames addObject:[[song path] stringByExpandingTildeInPath]];
if(@available(macOS 10.13, *)) { if(@available(macOS 10.13, *)) {
[item setData:[song.URL dataRepresentation] forType:NSPasteboardTypeFileURL]; [item setData:[song.url dataRepresentation] forType:NSPasteboardTypeFileURL];
} else { } else {
[item setPropertyList:@[song.URL] forType:NSFilenamesPboardType]; [item setPropertyList:@[song.url] forType:NSFilenamesPboardType];
} }
return item; return item;
@ -759,6 +730,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
- (void)insertObjectsUnsynced:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes { - (void)insertObjectsUnsynced:(NSArray *)objects atArrangedObjectIndexes:(NSIndexSet *)indexes {
[super insertObjects:objects atArrangedObjectIndexes:indexes]; [super insertObjects:objects atArrangedObjectIndexes:indexes];
[self rearrangeObjects];
if([self shuffle] != ShuffleOff) [self resetShuffleList]; if([self shuffle] != ShuffleOff) [self resetShuffleList];
} }
@ -771,20 +743,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[[self undoManager] setActionName:actionName]; [[self undoManager] setActionName:actionName];
for(PlaylistEntry *pe in objects) { for(PlaylistEntry *pe in objects) {
pe.deleted = NO; pe.deLeted = NO;
} }
[self beginProgress:NSLocalizedString(@"ProgressActionInsertingEntries", @"inserting playlist entries")];
[[SQLiteStore sharedStore] playlistInsertTracks:objects
atObjectIndexes:indexes
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[super insertObjects:objects atArrangedObjectIndexes:indexes]; [super insertObjects:objects atArrangedObjectIndexes:indexes];
[self completeProgress]; [self commitPersistentStore];
if([self shuffle] != ShuffleOff) [self resetShuffleList]; if([self shuffle] != ShuffleOff) [self resetShuffleList];
} }
@ -797,25 +761,17 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[[self undoManager] setActionName:actionName]; [[self undoManager] setActionName:actionName];
for(PlaylistEntry *pe in objects) { for(PlaylistEntry *pe in objects) {
if(pe.deleted && pe.trashURL) { if(pe.deLeted && pe.trashUrl) {
NSError *error = nil; NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtURL:pe.trashURL toURL:pe.URL error:&error]; [[NSFileManager defaultManager] moveItemAtURL:pe.trashUrl toURL:pe.url error:&error];
} }
pe.deleted = NO; pe.deLeted = NO;
pe.trashURL = nil; pe.trashUrl = nil;
} }
[self beginProgress:NSLocalizedString(@"ProgressActionUntrashingEntries", @"restoring playlist entries from the trash")];
[[SQLiteStore sharedStore] playlistInsertTracks:objects
atObjectIndexes:indexes
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[super insertObjects:objects atArrangedObjectIndexes:indexes]; [super insertObjects:objects atArrangedObjectIndexes:indexes];
[self completeProgress]; [self commitPersistentStore];
if([self shuffle] != ShuffleOff) [self resetShuffleList]; if([self shuffle] != ShuffleOff) [self resetShuffleList];
} }
@ -838,12 +794,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[[self undoManager] setActionName:actionName]; [[self undoManager] setActionName:actionName];
DLog(@"Removing indexes: %@", indexes); DLog(@"Removing indexes: %@", indexes);
DLog(@"Current index: %li", currentEntry.index); DLog(@"Current index: %lli", currentEntry.index);
NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *pe in objects) { for(PlaylistEntry *pe in objects) {
[unarrangedIndexes addIndex:[pe index]]; [unarrangedIndexes addIndex:[pe index]];
pe.deleted = YES; pe.deLeted = YES;
} }
if([indexes containsIndex:currentEntry.index]) { if([indexes containsIndex:currentEntry.index]) {
@ -855,7 +811,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(currentEntry.index >= 0 && [unarrangedIndexes containsIndex:currentEntry.index]) { if(currentEntry.index >= 0 && [unarrangedIndexes containsIndex:currentEntry.index]) {
currentEntry.index = -currentEntry.index - 1; currentEntry.index = -currentEntry.index - 1;
DLog(@"Current removed: %li", currentEntry.index); DLog(@"Current removed: %lli", currentEntry.index);
} }
if(currentEntry.index < 0) // Need to update the negative index if(currentEntry.index < 0) // Need to update the negative index
@ -872,18 +828,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
currentEntry.index = -i - 1; currentEntry.index = -i - 1;
} }
[self beginProgress:NSLocalizedString(@"ProgressActionRemovingEntries", @"removing playlist entries")];
[[SQLiteStore sharedStore] playlistRemoveTracksAtIndexes:unarrangedIndexes
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[super removeObjectsAtArrangedObjectIndexes:indexes]; [super removeObjectsAtArrangedObjectIndexes:indexes];
if([self shuffle] != ShuffleOff) [self resetShuffleList]; [self commitPersistentStore];
[self completeProgress]; if([self shuffle] != ShuffleOff) [self resetShuffleList];
[playbackController playlistDidChange:self]; [playbackController playlistDidChange:self];
} }
@ -898,12 +847,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[[self undoManager] setActionName:actionName]; [[self undoManager] setActionName:actionName];
DLog(@"Trashing indexes: %@", indexes); DLog(@"Trashing indexes: %@", indexes);
DLog(@"Current index: %li", currentEntry.index); DLog(@"Current index: %lli", currentEntry.index);
NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *pe in objects) { for(PlaylistEntry *pe in objects) {
[unarrangedIndexes addIndex:[pe index]]; [unarrangedIndexes addIndex:[pe index]];
pe.deleted = YES; pe.deLeted = YES;
} }
if([indexes containsIndex:currentEntry.index]) { if([indexes containsIndex:currentEntry.index]) {
@ -916,29 +865,22 @@ 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 setProgressStatus:progress];
}];
[super removeObjectsAtArrangedObjectIndexes:indexes]; [super removeObjectsAtArrangedObjectIndexes:indexes];
[self commitPersistentStore];
if([self shuffle] != ShuffleOff) [self resetShuffleList]; if([self shuffle] != ShuffleOff) [self resetShuffleList];
[playbackController playlistDidChange:self]; [playbackController playlistDidChange:self];
for(PlaylistEntry *pe in objects) { for(PlaylistEntry *pe in objects) {
if([pe.URL isFileURL]) { if([pe.url isFileURL]) {
NSURL *removed = nil; NSURL *removed = nil;
NSError *error = nil; NSError *error = nil;
[[NSFileManager defaultManager] trashItemAtURL:pe.URL resultingItemURL:&removed error:&error]; [[NSFileManager defaultManager] trashItemAtURL:pe.url resultingItemURL:&removed error:&error];
pe.trashURL = removed; pe.trashUrl = removed;
} }
} }
[self completeProgress];
} }
- (void)setSortDescriptors:(NSArray *)sortDescriptors { - (void)setSortDescriptors:(NSArray *)sortDescriptors {
@ -1049,10 +991,10 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableArray *duplicates = [[NSMutableArray alloc] init]; NSMutableArray *duplicates = [[NSMutableArray alloc] init];
for(PlaylistEntry *pe in [self content]) { for(PlaylistEntry *pe in [self content]) {
if([originals containsObject:[pe URL]]) if([originals containsObject:pe.url])
[duplicates addObject:pe]; [duplicates addObject:pe];
else else
[originals addObject:[pe URL]]; [originals addObject:pe.url];
} }
if([duplicates count] > 0) { if([duplicates count] > 0) {
@ -1069,7 +1011,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSMutableArray *deadItems = [[NSMutableArray alloc] init]; NSMutableArray *deadItems = [[NSMutableArray alloc] init];
for(PlaylistEntry *pe in [self content]) { for(PlaylistEntry *pe in [self content]) {
NSURL *url = [pe URL]; NSURL *url = pe.url;
if([url isFileURL]) if([url isFileURL])
if(![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) if(![[NSFileManager defaultManager] fileExistsAtPath:[url path]])
[deadItems addObject:pe]; [deadItems addObject:pe];
@ -1120,7 +1062,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
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];
@ -1442,7 +1383,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
- (IBAction)emptyQueueList:(id)sender { - (IBAction)emptyQueueList:(id)sender {
[self emptyQueueListUnsynced]; [self emptyQueueListUnsynced];
[[SQLiteStore sharedStore] queueEmpty];
} }
- (void)emptyQueueListUnsynced { - (void)emptyQueueListUnsynced {
@ -1462,8 +1402,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} }
- (IBAction)toggleQueued:(id)sender { - (IBAction)toggleQueued:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *queueItem in [self selectedObjects]) { for(PlaylistEntry *queueItem in [self selectedObjects]) {
@ -1472,15 +1410,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
queueItem.queued = NO; queueItem.queued = NO;
queueItem.queuePosition = -1; queueItem.queuePosition = -1;
[store queueRemovePlaylistItems:@[[NSNumber numberWithInteger:[queueItem index]]]];
} 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]];
} }
[refreshSet addIndex:[queueItem index]]; [refreshSet addIndex:[queueItem index]];
@ -1504,8 +1438,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} }
- (IBAction)removeFromQueue:(id)sender { - (IBAction)removeFromQueue:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *queueItem in [self selectedObjects]) { for(PlaylistEntry *queueItem in [self selectedObjects]) {
@ -1513,7 +1445,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
queueItem.queuePosition = -1; queueItem.queuePosition = -1;
[queueList removeObject:queueItem]; [queueList removeObject:queueItem];
[store queueRemovePlaylistItems:@[queueItem]];
[refreshSet addIndex:[queueItem index]]; [refreshSet addIndex:[queueItem index]];
} }
@ -1533,8 +1464,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
} }
- (IBAction)addToQueue:(id)sender { - (IBAction)addToQueue:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init]; NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *queueItem in [self selectedObjects]) { for(PlaylistEntry *queueItem in [self selectedObjects]) {
@ -1542,7 +1471,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
queueItem.queuePosition = (int)[queueList count]; queueItem.queuePosition = (int)[queueList count];
[queueList addObject:queueItem]; [queueList addObject:queueItem];
[store queueAddItem:[queueItem index]];
} }
for(PlaylistEntry *queueItem in queueList) { for(PlaylistEntry *queueItem in queueList) {

View File

@ -8,173 +8,58 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface PlaylistEntry : NSObject <NSCopying> { #import "Cog-Swift.h"
NSInteger index;
NSInteger shuffleIndex;
NSInteger dbIndex;
NSInteger entryId;
NSInteger artId;
BOOL current; @interface PlaylistEntry (Extension)
BOOL removed;
BOOL stopAfter; + (NSSet *_Nonnull)keyPathsForValuesAffectingTitle;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingDisplay;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingLength;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingPath;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingFilename;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingStatus;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingStatusMessage;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingSpam;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingAlbumArt;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingTrackText;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingLengthText;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingYearText;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingCuesheetPresent;
+ (NSSet *_Nonnull)keyPathsForValuesAffectingGainCorrection;
BOOL queued; @property(nonatomic, readonly) NSString *_Nonnull display;
NSInteger queuePosition; @property(nonatomic, retain, readonly) NSNumber *_Nonnull length;
@property(nonatomic, readonly) NSString *_Nonnull path;
@property(nonatomic, readonly) NSString *_Nonnull filename;
BOOL error; @property(nonatomic, readonly) NSString *_Nonnull spam;
NSString *errorMessage;
NSURL *URL; @property(nonatomic, readonly) NSString *_Nonnull positionText;
NSURL *trashURL;
NSString *artist; @property(nonatomic, readonly) NSString *_Nonnull lengthText;
NSString *albumartist;
NSString *album;
NSString *title;
NSString *genre;
NSNumber *year;
NSNumber *track;
NSNumber *disc;
NSString *cuesheet; @property(nonatomic, readonly) NSString *_Nonnull yearText;
NSData *albumArtInternal; @property(nonatomic, readonly) NSString *_Nonnull title;
float replayGainAlbumGain; @property(nonatomic, readonly) NSString *_Nonnull trackText;
float replayGainAlbumPeak;
float replayGainTrackGain;
float replayGainTrackPeak;
float volume;
double currentPosition; @property(nonatomic, readonly) NSString *_Nonnull cuesheetPresent;
long long totalFrames; @property(nonatomic, retain, readonly) NSImage *_Nullable albumArt;
int bitrate;
int channels;
uint32_t channelConfig;
int bitsPerSample;
BOOL floatingPoint;
BOOL Unsigned;
float sampleRate;
NSString *codec; @property(nonatomic, readonly) NSString *_Nonnull gainCorrection;
NSString *endian; @property(nonatomic, readonly) NSString *_Nonnull gainInfo;
NSString *encoding; @property(nonatomic, readonly) NSString *_Nullable status;
@property(nonatomic, readonly) NSString *_Nullable statusMessage;
BOOL seekable; @property(nonatomic) NSURL *_Nullable url;
@property(nonatomic) NSURL *_Nullable trashUrl;
BOOL metadataLoaded; @property(nonatomic) NSData *_Nullable albumArtInternal;
BOOL deleted; - (void)setMetadata:(NSDictionary *_Nonnull)metadata;
}
+ (NSSet *)keyPathsForValuesAffectingDisplay;
+ (NSSet *)keyPathsForValuesAffectingLength;
+ (NSSet *)keyPathsForValuesAffectingPath;
+ (NSSet *)keyPathsForValuesAffectingFilename;
+ (NSSet *)keyPathsForValuesAffectingStatus;
+ (NSSet *)keyPathsForValuesAffectingStatusMessage;
+ (NSSet *)keyPathsForValuesAffectingSpam;
+ (NSSet *)keyPathsForValuesAffectingAlbumArt;
+ (NSSet *)keyPathsForValuesAffectingTrackText;
+ (NSSet *)keyPathsForValuesAffectingLengthText;
+ (NSSet *)keyPathsForValuesAffectingYearText;
+ (NSSet *)keyPathsForValuesAffectingCuesheetPresent;
+ (NSSet *)keyPathsForValuesAffectingGainCorrection;
@property(readonly) NSString *display;
@property(retain, readonly) NSNumber *length;
@property(readonly) NSString *path;
@property(readonly) NSString *filename;
@property(readonly) NSString *spam;
@property(readonly) NSString *positionText;
@property(readonly) NSString *lengthText;
@property(readonly) NSString *yearText;
@property(readonly) NSString *rawTitle;
@property(readonly) NSString *trackText;
@property NSInteger index;
@property NSInteger shuffleIndex;
@property NSInteger dbIndex;
@property NSInteger entryId;
@property NSInteger artId;
@property(readonly) NSString *status;
@property(readonly) NSString *statusMessage;
@property BOOL current;
@property BOOL removed;
@property BOOL stopAfter;
@property BOOL queued;
@property NSInteger queuePosition;
@property BOOL error;
@property(retain) NSString *errorMessage;
@property(retain) NSURL *URL;
@property(retain) NSURL *trashURL;
@property(retain) NSString *artist;
@property(retain) NSString *albumartist;
@property(retain) NSString *album;
@property(nonatomic, retain) NSString *title;
@property(retain) NSString *genre;
@property(retain) NSNumber *year;
@property(retain) NSNumber *track;
@property(retain) NSNumber *disc;
@property(retain) NSString *cuesheet;
@property(readonly) NSString *cuesheetPresent;
@property(retain, readonly) NSImage *albumArt;
@property(retain) NSData *albumArtInternal;
@property long long totalFrames;
@property int bitrate;
@property int channels;
@property uint32_t channelConfig;
@property int bitsPerSample;
@property BOOL floatingPoint;
@property BOOL Unsigned;
@property float sampleRate;
@property(retain) NSString *codec;
@property float replayGainAlbumGain;
@property float replayGainAlbumPeak;
@property float replayGainTrackGain;
@property float replayGainTrackPeak;
@property float volume;
@property(readonly) NSString *gainCorrection;
@property(readonly) NSString *gainInfo;
@property double currentPosition;
@property(retain) NSString *endian;
@property(retain) NSString *encoding;
@property BOOL seekable;
@property BOOL metadataLoaded;
@property BOOL deleted;
- (void)setMetadata:(NSDictionary *)metadata;
@end @end

View File

@ -6,73 +6,35 @@
// Copyright 2005 Vincent Spader All rights reserved. // Copyright 2005 Vincent Spader All rights reserved.
// //
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "PlaylistEntry.h" #import "PlaylistEntry.h"
#import "AVIFDecoder.h" #import "AVIFDecoder.h"
#import "SHA256Digest.h"
#import "SecondsFormatter.h" #import "SecondsFormatter.h"
@implementation PlaylistEntry extern NSPersistentContainer *__persistentContainer;
extern NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary;
@synthesize index; @implementation PlaylistEntry (Extension)
@synthesize shuffleIndex;
@synthesize dbIndex;
@synthesize entryId;
@synthesize artId;
@synthesize current;
@synthesize removed;
@synthesize stopAfter;
@synthesize queued;
@synthesize queuePosition;
@synthesize error;
@synthesize errorMessage;
@synthesize URL;
@synthesize trashURL;
@synthesize artist;
@synthesize albumartist;
@synthesize album;
@synthesize genre;
@synthesize year;
@synthesize track;
@synthesize disc;
@synthesize cuesheet;
@synthesize totalFrames;
@synthesize bitrate;
@synthesize channels;
@synthesize channelConfig;
@synthesize bitsPerSample;
@synthesize floatingPoint;
@synthesize Unsigned;
@synthesize sampleRate;
@synthesize codec;
@synthesize replayGainAlbumGain;
@synthesize replayGainAlbumPeak;
@synthesize replayGainTrackGain;
@synthesize replayGainTrackPeak;
@synthesize volume;
@synthesize currentPosition;
@synthesize endian;
@synthesize encoding;
@synthesize seekable;
@synthesize metadataLoaded;
@synthesize deleted;
// The following read-only keys depend on the values of other properties // The following read-only keys depend on the values of other properties
+ (NSSet *)keyPathsForValuesAffectingUrl {
return [NSSet setWithObject:@"urlString"];
}
+ (NSSet *)keyPathsForValuesAffectingTrashUrl {
return [NSSet setWithObject:@"trashUrlString"];
}
+ (NSSet *)keyPathsForValuesAffectingTitle {
return [NSSet setWithObject:@"rawTitle"];
}
+ (NSSet *)keyPathsForValuesAffectingDisplay { + (NSSet *)keyPathsForValuesAffectingDisplay {
return [NSSet setWithObjects:@"artist", @"title", nil]; return [NSSet setWithObjects:@"artist", @"title", nil];
} }
@ -82,11 +44,11 @@
} }
+ (NSSet *)keyPathsForValuesAffectingPath { + (NSSet *)keyPathsForValuesAffectingPath {
return [NSSet setWithObject:@"URL"]; return [NSSet setWithObject:@"url"];
} }
+ (NSSet *)keyPathsForValuesAffectingFilename { + (NSSet *)keyPathsForValuesAffectingFilename {
return [NSSet setWithObject:@"URL"]; return [NSSet setWithObject:@"url"];
} }
+ (NSSet *)keyPathsForValuesAffectingStatus { + (NSSet *)keyPathsForValuesAffectingStatus {
@ -98,7 +60,7 @@
} }
+ (NSSet *)keyPathsForValuesAffectingSpam { + (NSSet *)keyPathsForValuesAffectingSpam {
return [NSSet setWithObjects:@"albumartist", @"artist", @"title", @"album", @"track", @"disc", @"totalFrames", @"currentPosition", @"bitrate", nil]; return [NSSet setWithObjects:@"albumartist", @"artist", @"rawTitle", @"album", @"track", @"disc", @"totalFrames", @"currentPosition", @"bitrate", nil];
} }
+ (NSSet *)keyPathsForValuesAffectingTrackText { + (NSSet *)keyPathsForValuesAffectingTrackText {
@ -134,54 +96,20 @@
} }
- (NSString *)description { - (NSString *)description {
return [NSString stringWithFormat:@"PlaylistEntry %li:(%@)", self.index, self.URL]; return [NSString stringWithFormat:@"PlaylistEntry %lli:(%@)", self.index, self.url];
}
- (id)init {
if(self = [super init]) {
self.replayGainAlbumGain = 0;
self.replayGainAlbumPeak = 0;
self.replayGainTrackGain = 0;
self.replayGainTrackPeak = 0;
self.volume = 1;
self.deleted = NO;
}
return self;
}
- (void)dealloc {
self.errorMessage = nil;
self.URL = nil;
self.artist = nil;
self.albumartist = nil;
self.album = nil;
self.title = nil;
self.genre = nil;
self.year = nil;
self.track = nil;
self.disc = nil;
self.albumArtInternal = nil;
self.cuesheet = nil;
self.endian = nil;
self.codec = nil;
} }
// Get the URL if the title is blank // Get the URL if the title is blank
@synthesize title; @dynamic title;
- (NSString *)title { - (NSString *)title {
if((title == nil || [title isEqualToString:@""]) && self.URL) { if((self.rawTitle == nil || [self.rawTitle isEqualToString:@""]) && self.url) {
return [[self.URL path] lastPathComponent]; return [[self.url path] lastPathComponent];
} }
return title; return self.rawTitle;
} }
@synthesize rawTitle; - (void)setTitle:(NSString *)title {
- (NSString *)rawTitle { self.rawTitle = title;
return title;
} }
@dynamic display; @dynamic display;
@ -200,14 +128,14 @@
BOOL hasAlbumArtist = (self.albumartist != nil) && (![self.albumartist isEqualToString:@""]); BOOL hasAlbumArtist = (self.albumartist != nil) && (![self.albumartist isEqualToString:@""]);
BOOL hasTrackArtist = (hasArtist && hasAlbumArtist) && (![self.albumartist isEqualToString:self.artist]); BOOL hasTrackArtist = (hasArtist && hasAlbumArtist) && (![self.albumartist isEqualToString:self.artist]);
BOOL hasAlbum = (self.album != nil) && (![self.album isEqualToString:@""]); BOOL hasAlbum = (self.album != nil) && (![self.album isEqualToString:@""]);
BOOL hasTrack = (self.track != 0) && ([self.track intValue] != 0); BOOL hasTrack = (self.track != 0);
BOOL hasLength = (self.totalFrames != 0); BOOL hasLength = (self.totalFrames != 0);
BOOL hasCurrentPosition = (self.currentPosition != 0) && (self.current); BOOL hasCurrentPosition = (self.currentPosition != 0) && (self.current);
BOOL hasExtension = NO; BOOL hasExtension = NO;
BOOL hasTitle = (title != nil) && (![title isEqualToString:@""]); BOOL hasTitle = (self.rawTitle != nil) && (![self.rawTitle isEqualToString:@""]);
BOOL hasCodec = (self.codec != nil) && (![self.codec isEqualToString:@""]); BOOL hasCodec = (self.codec != nil) && (![self.codec isEqualToString:@""]);
NSMutableString *filename = [NSMutableString stringWithString:[self filename]]; NSMutableString *filename = [NSMutableString stringWithString:self.filename];
NSRange dotPosition = [filename rangeOfString:@"." options:NSBackwardsSearch]; NSRange dotPosition = [filename rangeOfString:@"." options:NSBackwardsSearch];
NSString *extension = nil; NSString *extension = nil;
@ -258,7 +186,7 @@
} }
if(hasTitle) { if(hasTitle) {
[elements addObject:title]; [elements addObject:self.rawTitle];
} else { } else {
[elements addObject:filename]; [elements addObject:filename];
} }
@ -272,7 +200,7 @@
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init]; SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
[elements addObject:@" ("]; [elements addObject:@" ("];
if(hasCurrentPosition) { if(hasCurrentPosition) {
[elements addObject:[secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]]]; [elements addObject:[secondsFormatter stringForObjectValue:[NSNumber numberWithDouble:self.currentPosition]]];
} }
if(hasLength) { if(hasLength) {
if(hasCurrentPosition) { if(hasCurrentPosition) {
@ -288,11 +216,11 @@
@dynamic trackText; @dynamic trackText;
- (NSString *)trackText { - (NSString *)trackText {
if([self.track intValue]) { if(self.track != 0) {
if([self.disc intValue]) { if(self.disc != 0) {
return [NSString stringWithFormat:@"%@.%02u", self.disc, [self.track intValue]]; return [NSString stringWithFormat:@"%u.%02u", self.disc, self.track];
} else { } else {
return [NSString stringWithFormat:@"%02u", [self.track intValue]]; return [NSString stringWithFormat:@"%02u", self.track];
} }
} else { } else {
return @""; return @"";
@ -301,8 +229,8 @@
@dynamic yearText; @dynamic yearText;
- (NSString *)yearText { - (NSString *)yearText {
if([self.year intValue]) { if(self.year != 0) {
return [NSString stringWithFormat:@"%@", self.year]; return [NSString stringWithFormat:@"%u", self.year];
} else { } else {
return @""; return @"";
} }
@ -310,7 +238,7 @@
@dynamic cuesheetPresent; @dynamic cuesheetPresent;
- (NSString *)cuesheetPresent { - (NSString *)cuesheetPresent {
if(cuesheet && [cuesheet length]) { if(self.cuesheet && [self.cuesheet length]) {
return @"yes"; return @"yes";
} else { } else {
return @"no"; return @"no";
@ -319,17 +247,17 @@
@dynamic gainCorrection; @dynamic gainCorrection;
- (NSString *)gainCorrection { - (NSString *)gainCorrection {
if(replayGainAlbumGain) { if(self.replayGainAlbumGain) {
if(replayGainAlbumPeak) if(self.replayGainAlbumPeak)
return @"Album Gain plus Peak"; return @"Album Gain plus Peak";
else else
return @"Album Gain"; return @"Album Gain";
} else if(replayGainTrackGain) { } else if(self.replayGainTrackGain) {
if(replayGainTrackPeak) if(self.replayGainTrackPeak)
return @"Track Gain plus Peak"; return @"Track Gain plus Peak";
else else
return @"Track Gain"; return @"Track Gain";
} else if(volume && volume != 1) { } else if(self.volume && self.volume != 1.0) {
return @"Volume scale"; return @"Volume scale";
} else { } else {
return @"None"; return @"None";
@ -339,20 +267,20 @@
@dynamic gainInfo; @dynamic gainInfo;
- (NSString *)gainInfo { - (NSString *)gainInfo {
NSMutableArray *gainItems = [[NSMutableArray alloc] init]; NSMutableArray *gainItems = [[NSMutableArray alloc] init];
if(replayGainAlbumGain) { if(self.replayGainAlbumGain) {
[gainItems addObject:[NSString stringWithFormat:@"Album Gain: %+.2f dB", replayGainAlbumGain]]; [gainItems addObject:[NSString stringWithFormat:@"Album Gain: %+.2f dB", self.replayGainAlbumGain]];
} }
if(replayGainAlbumPeak) { if(self.replayGainAlbumPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Album Peak: %.6f", replayGainAlbumPeak]]; [gainItems addObject:[NSString stringWithFormat:@"Album Peak: %.6f", self.replayGainAlbumPeak]];
} }
if(replayGainTrackGain) { if(self.replayGainTrackGain) {
[gainItems addObject:[NSString stringWithFormat:@"Track Gain: %+.2f dB", replayGainTrackGain]]; [gainItems addObject:[NSString stringWithFormat:@"Track Gain: %+.2f dB", self.replayGainTrackGain]];
} }
if(replayGainTrackPeak) { if(self.replayGainTrackPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Track Peak: %.6f", replayGainTrackPeak]]; [gainItems addObject:[NSString stringWithFormat:@"Track Peak: %.6f", self.replayGainTrackPeak]];
} }
if(volume && volume != 1) { if(self.volume && self.volume != 1) {
[gainItems addObject:[NSString stringWithFormat:@"Volume Scale: %.2f%C", volume, (unichar)0x00D7]]; [gainItems addObject:[NSString stringWithFormat:@"Volume Scale: %.2f%C", self.volume, (unichar)0x00D7]];
} }
return [gainItems componentsJoinedByString:@"\n"]; return [gainItems componentsJoinedByString:@"\n"];
} }
@ -360,35 +288,33 @@
@dynamic positionText; @dynamic positionText;
- (NSString *)positionText { - (NSString *)positionText {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init]; SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
NSString *time = [secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]]; NSString *time = [secondsFormatter stringForObjectValue:[NSNumber numberWithDouble:self.currentPosition]];
return time; return time;
} }
@dynamic lengthText; @dynamic lengthText;
- (NSString *)lengthText { - (NSString *)lengthText {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init]; SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
NSString *time = [secondsFormatter stringForObjectValue:[self length]]; NSString *time = [secondsFormatter stringForObjectValue:self.length];
return time; return time;
} }
@synthesize albumArtInternal;
@dynamic albumArt; @dynamic albumArt;
- (NSImage *)albumArt { - (NSImage *)albumArt {
if(!albumArtInternal || ![albumArtInternal length]) return nil; if(!self.albumArtInternal || ![self.albumArtInternal length]) return nil;
NSString *imageCacheTag = [NSString stringWithFormat:@"%ld", artId]; NSString *imageCacheTag = self.artHash;
NSImage *image = [NSImage imageNamed:imageCacheTag]; NSImage *image = [NSImage imageNamed:imageCacheTag];
if(image == nil) { if(image == nil) {
if([AVIFDecoder isAVIFFormatForData:albumArtInternal]) { if([AVIFDecoder isAVIFFormatForData:self.albumArtInternal]) {
CGImageRef imageRef = [AVIFDecoder createAVIFImageWithData:albumArtInternal]; CGImageRef imageRef = [AVIFDecoder createAVIFImageWithData:self.albumArtInternal];
if(imageRef) { if(imageRef) {
image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize]; image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize];
CFRelease(imageRef); CFRelease(imageRef);
} }
} else { } else {
image = [[NSImage alloc] initWithData:albumArtInternal]; image = [[NSImage alloc] initWithData:self.albumArtInternal];
} }
[image setName:imageCacheTag]; [image setName:imageCacheTag];
} }
@ -402,22 +328,94 @@
} }
} }
@dynamic albumArtInternal;
- (NSData *)albumArtInternal {
NSString *imageCacheTag = self.artHash;
return [__artworkDictionary objectForKey:imageCacheTag].artData;
}
- (void)setAlbumArtInternal:(NSData *)albumArtInternal {
if(!albumArtInternal || [albumArtInternal length] == 0) return;
NSString *imageCacheTag = [SHA256Digest digestDataAsString:albumArtInternal];
self.artHash = imageCacheTag;
if(![__artworkDictionary objectForKey:imageCacheTag]) {
AlbumArtwork *art = [NSEntityDescription insertNewObjectForEntityForName:@"AlbumArtwork" inManagedObjectContext:__persistentContainer.viewContext];
art.artHash = imageCacheTag;
art.artData = albumArtInternal;
[__artworkDictionary setObject:art forKey:imageCacheTag];
}
}
@dynamic length; @dynamic length;
- (NSNumber *)length { - (NSNumber *)length {
return [NSNumber numberWithDouble:(self.metadataLoaded) ? ((double)self.totalFrames / self.sampleRate) : 0.0]; return [NSNumber numberWithDouble:(self.metadataLoaded) ? ((double)self.totalFrames / self.sampleRate) : 0.0];
} }
NSURL *_Nullable urlForPath(NSString *_Nullable path) {
if(!path || ![path length]) {
return nil;
}
NSRange protocolRange = [path rangeOfString:@"://"];
if(protocolRange.location != NSNotFound) {
return [NSURL URLWithString:path];
}
NSMutableString *unixPath = [path mutableCopy];
// Get the fragment
NSString *fragment = @"";
NSScanner *scanner = [NSScanner scannerWithString:unixPath];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"];
while(![scanner isAtEnd]) {
NSString *possibleFragment;
[scanner scanUpToString:@"#" intoString:nil];
if([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) {
fragment = possibleFragment;
[unixPath deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])];
break;
}
}
// Append the fragment
NSURL *url = [NSURL URLWithString:[[[NSURL fileURLWithPath:unixPath] absoluteString] stringByAppendingString:fragment]];
return url;
}
@dynamic url;
- (NSURL *)url {
return urlForPath(self.urlString);
}
- (void)setUrl:(NSURL *)url {
self.urlString = url ? [url absoluteString] : nil;
}
@dynamic trashUrl;
- (NSURL *)trashUrl {
return urlForPath(self.trashUrlString);
}
- (void)setTrashUrl:(NSURL *)trashUrl {
self.trashUrlString = trashUrl ? [trashUrl absoluteString] : nil;
}
@dynamic path; @dynamic path;
- (NSString *)path { - (NSString *)path {
if([self.URL isFileURL]) if([self.url isFileURL])
return [[self.URL path] stringByAbbreviatingWithTildeInPath]; return [[self.url path] stringByAbbreviatingWithTildeInPath];
else else
return [self.URL absoluteString]; return [self.url absoluteString];
} }
@dynamic filename; @dynamic filename;
- (NSString *)filename { - (NSString *)filename {
return [[self.URL path] lastPathComponent]; return [[self.url path] lastPathComponent];
} }
@dynamic status; @dynamic status;
@ -442,9 +440,9 @@
} else if(self.current) { } else if(self.current) {
return @"Playing..."; return @"Playing...";
} else if(self.queued) { } else if(self.queued) {
return [NSString stringWithFormat:@"Queued: %li", self.queuePosition + 1]; return [NSString stringWithFormat:@"Queued: %lli", self.queuePosition + 1];
} else if(self.error) { } else if(self.error) {
return errorMessage; return self.errorMessage;
} }
return nil; return nil;
@ -461,74 +459,4 @@
[self setMetadataLoaded:YES]; [self setMetadataLoaded:YES];
} }
// Now we duplicate the object to a new handle, but merely reference the same data
- (id)copyWithZone:(NSZone *)zone {
PlaylistEntry *pe = [[[self class] allocWithZone:zone] init];
if(pe) {
pe->index = index;
pe->shuffleIndex = shuffleIndex;
pe->dbIndex = dbIndex;
pe->entryId = entryId;
pe->artId = artId;
pe->current = current;
pe->removed = removed;
pe->stopAfter = stopAfter;
pe->queued = queued;
pe->queuePosition = queuePosition;
pe->error = error;
pe->errorMessage = errorMessage;
pe->URL = URL;
pe->artist = artist;
pe->albumartist = albumartist;
pe->album = album;
pe->title = title;
pe->genre = genre;
pe->year = year;
pe->track = track;
pe->disc = disc;
pe->cuesheet = cuesheet;
pe->albumArtInternal = albumArtInternal;
pe->replayGainAlbumGain = replayGainAlbumGain;
pe->replayGainAlbumPeak = replayGainAlbumPeak;
pe->replayGainTrackGain = replayGainTrackGain;
pe->replayGainTrackPeak = replayGainTrackPeak;
pe->volume = volume;
currentPosition = pe->currentPosition;
pe->totalFrames = totalFrames;
pe->bitrate = bitrate;
pe->channels = channels;
pe->channelConfig = channelConfig;
pe->bitsPerSample = bitsPerSample;
pe->floatingPoint = floatingPoint;
pe->Unsigned = Unsigned;
pe->sampleRate = sampleRate;
pe->codec = codec;
pe->endian = endian;
pe->encoding = encoding;
pe->seekable = seekable;
pe->metadataLoaded = metadataLoaded;
pe->deleted = deleted;
}
return pe;
}
@end @end

View File

@ -42,6 +42,8 @@ typedef enum {
- (NSArray *)addDatabase; - (NSArray *)addDatabase;
- (BOOL)addDataStore;
// 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

@ -10,6 +10,9 @@
#include <mach/semaphore.h> #include <mach/semaphore.h>
#import "Cog-Swift.h"
#import <CoreData/CoreData.h>
#import "AppController.h" #import "AppController.h"
#import "PlaylistController.h" #import "PlaylistController.h"
#import "PlaylistEntry.h" #import "PlaylistEntry.h"
@ -36,6 +39,8 @@
#import "RedundantPlaylistDataStore.h" #import "RedundantPlaylistDataStore.h"
extern NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary;
@implementation PlaylistLoader @implementation PlaylistLoader
- (id)init { - (id)init {
@ -107,7 +112,7 @@
[fileHandle writeData:[@"#\n" dataUsingEncoding:NSUTF8StringEncoding]]; [fileHandle writeData:[@"#\n" dataUsingEncoding:NSUTF8StringEncoding]];
for(PlaylistEntry *pe in [playlistController arrangedObjects]) { for(PlaylistEntry *pe in [playlistController arrangedObjects]) {
NSString *path = [self relativePathFrom:filename toURL:[pe URL]]; NSString *path = [self relativePathFrom:filename toURL:pe.url];
[fileHandle writeData:[[path stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]]; [fileHandle writeData:[[path stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]];
} }
@ -127,7 +132,7 @@
int i = 1; int i = 1;
for(PlaylistEntry *pe in [playlistController arrangedObjects]) { for(PlaylistEntry *pe in [playlistController arrangedObjects]) {
NSString *path = [self relativePathFrom:filename toURL:[pe URL]]; NSString *path = [self relativePathFrom:filename toURL:pe.url];
NSString *entry = [NSString stringWithFormat:@"File%i=%@\n", i, path]; NSString *entry = [NSString stringWithFormat:@"File%i=%@\n", i, path];
[fileHandle writeData:[entry dataUsingEncoding:NSUTF8StringEncoding]]; [fileHandle writeData:[entry dataUsingEncoding:NSUTF8StringEncoding]];
@ -191,7 +196,7 @@ NSMutableDictionary *dictionaryWithPropertiesOfObject(id obj, NSArray *filterLis
NSMutableDictionary *dict = dictionaryWithPropertiesOfObject(pe, filterList); NSMutableDictionary *dict = dictionaryWithPropertiesOfObject(pe, filterList);
NSString *path = [self relativePathFrom:filename toURL:[pe URL]]; NSString *path = [self relativePathFrom:filename toURL:pe.url];
[dict setObject:path forKey:@"URL"]; [dict setObject:path forKey:@"URL"];
NSData *albumArt = [dict objectForKey:@"albumArtInternal"]; NSData *albumArt = [dict objectForKey:@"albumArtInternal"];
@ -500,12 +505,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSInteger i = 0; NSInteger i = 0;
NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count]; NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
for(NSURL *url in validURLs) { for(NSURL *url in validURLs) {
PlaylistEntry *pe; PlaylistEntry *pe = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:playlistController.persistentContainer.viewContext];
pe = [[PlaylistEntry alloc] init];
pe.URL = url; pe.url = url;
pe.index = index + i; pe.index = index + i;
pe.title = [[url path] lastPathComponent]; pe.rawTitle = [[url path] lastPathComponent];
pe.queuePosition = -1; pe.queuePosition = -1;
[entries addObject:pe]; [entries addObject:pe];
@ -519,8 +523,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(xmlData) { if(xmlData) {
for(NSDictionary *entry in [xmlData objectForKey:@"entries"]) { for(NSDictionary *entry in [xmlData objectForKey:@"entries"]) {
PlaylistEntry *pe; PlaylistEntry *pe = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:playlistController.persistentContainer.viewContext];
pe = [[PlaylistEntry alloc] init];
[pe setValuesForKeysWithDictionary:entry]; [pe setValuesForKeysWithDictionary:entry];
pe.index = index + i; pe.index = index + i;
@ -574,8 +577,10 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if([arrayRest count]) if([arrayRest count])
[self performSelectorInBackground:@selector(loadInfoForEntries:) withObject:arrayRest]; [self performSelectorInBackground:@selector(loadInfoForEntries:) withObject:arrayRest];
else else {
[playlistController commitPersistentStore];
[self completeProgress]; [self completeProgress];
}
return entries; return entries;
} }
} }
@ -585,8 +590,6 @@ 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];
__block double progress = 0.0; __block double progress = 0.0;
double progressstep; double progressstep;
@ -645,13 +648,13 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return; return;
} }
DLog(@"Loading metadata for %@", weakPe.URL); DLog(@"Loading metadata for %@", weakPe.url);
NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:weakPe.URL]; NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:weakPe.url];
if(entryProperties == nil) if(entryProperties == nil)
return; return;
NSDictionary *entryMetadata = [AudioMetadataReader metadataForURL:weakPe.URL]; NSDictionary *entryMetadata = [AudioMetadataReader metadataForURL:weakPe.url];
NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:entryMetadata]; NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:entryMetadata];
@ -683,13 +686,14 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{ dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
if(!weakPe.deleted) { if(!weakPe.deleted) {
[weakPe setMetadata:entryInfo]; [weakPe setMetadata:entryInfo];
[store trackUpdate:weakPe];
} }
progress += progressstep; progress += progressstep;
[self setProgressJobStatus:progress]; [self setProgressJobStatus:progress];
}); });
} }
[playlistController commitPersistentStore];
[playlistController performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO]; [playlistController performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO];
{ {
@ -711,8 +715,6 @@ 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) {
@ -734,16 +736,15 @@ 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];
DLog(@"Loading metadata for %@", pe.URL); DLog(@"Loading metadata for %@", pe.url);
NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:pe.URL]; NSDictionary *entryProperties = [AudioPropertiesReader propertiesForURL:pe.url];
if(entryProperties == nil) if(entryProperties == nil)
return; return;
NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[AudioMetadataReader metadataForURL:pe.URL]]; NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[AudioMetadataReader metadataForURL:pe.url]];
[pe setMetadata:entryInfo]; [pe setMetadata:entryInfo];
[store trackUpdate:pe];
}]; }];
[self->playlistController updateTotalTime]; [self->playlistController updateTotalTime];
@ -766,6 +767,81 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
return [self insertURLs:@[url] atIndex:(int)[[playlistController content] count] sort:NO]; return [self insertURLs:@[url] atIndex:(int)[[playlistController content] count] sort:NO];
} }
- (BOOL)addDataStore {
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) {
[__artworkDictionary setObject:art forKey:art.artHash];
}
request = [NSFetchRequest fetchRequestWithEntityName:@"PlaylistEntry"];
results = [moc executeFetchRequest:request error:&error];
if(!results) {
ALog(@"Error fetching PlaylistEntry objects: %@\n%@", [error localizedDescription], [error userInfo]);
abort();
}
if([results count] == 0) return NO;
NSMutableArray *resultsCopy = [results mutableCopy];
NSMutableIndexSet *pruneSet = [[NSMutableIndexSet alloc] init];
NSUInteger index = 0;
for(PlaylistEntry *pe in resultsCopy) {
if(pe.deLeted) {
[pruneSet addIndex:index];
[moc deleteObject:pe];
}
++index;
}
[resultsCopy removeObjectsAtIndexes:pruneSet];
if([pruneSet count]) {
[playlistController commitPersistentStore];
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES];
results = [resultsCopy sortedArrayUsingDescriptors:@[sortDescriptor]];
{
NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [results count])];
[playlistController insertObjectsUnsynced:results atArrangedObjectIndexes:is];
}
[playlistController emptyQueueListUnsynced];
NSMutableDictionary *queueList = [[NSMutableDictionary alloc] init];
for(PlaylistEntry *pe in results) {
if(pe.queued && pe.queuePosition >= 0) {
NSString *queuePos = [NSString stringWithFormat:@"%llu", pe.queuePosition];
[queueList setObject:pe forKey:queuePos];
}
}
if([queueList count]) {
for(size_t i = 0, j = [queueList count]; i < j; ++i) {
NSString *queuePos = [NSString stringWithFormat:@"%zu", i];
PlaylistEntry *pe = [queueList objectForKey:queuePos];
[[playlistController queueList] addObject:pe];
}
}
return YES;
}
return NO;
}
- (NSArray *)addDatabase { - (NSArray *)addDatabase {
SQLiteStore *store = [SQLiteStore sharedStore]; SQLiteStore *store = [SQLiteStore sharedStore];

View File

@ -221,7 +221,7 @@
NSMutableArray *selectedURLs = [NSMutableArray arrayWithCapacity:capacity]; NSMutableArray *selectedURLs = [NSMutableArray arrayWithCapacity:capacity];
for(PlaylistEntry *pe in entries) { for(PlaylistEntry *pe in entries) {
[selectedURLs addObject:[pe URL]]; [selectedURLs addObject:pe.url];
} }
NSError *error; NSError *error;

View File

@ -6,16 +6,10 @@
// Copyright 2008 Matthew Leon Grinshpun. All rights reserved. // Copyright 2008 Matthew Leon Grinshpun. All rights reserved.
// //
#import "PlaylistEntry.h"
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@interface SpotlightPlaylistEntry : PlaylistEntry { #import "PlaylistEntry.h"
NSNumber *length;
NSString *spotlightTrack;
}
+ (SpotlightPlaylistEntry *)playlistEntryWithMetadataItem:(NSMetadataItem *)metadataItem; @interface SpotlightPlaylistEntry : NSObject
+ (PlaylistEntry *)playlistEntryWithMetadataItem:(NSMetadataItem *)metadataItem;
@property(retain, readwrite) NSNumber *length;
@property(retain) NSString *spotlightTrack;
@end @end

View File

@ -13,33 +13,30 @@
// with format (entryKey, transformerName) // with format (entryKey, transformerName)
static NSDictionary *importKeys; static NSDictionary *importKeys;
extern NSPersistentContainer *__persistentContainer;
@implementation SpotlightPlaylistEntry @implementation SpotlightPlaylistEntry
+ (void)initialize { + (void)initialize {
// We need to translate the path string to a full URL // We need to translate the path string to a full URL
NSArray *URLTransform = NSArray *URLTransform =
@[@"URL", @"PathToURLTransformer"]; @[@"url", @"PathToURLTransformer"];
// Extract the artist name from the authors array // Extract the artist name from the authors array
NSArray *artistTransform = NSArray *artistTransform =
@[@"artist", @"AuthorToArtistTransformer"]; @[@"artist", @"AuthorToArtistTransformer"];
// Track numbers must sometimes be converted from NSNumber to NSString importKeys = @{ @"kMDItemTitle": @"title",
NSArray *trackTransform = @"kMDItemAlbum": @"album",
@[@"spotlightTrack", @"NumberToStringTransformer"]; @"kMDItemAudioTrackNumber": @"track",
@"kMDItemRecordingYear": @"year",
importKeys = @{@"kMDItemTitle": @"title", @"kMDItemMusicalGenre": @"genre",
@"kMDItemAlbum": @"album", @"kMDItemPath": URLTransform,
@"kMDItemAudioTrackNumber": trackTransform, @"kMDItemAuthors": artistTransform };
@"kMDItemRecordingYear": @"year",
@"kMDItemMusicalGenre": @"genre",
@"kMDItemDurationSeconds": @"length",
@"kMDItemPath": URLTransform,
@"kMDItemAuthors": artistTransform};
} }
+ (SpotlightPlaylistEntry *)playlistEntryWithMetadataItem:(NSMetadataItem *)metadataItem { + (PlaylistEntry *)playlistEntryWithMetadataItem:(NSMetadataItem *)metadataItem {
SpotlightPlaylistEntry *entry = [[SpotlightPlaylistEntry alloc] init]; PlaylistEntry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:__persistentContainer.viewContext];
// loop through the keys we want to extract // loop through the keys we want to extract
for(NSString *mdKey in importKeys) { for(NSString *mdKey in importKeys) {
@ -68,17 +65,4 @@ static NSDictionary *importKeys;
return entry; return entry;
} }
// Length is no longer a dependent key
+ (NSSet *)keyPathsForValuesAffectingLength {
return nil;
}
- (void)dealloc {
self.length = nil;
self.spotlightTrack = nil;
}
@synthesize length;
@synthesize spotlightTrack;
@end @end

View File

@ -27,10 +27,14 @@
@property(nonatomic, assign, readwrite) sqlite3 *database; @property(nonatomic, assign, readwrite) sqlite3 *database;
+ (SQLiteStore *)sharedStore; + (SQLiteStore *)sharedStore;
+ (BOOL)databaseStarted;
- (id)init; - (id)init;
- (void)dealloc; - (void)dealloc;
- (void)shutdown;
#if 0
- (void)trackUpdate:(PlaylistEntry *)track; - (void)trackUpdate:(PlaylistEntry *)track;
- (void)playlistInsertTracks:(NSArray *)tracks atIndex:(int64_t)index progressCall:(void (^)(double progress))callback; - (void)playlistInsertTracks:(NSArray *)tracks atIndex:(int64_t)index progressCall:(void (^)(double progress))callback;
@ -38,9 +42,11 @@
- (void)playlistRemoveTracks:(int64_t)index forCount:(int64_t)count progressCall:(void (^)(double progress))callback; - (void)playlistRemoveTracks:(int64_t)index forCount:(int64_t)count progressCall:(void (^)(double progress))callback;
- (void)playlistRemoveTracksAtIndexes:(NSIndexSet *)indexes progressCall:(void (^)(double progress))callback; - (void)playlistRemoveTracksAtIndexes:(NSIndexSet *)indexes progressCall:(void (^)(double progress))callback;
- (PlaylistEntry *)playlistGetItem:(int64_t)index; - (PlaylistEntry *)playlistGetItem:(int64_t)index;
#endif
- (PlaylistEntry *)playlistGetCachedItem:(int64_t)index; - (PlaylistEntry *)playlistGetCachedItem:(int64_t)index;
- (int64_t)playlistGetCount; - (int64_t)playlistGetCount;
#if 0
- (void)playlistMoveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex progressCall:(void (^)(double))callback; - (void)playlistMoveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex progressCall:(void (^)(double))callback;
- (void)playlistMoveObjectsFromIndex:(NSUInteger)fromIndex toArrangedObjectIndexes:(NSIndexSet *)indexSet progressCall:(void (^)(double))callback; - (void)playlistMoveObjectsFromIndex:(NSUInteger)fromIndex toArrangedObjectIndexes:(NSIndexSet *)indexSet progressCall:(void (^)(double))callback;
@ -50,9 +56,12 @@
- (void)queueAddItems:(NSArray *)playlistIndexes; - (void)queueAddItems:(NSArray *)playlistIndexes;
- (void)queueRemoveItem:(int64_t)queueIndex; - (void)queueRemoveItem:(int64_t)queueIndex;
- (void)queueRemovePlaylistItems:(NSArray *)playlistIndexes; - (void)queueRemovePlaylistItems:(NSArray *)playlistIndexes;
#endif
- (int64_t)queueGetEntry:(int64_t)queueIndex; - (int64_t)queueGetEntry:(int64_t)queueIndex;
- (int64_t)queueGetCount; - (int64_t)queueGetCount;
#if 0
- (void)queueEmpty; - (void)queueEmpty;
#endif
@end @end

View File

@ -7,11 +7,15 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "SQLiteStore.h" #import "SQLiteStore.h"
#import "Logging.h" #import "Logging.h"
#import "SHA256Digest.h" #import "SHA256Digest.h"
extern NSPersistentContainer *__persistentContainer;
NSString *getDatabasePath(void) { NSString *getDatabasePath(void) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"]; NSString *basePath = [[paths firstObject] stringByAppendingPathComponent:@"Cog"];
@ -503,37 +507,7 @@ enum {
const char *query_count_queue = "SELECT COUNT(*) FROM queue"; const char *query_count_queue = "SELECT COUNT(*) FROM queue";
NSURL *_Nonnull urlForPath(NSString *_Nullable path) { NSURL *_Nonnull urlForPath(NSString *_Nullable path);
if(!path || ![path length]) {
return [NSURL URLWithString:@"silence://10"];
}
NSRange protocolRange = [path rangeOfString:@"://"];
if(protocolRange.location != NSNotFound) {
return [NSURL URLWithString:path];
}
NSMutableString *unixPath = [path mutableCopy];
// Get the fragment
NSString *fragment = @"";
NSScanner *scanner = [NSScanner scannerWithString:unixPath];
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@"#1234567890"];
while(![scanner isAtEnd]) {
NSString *possibleFragment;
[scanner scanUpToString:@"#" intoString:nil];
if([scanner scanCharactersFromSet:characterSet intoString:&possibleFragment] && [scanner isAtEnd]) {
fragment = possibleFragment;
[unixPath deleteCharactersInRange:NSMakeRange([scanner scanLocation] - [possibleFragment length], [possibleFragment length])];
break;
}
}
// Append the fragment
NSURL *url = [NSURL URLWithString:[[[NSURL fileURLWithPath:unixPath] absoluteString] stringByAppendingString:fragment]];
return url;
}
@interface SQLiteStore (Private) @interface SQLiteStore (Private)
- (NSString *_Nullable)addString:(id _Nullable)string returnId:(int64_t *_Nonnull)stringId; - (NSString *_Nullable)addString:(id _Nullable)string returnId:(int64_t *_Nonnull)stringId;
@ -542,14 +516,18 @@ NSURL *_Nonnull urlForPath(NSString *_Nullable path) {
- (NSData *_Nullable)addArt:(id _Nullable)art returnId:(int64_t *_Nonnull)artId; - (NSData *_Nullable)addArt:(id _Nullable)art returnId:(int64_t *_Nonnull)artId;
- (NSData *_Nonnull)getArt:(int64_t)artId; - (NSData *_Nonnull)getArt:(int64_t)artId;
- (void)removeArt:(int64_t)artId; - (void)removeArt:(int64_t)artId;
#if 0
- (int64_t)addTrack:(PlaylistEntry *_Nonnull)track; - (int64_t)addTrack:(PlaylistEntry *_Nonnull)track;
#endif
- (PlaylistEntry *_Nonnull)getTrack:(int64_t)trackId; - (PlaylistEntry *_Nonnull)getTrack:(int64_t)trackId;
#if 0
- (void)removeTrack:(int64_t)trackId; - (void)removeTrack:(int64_t)trackId;
#endif
@end @end
@implementation SQLiteStore @implementation SQLiteStore
static SQLiteStore *g_sharedStore = NULL; static SQLiteStore *g_sharedStore = nil;
+ (SQLiteStore *)sharedStore { + (SQLiteStore *)sharedStore {
if(!g_sharedStore) { if(!g_sharedStore) {
@ -559,11 +537,14 @@ static SQLiteStore *g_sharedStore = NULL;
return g_sharedStore; return g_sharedStore;
} }
+ (BOOL)databaseStarted {
return g_sharedStore != nil;
}
@synthesize databasePath = g_databasePath; @synthesize databasePath = g_databasePath;
@synthesize database = g_database; @synthesize database = g_database;
- (id)init; - (id)init {
{
self = [super init]; self = [super init];
if(self) { if(self) {
@ -763,15 +744,20 @@ static SQLiteStore *g_sharedStore = NULL;
return nil; return nil;
} }
- (void)dealloc { - (void)shutdown {
if(g_database) { if(g_database) {
for(size_t i = 0; i < stmt_count; ++i) { for(size_t i = 0; i < stmt_count; ++i) {
if(stmt[i]) sqlite3_finalize(stmt[i]); if(stmt[i]) sqlite3_finalize(stmt[i]);
} }
sqlite3_close(g_database); sqlite3_close(g_database);
g_database = NULL;
} }
} }
- (void)dealloc {
[self shutdown];
}
- (NSString *)addString:(id _Nullable)inputObj returnId:(int64_t *_Nonnull)stringId { - (NSString *)addString:(id _Nullable)inputObj returnId:(int64_t *_Nonnull)stringId {
*stringId = -1; *stringId = -1;
@ -1099,7 +1085,7 @@ static SQLiteStore *g_sharedStore = NULL;
} }
- (int64_t)addTrack:(PlaylistEntry *_Nonnull)track { - (int64_t)addTrack:(PlaylistEntry *_Nonnull)track {
NSURL *url = [track URL]; NSURL *url = track.url;
NSString *urlString = [url absoluteString]; NSString *urlString = [url absoluteString];
int64_t urlId = -1; int64_t urlId = -1;
@ -1132,57 +1118,56 @@ static SQLiteStore *g_sharedStore = NULL;
if(rc != SQLITE_ROW) { if(rc != SQLITE_ROW) {
NSString *temp; NSString *temp;
int64_t albumId = -1; int64_t albumId = -1;
temp = [self addString:[track album] returnId:&albumId]; temp = [self addString:track.album returnId:&albumId];
if(temp) [track setAlbum:temp]; if(temp) track.album = temp;
int64_t albumartistId = -1; int64_t albumartistId = -1;
temp = [self addString:[track albumartist] returnId:&albumartistId]; temp = [self addString:track.albumartist returnId:&albumartistId];
if(temp) [track setAlbumartist:temp]; if(temp) track.albumartist = temp;
int64_t artistId = -1; int64_t artistId = -1;
temp = [self addString:[track artist] returnId:&artistId]; temp = [self addString:track.artist returnId:&artistId];
if(temp) [track setArtist:temp]; if(temp) track.artist = temp;
int64_t titleId = -1; int64_t titleId = -1;
temp = [self addString:[track rawTitle] returnId:&titleId]; temp = [self addString:track.rawTitle returnId:&titleId];
if(temp) [track setTitle:temp]; if(temp) track.rawTitle = temp;
int64_t genreId = -1; int64_t genreId = -1;
temp = [self addString:[track genre] returnId:&genreId]; temp = [self addString:track.genre returnId:&genreId];
if(temp) [track setGenre:temp]; if(temp) track.genre = temp;
int64_t codecId = -1; int64_t codecId = -1;
temp = [self addString:[track codec] returnId:&codecId]; temp = [self addString:track.codec returnId:&codecId];
if(temp) [track setCodec:temp]; if(temp) track.codec = temp;
int64_t cuesheetId = -1; int64_t cuesheetId = -1;
temp = [self addString:[track cuesheet] returnId:&cuesheetId]; temp = [self addString:track.cuesheet returnId:&cuesheetId];
if(temp) [track setCuesheet:temp]; if(temp) track.cuesheet = temp;
int64_t encodingId = -1; int64_t encodingId = -1;
temp = [self addString:[track encoding] returnId:&encodingId]; temp = [self addString:track.encoding returnId:&encodingId];
if(temp) [track setEncoding:temp]; if(temp) track.encoding = temp;
int64_t trackNr = [[track track] intValue] | (((uint64_t)[[track disc] intValue]) << 32); int64_t trackNr = track.track | (((uint64_t)track.disc) << 32);
int64_t year = [[track year] intValue]; int64_t year = track.year;
int64_t unsignedFmt = [track Unsigned]; int64_t unsignedFmt = track.unSigned;
int64_t bitrate = [track bitrate]; int64_t bitrate = track.bitrate;
double samplerate = [track sampleRate]; double samplerate = track.sampleRate;
int64_t bitspersample = [track bitsPerSample]; int64_t bitspersample = track.bitsPerSample;
int64_t channels = [track channels]; int64_t channels = track.channels;
int64_t channelConfig = [track channelConfig]; int64_t channelConfig = track.channelConfig;
int64_t endianId = -1; int64_t endianId = -1;
temp = [self addString:[track endian] returnId:&endianId]; temp = [self addString:track.endian returnId:&endianId];
if(temp) [track setEndian:temp]; if(temp) track.endian = temp;
int64_t floatingpoint = [track floatingPoint]; int64_t floatingpoint = track.floatingPoint;
int64_t totalframes = [track totalFrames]; int64_t totalframes = track.totalFrames;
int64_t metadataloaded = [track metadataLoaded]; int64_t metadataloaded = track.metadataLoaded;
int64_t seekable = [track seekable]; int64_t seekable = track.seekable;
double volume = [track volume]; double volume = track.volume;
double replaygainalbumgain = [track replayGainAlbumGain]; double replaygainalbumgain = track.replayGainAlbumGain;
double replaygainalbumpeak = [track replayGainAlbumPeak]; double replaygainalbumpeak = track.replayGainAlbumPeak;
double replaygaintrackgain = [track replayGainTrackGain]; double replaygaintrackgain = track.replayGainTrackGain;
double replaygaintrackpeak = [track replayGainTrackPeak]; double replaygaintrackpeak = track.replayGainTrackPeak;
NSData *albumArt = [track albumArtInternal]; NSData *albumArt = track.albumArtInternal;
int64_t artId = -1; int64_t artId = -1;
if(albumArt) if(albumArt)
albumArt = [self addArt:albumArt returnId:&artId]; albumArt = [self addArt:albumArt returnId:&artId];
if(albumArt) [track setAlbumArtInternal:albumArt]; if(albumArt) track.albumArtInternal = albumArt;
[track setArtId:artId];
st = stmt[stmt_add_track]; st = stmt[stmt_add_track];
@ -1254,8 +1239,9 @@ static SQLiteStore *g_sharedStore = NULL;
return ret; return ret;
} }
#if 0
- (void)trackUpdate:(PlaylistEntry *)track { - (void)trackUpdate:(PlaylistEntry *)track {
NSURL *url = [track URL]; NSURL *url = track.url;
NSString *urlString = [url absoluteString]; NSString *urlString = [url absoluteString];
int64_t urlId = -1; int64_t urlId = -1;
@ -1349,57 +1335,56 @@ static SQLiteStore *g_sharedStore = NULL;
{ {
NSString *temp; NSString *temp;
int64_t albumId = -1; int64_t albumId = -1;
temp = [self addString:[track album] returnId:&albumId]; temp = [self addString:track.album returnId:&albumId];
if(temp) [track setAlbum:temp]; if(temp) track.album = temp;
int64_t albumartistId = -1; int64_t albumartistId = -1;
temp = [self addString:[track albumartist] returnId:&albumartistId]; temp = [self addString:track.albumartist returnId:&albumartistId];
if(temp) [track setAlbumartist:temp]; if(temp) track.albumartist = temp;
int64_t artistId = -1; int64_t artistId = -1;
temp = [self addString:[track artist] returnId:&artistId]; temp = [self addString:track.artist returnId:&artistId];
if(temp) [track setArtist:temp]; if(temp) track.artist = temp;
int64_t titleId = -1; int64_t titleId = -1;
temp = [self addString:[track rawTitle] returnId:&titleId]; temp = [self addString:track.rawTitle returnId:&titleId];
if(temp) [track setTitle:temp]; if(temp) track.rawTitle = temp;
int64_t genreId = -1; int64_t genreId = -1;
temp = [self addString:[track genre] returnId:&genreId]; temp = [self addString:track.genre returnId:&genreId];
if(temp) [track setGenre:temp]; if(temp) track.genre = temp;
int64_t codecId = -1; int64_t codecId = -1;
temp = [self addString:[track codec] returnId:&codecId]; temp = [self addString:track.codec returnId:&codecId];
if(temp) [track setCodec:temp]; if(temp) track.codec = temp;
int64_t cuesheetId = -1; int64_t cuesheetId = -1;
temp = [self addString:[track cuesheet] returnId:&cuesheetId]; temp = [self addString:track.cuesheet returnId:&cuesheetId];
if(temp) [track setCuesheet:temp]; if(temp) track.cuesheet = temp;
int64_t encodingId = -1; int64_t encodingId = -1;
temp = [self addString:[track encoding] returnId:&encodingId]; temp = [self addString:track.encoding returnId:&encodingId];
if(temp) [track setEncoding:temp]; if(temp) track.encoding = temp;
int64_t trackNr = [[track track] intValue] | (((uint64_t)[[track disc] intValue]) << 32); int64_t trackNr = track.track | (((uint64_t)track.disc) << 32);
int64_t year = [[track year] intValue]; int64_t year = track.year;
int64_t unsignedFmt = [track Unsigned]; int64_t unsignedFmt = track.unSigned;
int64_t bitrate = [track bitrate]; int64_t bitrate = track.bitrate;
double samplerate = [track sampleRate]; double samplerate = track.sampleRate;
int64_t bitspersample = [track bitsPerSample]; int64_t bitspersample = track.bitsPerSample;
int64_t channels = [track channels]; int64_t channels = track.channels;
int64_t channelConfig = [track channelConfig]; int64_t channelConfig = track.channelConfig;
int64_t endianId = -1; int64_t endianId = -1;
temp = [self addString:[track endian] returnId:&endianId]; temp = [self addString:track.endian returnId:&endianId];
if(temp) [track setEndian:temp]; if(temp) track.endian = temp;
int64_t floatingpoint = [track floatingPoint]; int64_t floatingpoint = track.floatingPoint;
int64_t totalframes = [track totalFrames]; int64_t totalframes = track.totalFrames;
int64_t metadataloaded = [track metadataLoaded]; int64_t metadataloaded = track.metadataLoaded;
int64_t seekable = [track seekable]; int64_t seekable = track.seekable;
double volume = [track volume]; double volume = track.volume;
double replaygainalbumgain = [track replayGainAlbumGain]; double replaygainalbumgain = track.replayGainAlbumGain;
double replaygainalbumpeak = [track replayGainAlbumPeak]; double replaygainalbumpeak = track.replayGainAlbumPeak;
double replaygaintrackgain = [track replayGainTrackGain]; double replaygaintrackgain = track.replayGainTrackGain;
double replaygaintrackpeak = [track replayGainTrackPeak]; double replaygaintrackpeak = track.replayGainTrackPeak;
NSData *albumArt = [track albumArtInternal]; NSData *albumArt = track.albumArtInternal;
int64_t artId = -1; int64_t artId = -1;
if(albumArt) if(albumArt)
albumArt = [self addArt:albumArt returnId:&artId]; albumArt = [self addArt:albumArt returnId:&artId];
if(albumArt) [track setAlbumArtInternal:albumArt]; if(albumArt) track.albumArtInternal = albumArt;
[track setArtId:artId];
st = stmt[stmt_update_track]; st = stmt[stmt_update_track];
@ -1452,12 +1437,13 @@ static SQLiteStore *g_sharedStore = NULL;
return; return;
} }
[databaseMirror replaceObjectAtIndex:[track index] withObject:[track copy]]; [databaseMirror replaceObjectAtIndex:[track index] withObject:track];
} }
} }
#endif
- (PlaylistEntry *_Nonnull)getTrack:(int64_t)trackId { - (PlaylistEntry *_Nonnull)getTrack:(int64_t)trackId {
PlaylistEntry *entry = [[PlaylistEntry alloc] init]; PlaylistEntry *entry = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:__persistentContainer.viewContext];
if(trackId < 0) if(trackId < 0)
return entry; return entry;
@ -1509,41 +1495,40 @@ static SQLiteStore *g_sharedStore = NULL;
uint64_t discNr = ((uint64_t)trackNr) >> 32; uint64_t discNr = ((uint64_t)trackNr) >> 32;
trackNr &= (1UL << 32) - 1; trackNr &= (1UL << 32) - 1;
[entry setURL:urlForPath([self getString:urlId])]; entry.url = urlForPath([self getString:urlId]);
[entry setAlbum:[self getString:albumId]]; entry.album = [self getString:albumId];
[entry setAlbumartist:[self getString:albumartistId]]; entry.albumartist = [self getString:albumartistId];
[entry setArtist:[self getString:artistId]]; entry.artist = [self getString:artistId];
[entry setTitle:[self getString:titleId]]; entry.rawTitle = [self getString:titleId];
[entry setGenre:[self getString:genreId]]; entry.genre = [self getString:genreId];
[entry setCodec:[self getString:codecId]]; entry.codec = [self getString:codecId];
[entry setCuesheet:[self getString:cuesheetId]]; entry.cuesheet = [self getString:cuesheetId];
[entry setEncoding:[self getString:encodingId]]; entry.encoding = [self getString:encodingId];
[entry setTrack:[NSNumber numberWithInteger:trackNr]]; entry.track = (int32_t)trackNr;
[entry setDisc:[NSNumber numberWithInteger:discNr]]; entry.disc = (int32_t)discNr;
[entry setYear:[NSNumber numberWithInteger:year]]; entry.year = (int32_t)year;
[entry setUnsigned:!!unsignedFmt]; entry.unSigned = !!unsignedFmt;
[entry setBitrate:(int)bitrate]; entry.bitrate = (int32_t)bitrate;
[entry setSampleRate:samplerate]; entry.sampleRate = samplerate;
[entry setBitsPerSample:(int)bitspersample]; entry.bitsPerSample = (int32_t)bitspersample;
[entry setChannels:(int)channels]; entry.channels = (int32_t)channels;
[entry setChannelConfig:(uint32_t)channelConfig]; entry.channelConfig = (uint32_t)channelConfig;
[entry setEndian:[self getString:endianId]]; entry.endian = [self getString:endianId];
[entry setFloatingPoint:!!floatingpoint]; entry.floatingPoint = !!floatingpoint;
[entry setTotalFrames:totalframes]; entry.totalFrames = totalframes;
[entry setSeekable:!!seekable]; entry.seekable = !!seekable;
[entry setVolume:volume]; entry.volume = volume;
[entry setReplayGainAlbumGain:replaygainalbumgain]; entry.replayGainAlbumGain = replaygainalbumgain;
[entry setReplayGainAlbumPeak:replaygainalbumpeak]; entry.replayGainAlbumPeak = replaygainalbumpeak;
[entry setReplayGainTrackGain:replaygaintrackgain]; entry.replayGainTrackGain = replaygaintrackgain;
[entry setReplayGainTrackPeak:replaygaintrackpeak]; entry.replayGainTrackPeak = replaygaintrackpeak;
[entry setArtId:artId]; entry.albumArtInternal = [self getArt:artId];
[entry setAlbumArtInternal:[self getArt:artId]];
[entry setMetadataLoaded:!!metadataloaded]; entry.metadataLoaded = !!metadataloaded;
[entry setDbIndex:trackId]; entry.dbIndex = trackId;
} }
sqlite3_reset(st); sqlite3_reset(st);
@ -1692,7 +1677,7 @@ static SQLiteStore *g_sharedStore = NULL;
NSMutableArray *tracksCopy = [[NSMutableArray alloc] init]; NSMutableArray *tracksCopy = [[NSMutableArray alloc] init];
for(PlaylistEntry *pe in tracks) { for(PlaylistEntry *pe in tracks) {
[tracksCopy addObject:[pe copy]]; [tracksCopy addObject:pe];
} }
[databaseMirror insertObjects:tracksCopy atIndexes:indexes]; [databaseMirror insertObjects:tracksCopy atIndexes:indexes];
@ -1724,6 +1709,7 @@ static SQLiteStore *g_sharedStore = NULL;
callback(-1); callback(-1);
} }
#if 0
- (void)playlistRemoveTracks:(int64_t)index forCount:(int64_t)count progressCall:(void (^)(double))callback { - (void)playlistRemoveTracks:(int64_t)index forCount:(int64_t)count progressCall:(void (^)(double))callback {
if(!count) { if(!count) {
callback(-1); callback(-1);
@ -1830,16 +1816,17 @@ static SQLiteStore *g_sharedStore = NULL;
}]; }];
callback(-1); callback(-1);
} }
#endif
- (PlaylistEntry *)playlistGetCachedItem:(int64_t)index { - (PlaylistEntry *)playlistGetCachedItem:(int64_t)index {
if(index >= 0 && index < [databaseMirror count]) if(index >= 0 && index < [databaseMirror count])
return [[databaseMirror objectAtIndex:index] copy]; return [databaseMirror objectAtIndex:index];
else else
return nil; return nil;
} }
- (PlaylistEntry *)playlistGetItem:(int64_t)index { - (PlaylistEntry *)playlistGetItem:(int64_t)index {
PlaylistEntry *entry = [[PlaylistEntry alloc] init]; PlaylistEntry *entry = nil;
sqlite3_stmt *st = stmt[stmt_select_playlist]; sqlite3_stmt *st = stmt[stmt_select_playlist];
@ -1858,8 +1845,8 @@ static SQLiteStore *g_sharedStore = NULL;
int64_t trackId = sqlite3_column_int64(st, select_playlist_out_track_id); int64_t trackId = sqlite3_column_int64(st, select_playlist_out_track_id);
int64_t entryId = sqlite3_column_int64(st, select_playlist_out_entry_id); int64_t entryId = sqlite3_column_int64(st, select_playlist_out_entry_id);
entry = [self getTrack:trackId]; entry = [self getTrack:trackId];
[entry setIndex:index]; entry.index = index;
[entry setEntryId:entryId]; entry.entryId = entryId;
} }
sqlite3_reset(st); sqlite3_reset(st);
@ -1882,6 +1869,7 @@ static SQLiteStore *g_sharedStore = NULL;
return ret; return ret;
} }
#if 0
- (void)playlistMoveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex progressCall:(void (^)(double))callback { - (void)playlistMoveObjectsInArrangedObjectsFromIndexes:(NSIndexSet *)indexSet toIndex:(NSUInteger)insertIndex progressCall:(void (^)(double))callback {
__block NSUInteger rangeCount = 0; __block NSUInteger rangeCount = 0;
__block NSUInteger firstIndex = 0; __block NSUInteger firstIndex = 0;
@ -2087,7 +2075,7 @@ static SQLiteStore *g_sharedStore = NULL;
return; return;
} }
[databaseMirror replaceObjectAtIndex:i withObject:[newpe copy]]; [databaseMirror replaceObjectAtIndex:i withObject:newpe];
callback(progress); callback(progress);
} }
@ -2168,6 +2156,7 @@ static SQLiteStore *g_sharedStore = NULL;
[self queueRemoveItem:queueIndex]; [self queueRemoveItem:queueIndex];
} }
} }
#endif
- (int64_t)queueGetEntry:(int64_t)queueIndex { - (int64_t)queueGetEntry:(int64_t)queueIndex {
sqlite3_stmt *st = stmt[stmt_select_queue]; sqlite3_stmt *st = stmt[stmt_select_queue];
@ -2195,6 +2184,7 @@ static SQLiteStore *g_sharedStore = NULL;
return ret; return ret;
} }
#if 0
- (void)queueEmpty { - (void)queueEmpty {
sqlite3_stmt *st = stmt[stmt_remove_queue_all]; sqlite3_stmt *st = stmt[stmt_remove_queue_all];
@ -2204,6 +2194,7 @@ static SQLiteStore *g_sharedStore = NULL;
return; return;
} }
} }
#endif
- (int64_t)queueGetCount { - (int64_t)queueGetCount {
sqlite3_stmt *st = stmt[stmt_count_queue]; sqlite3_stmt *st = stmt[stmt_count_queue];