[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 "PlaylistLoader.h"
#import "PlaylistView.h"
#import "SQLiteStore.h"
#import "SpotlightWindowController.h"
#import "StringToURLTransformer.h"
#import <CogAudio/Status.h>
@ -167,12 +168,16 @@ void *kAppControllerContext = &kAppControllerContext;
NSString *oldFilename = @"Default.m3u";
NSString *newFilename = @"Default.xml";
if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:dbFilename]]) {
[playlistLoader addDatabase];
} else if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:newFilename]]) {
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:newFilename]]];
} else {
[playlistLoader addURL:[NSURL fileURLWithPath:[basePath stringByAppendingPathComponent:oldFilename]]];
BOOL dataStorePresent = [playlistLoader addDataStore];
if(!dataStorePresent) {
if([[NSFileManager defaultManager] fileExistsAtPath:[basePath stringByAppendingPathComponent:dbFilename]]) {
[playlistLoader addDatabase];
} 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];
@ -375,9 +380,37 @@ void *kAppControllerContext = &kAppControllerContext;
[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;
NSString *fileName = @"Default.sqlite";
[[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error];
fileName = @"Default.xml";
[[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error];
fileName = @"Default.m3u";

View File

@ -160,16 +160,16 @@ NSString *CogPlaybackDidStopNotficiation = @"CogPlaybackDidStopNotficiation";
NSDictionary *makeRGInfo(PlaylistEntry *pe) {
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
if([pe replayGainAlbumGain] != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainAlbumGain]] forKey:@"replayGainAlbumGain"];
if([pe replayGainAlbumPeak] != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainAlbumPeak]] forKey:@"replayGainAlbumPeak"];
if([pe replayGainTrackGain] != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainTrackGain]] forKey:@"replayGainTrackGain"];
if([pe replayGainTrackPeak] != 0)
[dictionary setObject:[NSNumber numberWithFloat:[pe replayGainTrackPeak]] forKey:@"replayGainTrackPeak"];
if([pe volume] != 1)
[dictionary setObject:[NSNumber numberWithFloat:[pe volume]] forKey:@"volume"];
if(pe.replayGainAlbumGain != 0)
[dictionary setObject:[NSNumber numberWithFloat:pe.replayGainAlbumGain] forKey:@"replayGainAlbumGain"];
if(pe.replayGainAlbumPeak != 0)
[dictionary setObject:[NSNumber numberWithFloat:pe.replayGainAlbumPeak] forKey:@"replayGainAlbumPeak"];
if(pe.replayGainTrackGain != 0)
[dictionary setObject:[NSNumber numberWithFloat:pe.replayGainTrackGain] forKey:@"replayGainTrackGain"];
if(pe.replayGainTrackPeak != 0)
[dictionary setObject:[NSNumber numberWithFloat:pe.replayGainTrackPeak] forKey:@"replayGainTrackPeak"];
if(pe.volume != 1)
[dictionary setObject:[NSNumber numberWithFloat:pe.volume] forKey:@"volume"];
return dictionary;
}
@ -196,7 +196,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
return;
BOOL loadData = YES;
NSString *urlScheme = [[pe URL] scheme];
NSString *urlScheme = [pe.url scheme];
if([urlScheme isEqualToString:@"http"] ||
[urlScheme isEqualToString:@"https"])
loadData = NO;
@ -222,9 +222,9 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
[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 {
@ -574,7 +574,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
}
if(pe)
[player setNextStream:[pe URL] withUserInfo:pe withRGInfo:makeRGInfo(pe)];
[player setNextStream:pe.url withUserInfo:pe withRGInfo:makeRGInfo(pe)];
else
[player setNextStream:nil];
}
@ -674,7 +674,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
- (void)audioPlayer:(AudioPlayer *)player restartPlaybackAtCurrentPosition:(id)userInfo {
PlaylistEntry *pe = [playlistController currentEntry];
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 {
@ -768,7 +768,7 @@ NSDictionary *makeRGInfo(PlaylistEntry *pe) {
if([entry year]) {
// If PlaylistEntry can represent a full date like some tag formats can do, change it
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:[NSNumber numberWithFloat:[entry currentPosition]] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];

View File

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

View File

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

View File

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

View File

@ -1,8 +1,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>
<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"/>
</dependencies>
<objects>
@ -130,9 +130,9 @@ DQ
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<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">
<integer key="NSConditionallySetsEditable" value="1"/>
<bool key="NSConditionallySetsEditable" value="YES"/>
</dictionary>
</binding>
<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="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<sortDescriptor key="sortDescriptorPrototype" selector="compareTrackNumbers:" sortKey="spotlightTrack"/>
<sortDescriptor key="sortDescriptorPrototype" selector="compareTrackNumbers:" sortKey="trackText"/>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<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">
<integer key="NSAllowsEditingMultipleValuesSelection" value="1"/>
<integer key="NSAlwaysPresentsApplicationModalAlerts" value="0"/>
<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"/>
<bool key="NSConditionallySetsEditable" value="YES"/>
<bool key="NSCreatesSortDescriptor" value="NO"/>
</dictionary>
</binding>
<binding destination="186" name="fontSize" keyPath="values.fontSize" id="203"/>
@ -288,18 +278,18 @@ DQ
</connections>
<point key="canvasLocation" x="17" y="109"/>
</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>
<string>title</string>
<string>year</string>
<string>artist</string>
<string>album</string>
<string>genre</string>
<string>length</string>
<string>lengthText</string>
<string>track</string>
<string>spotlightTrack</string>
<string>trackText</string>
</declaredKeys>
<classReference key="objectClass" className="SpotlightPlaylistEntry"/>
<classReference key="objectClass" className="PlaylistEntry"/>
<connections>
<binding destination="-2" name="contentArray" keyPath="query.results" id="213"/>
<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, ); }; };
8377C66327B8CF6300E8BC0F /* SpectrumView.m in Sources */ = {isa = PBXBuildFile; fileRef = 8377C66127B8CF6300E8BC0F /* SpectrumView.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 */; };
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 */; };
@ -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>"; };
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>"; };
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>"; };
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>"; };
@ -1167,6 +1171,7 @@
17BB5CED0B8A86010009ACB1 /* AudioToolbox.framework in Frameworks */,
835FAC7F27BCDF5B00BA8562 /* libavif.a in Frameworks */,
835FAC7E27BCDF5B00BA8562 /* libaom.a in Frameworks */,
837DC92B285B05710005C58A /* CoreData.framework in Frameworks */,
17BB5CF90B8A86350009ACB1 /* AudioUnit.framework in Frameworks */,
17BB5CFA0B8A86350009ACB1 /* CoreAudio.framework in Frameworks */,
838F851E256B4E5E00C3E614 /* Sparkle.framework in Frameworks */,
@ -1608,6 +1613,7 @@
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
837DC92F285B3F790005C58A /* DataModel.xcdatamodeld */,
8316B3922839FFD5004CC392 /* Scenes.scnassets */,
832C1252180BD1E2005507C1 /* Cog.help */,
8E07AD280AAC9BE600A4B32F /* Preference Panes */,
@ -1634,6 +1640,7 @@
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
837DC92A285B05710005C58A /* CoreData.framework */,
8370D73E2775AE1300245CE0 /* libsqlite3.tbd */,
83AB9031237CEFD300A433D5 /* MediaPlayer.framework */,
8355D6B7180613FB00D05687 /* Security.framework */,
@ -2762,6 +2769,7 @@
1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */,
8E9A30160BA792DC0091081B /* NSFileHandle+CreateFile.m in Sources */,
179790E10C087AB7001D6996 /* OpenURLPanel.m in Sources */,
837DC931285B3F790005C58A /* DataModel.xcdatamodeld in Sources */,
835FAC7927BCDF2A00BA8562 /* AVIFDecoder.m in Sources */,
EDAAA41F25A665C000731773 /* PositionSliderToolbarItem.swift in Sources */,
1791FF900CB43A2C0070BC5C /* MediaKeysApplication.m in Sources */,
@ -3293,6 +3301,19 @@
defaultConfigurationName = Release;
};
/* 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 */;
}

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.
//
#import "DNDArrayController.h"
#import <Cocoa/Cocoa.h>
#import <CoreData/CoreData.h>
#import <Foundation/NSUndoManager.h>
#import "DNDArrayController.h"
@class AlbumArtwork;
@class PlaylistLoader;
@class PlaylistEntry;
@class SpotlightWindowController;
@ -61,7 +65,12 @@ typedef NS_ENUM(NSInteger, URLOrigin) {
@property(retain) NSString *_Nullable totalTime;
@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
- (void)commitPersistentStore;
- (void)updateTotalTime;
- (void)updatePlaylistIndexes;
- (IBAction)stopAfterCurrent:(id _Nullable)sender;

View File

@ -11,7 +11,6 @@
#import "PlaylistEntry.h"
#import "PlaylistLoader.h"
#import "RepeatTransformers.h"
#import "SQLiteStore.h"
#import "Shuffle.h"
#import "ShuffleTransformers.h"
#import "SpotlightWindowController.h"
@ -32,6 +31,9 @@
static NSArray *cellIdentifiers = nil;
NSPersistentContainer *__persistentContainer = nil;
NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary = nil;
static void *playlistControllerContext = &playlistControllerContext;
+ (void)initialize {
@ -94,17 +96,31 @@ static void *playlistControllerContext = &playlistControllerContext;
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if(!self) return nil;
if(self) {
shuffleList = [[NSMutableArray alloc] init];
queueList = [[NSMutableArray alloc] init];
shuffleList = [[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;
}
@ -218,31 +234,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
- (void)beginProgress:(NSString *)localizedDescription {
while(playbackController.progressOverall) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
self->playbackController.progressOverall = [NSProgress progressWithTotalUnitCount:100000];
self->playbackController.progressOverall.localizedDescription = localizedDescription;
});
}
- (void)completeProgress {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
[self->playbackController.progressOverall setCompletedUnitCount:100000];
self->playbackController.progressOverall = nil;
});
}
- (void)setProgressStatus:(double)status {
if(status >= 0) {
dispatch_sync_reentrant(dispatch_get_main_queue(), ^{
NSUInteger jobCount = (NSUInteger)ceil(1000.0 * status);
[self->playbackController.progressOverall setCompletedUnitCount:jobCount];
});
} else {
[self completeProgress];
- (void)commitPersistentStore {
NSError *error = nil;
[self.persistentContainer.viewContext save:&error];
if(error) {
ALog(@"Error committing playlist storage: %@", [error localizedDescription]);
}
}
@ -258,12 +254,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
}
if(updated) {
[self beginProgress:NSLocalizedString(@"ProgressActionUpdatingIndexes", @"updating playlist indexes")];
[[SQLiteStore sharedStore] syncPlaylistEntries:arranged
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[self completeProgress];
[self commitPersistentStore];
}
}
@ -347,59 +338,59 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
switch(index) {
case 0:
cellText = [NSString stringWithFormat:@"%ld", [pe index] + 1];
cellText = [NSString stringWithFormat:@"%lld", pe.index + 1];
cellTextAlignment = NSTextAlignmentRight;
break;
case 1:
cellImage = [statusImageTransformer transformedValue:[pe status]];
cellImage = [statusImageTransformer transformedValue:pe.status];
break;
case 2:
if([pe title]) cellText = [pe title];
if([pe title]) cellText = pe.title;
break;
case 3:
if([pe albumartist]) cellText = [pe albumartist];
if([pe albumartist]) cellText = pe.albumartist;
break;
case 4:
if([pe artist]) cellText = [pe artist];
if([pe artist]) cellText = pe.artist;
break;
case 5:
if([pe album]) cellText = [pe album];
if([pe album]) cellText = pe.album;
break;
case 6:
cellText = [pe lengthText];
cellText = pe.lengthText;
cellTextAlignment = NSTextAlignmentRight;
break;
case 7:
if([pe year]) cellText = [pe yearText];
if([pe year]) cellText = pe.yearText;
cellTextAlignment = NSTextAlignmentRight;
break;
case 8:
if([pe genre]) cellText = [pe genre];
if([pe genre]) cellText = pe.genre;
break;
case 9:
if([pe track]) cellText = [pe trackText];
if([pe track]) cellText = pe.trackText;
cellTextAlignment = NSTextAlignmentRight;
break;
case 10:
if([pe path]) cellText = [pe path];
if([pe path]) cellText = pe.path;
break;
case 11:
if([pe filename]) cellText = [pe filename];
if([pe filename]) cellText = pe.filename;
break;
case 12:
if([pe codec]) cellText = [pe codec];
if([pe codec]) cellText = pe.codec;
break;
}
}
@ -558,16 +549,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[super moveObjectsFromIndex:fromIndex toArrangedObjectIndexes:indexSet];
[self beginProgress:NSLocalizedString(@"ProgressActionMovingEntries", @"moving playlist entries")];
[[SQLiteStore sharedStore] playlistMoveObjectsFromIndex:fromIndex
toArrangedObjectIndexes:indexSet
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[self completeProgress];
[playbackController playlistDidChange:self];
}
@ -582,16 +563,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[super moveObjectsInArrangedObjectsFromIndexes:indexSet toIndex:insertIndex];
[self beginProgress:NSLocalizedString(@"ProgressActionMovingEntries", @"moving playlist entries")];
[[SQLiteStore sharedStore] playlistMoveObjectsInArrangedObjectsFromIndexes:indexSet
toIndex:insertIndex
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[self completeProgress];
[playbackController playlistDidChange:self];
}
@ -608,9 +579,9 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[filenames addObject:[[song path] stringByExpandingTildeInPath]];
if(@available(macOS 10.13, *)) {
[item setData:[song.URL dataRepresentation] forType:NSPasteboardTypeFileURL];
[item setData:[song.url dataRepresentation] forType:NSPasteboardTypeFileURL];
} else {
[item setPropertyList:@[song.URL] forType:NSFilenamesPboardType];
[item setPropertyList:@[song.url] forType:NSFilenamesPboardType];
}
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 {
[super insertObjects:objects atArrangedObjectIndexes:indexes];
[self rearrangeObjects];
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];
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];
[self completeProgress];
[self commitPersistentStore];
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];
for(PlaylistEntry *pe in objects) {
if(pe.deleted && pe.trashURL) {
if(pe.deLeted && pe.trashUrl) {
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.trashURL = nil;
pe.deLeted = NO;
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];
[self completeProgress];
[self commitPersistentStore];
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];
DLog(@"Removing indexes: %@", indexes);
DLog(@"Current index: %li", currentEntry.index);
DLog(@"Current index: %lli", currentEntry.index);
NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *pe in objects) {
[unarrangedIndexes addIndex:[pe index]];
pe.deleted = YES;
pe.deLeted = YES;
}
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]) {
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
@ -872,18 +828,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
currentEntry.index = -i - 1;
}
[self beginProgress:NSLocalizedString(@"ProgressActionRemovingEntries", @"removing playlist entries")];
[[SQLiteStore sharedStore] playlistRemoveTracksAtIndexes:unarrangedIndexes
progressCall:^(double progress) {
[self setProgressStatus:progress];
}];
[super removeObjectsAtArrangedObjectIndexes:indexes];
if([self shuffle] != ShuffleOff) [self resetShuffleList];
[self commitPersistentStore];
[self completeProgress];
if([self shuffle] != ShuffleOff) [self resetShuffleList];
[playbackController playlistDidChange:self];
}
@ -898,12 +847,12 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
[[self undoManager] setActionName:actionName];
DLog(@"Trashing indexes: %@", indexes);
DLog(@"Current index: %li", currentEntry.index);
DLog(@"Current index: %lli", currentEntry.index);
NSMutableIndexSet *unarrangedIndexes = [[NSMutableIndexSet alloc] init];
for(PlaylistEntry *pe in objects) {
[unarrangedIndexes addIndex:[pe index]];
pe.deleted = YES;
pe.deLeted = YES;
}
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];
[self commitPersistentStore];
if([self shuffle] != ShuffleOff) [self resetShuffleList];
[playbackController playlistDidChange:self];
for(PlaylistEntry *pe in objects) {
if([pe.URL isFileURL]) {
if([pe.url isFileURL]) {
NSURL *removed = nil;
NSError *error = nil;
[[NSFileManager defaultManager] trashItemAtURL:pe.URL resultingItemURL:&removed error:&error];
pe.trashURL = removed;
[[NSFileManager defaultManager] trashItemAtURL:pe.url resultingItemURL:&removed error:&error];
pe.trashUrl = removed;
}
}
[self completeProgress];
}
- (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];
for(PlaylistEntry *pe in [self content]) {
if([originals containsObject:[pe URL]])
if([originals containsObject:pe.url])
[duplicates addObject:pe];
else
[originals addObject:[pe URL]];
[originals addObject:pe.url];
}
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];
for(PlaylistEntry *pe in [self content]) {
NSURL *url = [pe URL];
NSURL *url = pe.url;
if([url isFileURL])
if(![[NSFileManager defaultManager] fileExistsAtPath:[url path]])
[deadItems addObject:pe];
@ -1120,7 +1062,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if([queueList count] > 0) {
pe = queueList[0];
[queueList removeObjectAtIndex:0];
[[SQLiteStore sharedStore] queueRemoveItem:0];
pe.queued = NO;
[pe setQueuePosition:-1];
@ -1442,7 +1383,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
- (IBAction)emptyQueueList:(id)sender {
[self emptyQueueListUnsynced];
[[SQLiteStore sharedStore] queueEmpty];
}
- (void)emptyQueueListUnsynced {
@ -1462,8 +1402,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
- (IBAction)toggleQueued:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
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.queuePosition = -1;
[store queueRemovePlaylistItems:@[[NSNumber numberWithInteger:[queueItem index]]]];
} else {
queueItem.queued = YES;
queueItem.queuePosition = (int)[queueList count];
[queueList addObject:queueItem];
[store queueAddItem:[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 {
SQLiteStore *store = [SQLiteStore sharedStore];
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
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;
[queueList removeObject:queueItem];
[store queueRemovePlaylistItems:@[queueItem]];
[refreshSet addIndex:[queueItem index]];
}
@ -1533,8 +1464,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
}
- (IBAction)addToQueue:(id)sender {
SQLiteStore *store = [SQLiteStore sharedStore];
NSMutableIndexSet *refreshSet = [[NSMutableIndexSet alloc] init];
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];
[queueList addObject:queueItem];
[store queueAddItem:[queueItem index]];
}
for(PlaylistEntry *queueItem in queueList) {

View File

@ -8,173 +8,58 @@
#import <Cocoa/Cocoa.h>
@interface PlaylistEntry : NSObject <NSCopying> {
NSInteger index;
NSInteger shuffleIndex;
NSInteger dbIndex;
NSInteger entryId;
NSInteger artId;
#import "Cog-Swift.h"
BOOL current;
BOOL removed;
@interface PlaylistEntry (Extension)
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;
NSInteger queuePosition;
@property(nonatomic, readonly) NSString *_Nonnull display;
@property(nonatomic, retain, readonly) NSNumber *_Nonnull length;
@property(nonatomic, readonly) NSString *_Nonnull path;
@property(nonatomic, readonly) NSString *_Nonnull filename;
BOOL error;
NSString *errorMessage;
@property(nonatomic, readonly) NSString *_Nonnull spam;
NSURL *URL;
NSURL *trashURL;
@property(nonatomic, readonly) NSString *_Nonnull positionText;
NSString *artist;
NSString *albumartist;
NSString *album;
NSString *title;
NSString *genre;
NSNumber *year;
NSNumber *track;
NSNumber *disc;
@property(nonatomic, readonly) NSString *_Nonnull lengthText;
NSString *cuesheet;
@property(nonatomic, readonly) NSString *_Nonnull yearText;
NSData *albumArtInternal;
@property(nonatomic, readonly) NSString *_Nonnull title;
float replayGainAlbumGain;
float replayGainAlbumPeak;
float replayGainTrackGain;
float replayGainTrackPeak;
float volume;
@property(nonatomic, readonly) NSString *_Nonnull trackText;
double currentPosition;
@property(nonatomic, readonly) NSString *_Nonnull cuesheetPresent;
long long totalFrames;
int bitrate;
int channels;
uint32_t channelConfig;
int bitsPerSample;
BOOL floatingPoint;
BOOL Unsigned;
float sampleRate;
@property(nonatomic, retain, readonly) NSImage *_Nullable albumArt;
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;
}
+ (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;
- (void)setMetadata:(NSDictionary *_Nonnull)metadata;
@end

View File

@ -6,73 +6,35 @@
// Copyright 2005 Vincent Spader All rights reserved.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#import "PlaylistEntry.h"
#import "AVIFDecoder.h"
#import "SHA256Digest.h"
#import "SecondsFormatter.h"
@implementation PlaylistEntry
extern NSPersistentContainer *__persistentContainer;
extern NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary;
@synthesize index;
@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;
@implementation PlaylistEntry (Extension)
// 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 {
return [NSSet setWithObjects:@"artist", @"title", nil];
}
@ -82,11 +44,11 @@
}
+ (NSSet *)keyPathsForValuesAffectingPath {
return [NSSet setWithObject:@"URL"];
return [NSSet setWithObject:@"url"];
}
+ (NSSet *)keyPathsForValuesAffectingFilename {
return [NSSet setWithObject:@"URL"];
return [NSSet setWithObject:@"url"];
}
+ (NSSet *)keyPathsForValuesAffectingStatus {
@ -98,7 +60,7 @@
}
+ (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 {
@ -134,54 +96,20 @@
}
- (NSString *)description {
return [NSString stringWithFormat:@"PlaylistEntry %li:(%@)", 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;
return [NSString stringWithFormat:@"PlaylistEntry %lli:(%@)", self.index, self.url];
}
// Get the URL if the title is blank
@synthesize title;
@dynamic title;
- (NSString *)title {
if((title == nil || [title isEqualToString:@""]) && self.URL) {
return [[self.URL path] lastPathComponent];
if((self.rawTitle == nil || [self.rawTitle isEqualToString:@""]) && self.url) {
return [[self.url path] lastPathComponent];
}
return title;
return self.rawTitle;
}
@synthesize rawTitle;
- (NSString *)rawTitle {
return title;
- (void)setTitle:(NSString *)title {
self.rawTitle = title;
}
@dynamic display;
@ -200,14 +128,14 @@
BOOL hasAlbumArtist = (self.albumartist != nil) && (![self.albumartist isEqualToString:@""]);
BOOL hasTrackArtist = (hasArtist && hasAlbumArtist) && (![self.albumartist isEqualToString:self.artist]);
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 hasCurrentPosition = (self.currentPosition != 0) && (self.current);
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:@""]);
NSMutableString *filename = [NSMutableString stringWithString:[self filename]];
NSMutableString *filename = [NSMutableString stringWithString:self.filename];
NSRange dotPosition = [filename rangeOfString:@"." options:NSBackwardsSearch];
NSString *extension = nil;
@ -258,7 +186,7 @@
}
if(hasTitle) {
[elements addObject:title];
[elements addObject:self.rawTitle];
} else {
[elements addObject:filename];
}
@ -272,7 +200,7 @@
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
[elements addObject:@" ("];
if(hasCurrentPosition) {
[elements addObject:[secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]]];
[elements addObject:[secondsFormatter stringForObjectValue:[NSNumber numberWithDouble:self.currentPosition]]];
}
if(hasLength) {
if(hasCurrentPosition) {
@ -288,11 +216,11 @@
@dynamic trackText;
- (NSString *)trackText {
if([self.track intValue]) {
if([self.disc intValue]) {
return [NSString stringWithFormat:@"%@.%02u", self.disc, [self.track intValue]];
if(self.track != 0) {
if(self.disc != 0) {
return [NSString stringWithFormat:@"%u.%02u", self.disc, self.track];
} else {
return [NSString stringWithFormat:@"%02u", [self.track intValue]];
return [NSString stringWithFormat:@"%02u", self.track];
}
} else {
return @"";
@ -301,8 +229,8 @@
@dynamic yearText;
- (NSString *)yearText {
if([self.year intValue]) {
return [NSString stringWithFormat:@"%@", self.year];
if(self.year != 0) {
return [NSString stringWithFormat:@"%u", self.year];
} else {
return @"";
}
@ -310,7 +238,7 @@
@dynamic cuesheetPresent;
- (NSString *)cuesheetPresent {
if(cuesheet && [cuesheet length]) {
if(self.cuesheet && [self.cuesheet length]) {
return @"yes";
} else {
return @"no";
@ -319,17 +247,17 @@
@dynamic gainCorrection;
- (NSString *)gainCorrection {
if(replayGainAlbumGain) {
if(replayGainAlbumPeak)
if(self.replayGainAlbumGain) {
if(self.replayGainAlbumPeak)
return @"Album Gain plus Peak";
else
return @"Album Gain";
} else if(replayGainTrackGain) {
if(replayGainTrackPeak)
} else if(self.replayGainTrackGain) {
if(self.replayGainTrackPeak)
return @"Track Gain plus Peak";
else
return @"Track Gain";
} else if(volume && volume != 1) {
} else if(self.volume && self.volume != 1.0) {
return @"Volume scale";
} else {
return @"None";
@ -339,20 +267,20 @@
@dynamic gainInfo;
- (NSString *)gainInfo {
NSMutableArray *gainItems = [[NSMutableArray alloc] init];
if(replayGainAlbumGain) {
[gainItems addObject:[NSString stringWithFormat:@"Album Gain: %+.2f dB", replayGainAlbumGain]];
if(self.replayGainAlbumGain) {
[gainItems addObject:[NSString stringWithFormat:@"Album Gain: %+.2f dB", self.replayGainAlbumGain]];
}
if(replayGainAlbumPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Album Peak: %.6f", replayGainAlbumPeak]];
if(self.replayGainAlbumPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Album Peak: %.6f", self.replayGainAlbumPeak]];
}
if(replayGainTrackGain) {
[gainItems addObject:[NSString stringWithFormat:@"Track Gain: %+.2f dB", replayGainTrackGain]];
if(self.replayGainTrackGain) {
[gainItems addObject:[NSString stringWithFormat:@"Track Gain: %+.2f dB", self.replayGainTrackGain]];
}
if(replayGainTrackPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Track Peak: %.6f", replayGainTrackPeak]];
if(self.replayGainTrackPeak) {
[gainItems addObject:[NSString stringWithFormat:@"Track Peak: %.6f", self.replayGainTrackPeak]];
}
if(volume && volume != 1) {
[gainItems addObject:[NSString stringWithFormat:@"Volume Scale: %.2f%C", volume, (unichar)0x00D7]];
if(self.volume && self.volume != 1) {
[gainItems addObject:[NSString stringWithFormat:@"Volume Scale: %.2f%C", self.volume, (unichar)0x00D7]];
}
return [gainItems componentsJoinedByString:@"\n"];
}
@ -360,35 +288,33 @@
@dynamic positionText;
- (NSString *)positionText {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
NSString *time = [secondsFormatter stringForObjectValue:[NSNumber numberWithFloat:currentPosition]];
NSString *time = [secondsFormatter stringForObjectValue:[NSNumber numberWithDouble:self.currentPosition]];
return time;
}
@dynamic lengthText;
- (NSString *)lengthText {
SecondsFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
NSString *time = [secondsFormatter stringForObjectValue:[self length]];
NSString *time = [secondsFormatter stringForObjectValue:self.length];
return time;
}
@synthesize albumArtInternal;
@dynamic 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];
if(image == nil) {
if([AVIFDecoder isAVIFFormatForData:albumArtInternal]) {
CGImageRef imageRef = [AVIFDecoder createAVIFImageWithData:albumArtInternal];
if([AVIFDecoder isAVIFFormatForData:self.albumArtInternal]) {
CGImageRef imageRef = [AVIFDecoder createAVIFImageWithData:self.albumArtInternal];
if(imageRef) {
image = [[NSImage alloc] initWithCGImage:imageRef size:NSZeroSize];
CFRelease(imageRef);
}
} else {
image = [[NSImage alloc] initWithData:albumArtInternal];
image = [[NSImage alloc] initWithData:self.albumArtInternal];
}
[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;
- (NSNumber *)length {
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;
- (NSString *)path {
if([self.URL isFileURL])
return [[self.URL path] stringByAbbreviatingWithTildeInPath];
if([self.url isFileURL])
return [[self.url path] stringByAbbreviatingWithTildeInPath];
else
return [self.URL absoluteString];
return [self.url absoluteString];
}
@dynamic filename;
- (NSString *)filename {
return [[self.URL path] lastPathComponent];
return [[self.url path] lastPathComponent];
}
@dynamic status;
@ -442,9 +440,9 @@
} else if(self.current) {
return @"Playing...";
} else if(self.queued) {
return [NSString stringWithFormat:@"Queued: %li", self.queuePosition + 1];
return [NSString stringWithFormat:@"Queued: %lli", self.queuePosition + 1];
} else if(self.error) {
return errorMessage;
return self.errorMessage;
}
return nil;
@ -461,74 +459,4 @@
[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

View File

@ -42,6 +42,8 @@ typedef enum {
- (NSArray *)addDatabase;
- (BOOL)addDataStore;
// Save playlist, auto-determines type based on extension. Uses m3u if it cannot be determined.
- (BOOL)save:(NSString *)filename;
- (BOOL)save:(NSString *)filename asType:(PlaylistType)type;

View File

@ -10,6 +10,9 @@
#include <mach/semaphore.h>
#import "Cog-Swift.h"
#import <CoreData/CoreData.h>
#import "AppController.h"
#import "PlaylistController.h"
#import "PlaylistEntry.h"
@ -36,6 +39,8 @@
#import "RedundantPlaylistDataStore.h"
extern NSMutableDictionary<NSString *, AlbumArtwork *> *__artworkDictionary;
@implementation PlaylistLoader
- (id)init {
@ -107,7 +112,7 @@
[fileHandle writeData:[@"#\n" dataUsingEncoding:NSUTF8StringEncoding]];
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]];
}
@ -127,7 +132,7 @@
int i = 1;
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];
[fileHandle writeData:[entry dataUsingEncoding:NSUTF8StringEncoding]];
@ -191,7 +196,7 @@ NSMutableDictionary *dictionaryWithPropertiesOfObject(id obj, NSArray *filterLis
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"];
NSData *albumArt = [dict objectForKey:@"albumArtInternal"];
@ -500,12 +505,11 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
NSInteger i = 0;
NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count];
for(NSURL *url in validURLs) {
PlaylistEntry *pe;
pe = [[PlaylistEntry alloc] init];
PlaylistEntry *pe = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:playlistController.persistentContainer.viewContext];
pe.URL = url;
pe.url = url;
pe.index = index + i;
pe.title = [[url path] lastPathComponent];
pe.rawTitle = [[url path] lastPathComponent];
pe.queuePosition = -1;
[entries addObject:pe];
@ -519,8 +523,7 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if(xmlData) {
for(NSDictionary *entry in [xmlData objectForKey:@"entries"]) {
PlaylistEntry *pe;
pe = [[PlaylistEntry alloc] init];
PlaylistEntry *pe = [NSEntityDescription insertNewObjectForEntityForName:@"PlaylistEntry" inManagedObjectContext:playlistController.persistentContainer.viewContext];
[pe setValuesForKeysWithDictionary:entry];
pe.index = index + i;
@ -574,8 +577,10 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
if([arrayRest count])
[self performSelectorInBackground:@selector(loadInfoForEntries:) withObject:arrayRest];
else
else {
[playlistController commitPersistentStore];
[self completeProgress];
}
return entries;
}
}
@ -585,8 +590,6 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
long i, j;
NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init];
SQLiteStore *store = [SQLiteStore sharedStore];
__block double progress = 0.0;
double progressstep;
@ -645,13 +648,13 @@ static inline void dispatch_sync_reentrant(dispatch_queue_t queue, dispatch_bloc
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)
return;
NSDictionary *entryMetadata = [AudioMetadataReader metadataForURL:weakPe.URL];
NSDictionary *entryMetadata = [AudioMetadataReader metadataForURL:weakPe.url];
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(), ^{
if(!weakPe.deleted) {
[weakPe setMetadata:entryInfo];
[store trackUpdate:weakPe];
}
progress += progressstep;
[self setProgressJobStatus:progress];
});
}
[playlistController commitPersistentStore];
[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;
NSMutableIndexSet *load_info_indexes = [[NSMutableIndexSet alloc] init];
SQLiteStore *store = [SQLiteStore sharedStore];
i = 0;
j = 0;
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) {
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)
return;
NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[AudioMetadataReader metadataForURL:pe.URL]];
NSDictionary *entryInfo = [NSDictionary dictionaryByMerging:entryProperties with:[AudioMetadataReader metadataForURL:pe.url]];
[pe setMetadata:entryInfo];
[store trackUpdate:pe];
}];
[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];
}
- (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 {
SQLiteStore *store = [SQLiteStore sharedStore];

View File

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

View File

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

View File

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

View File

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

View File

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