From 4c95c943efc11da194c4dbb01e86201c9bad9bee Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Thu, 16 Jun 2022 07:14:33 -0700 Subject: [PATCH] [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 --- Application/AppController.m | 47 ++- Application/PlaybackController.m | 32 +- Application/PlaybackEventController.h | 1 - Application/PlaybackEventController.m | 22 +- AudioScrobbler/AudioScrobbler.m | 2 +- Base.lproj/SpotlightPanel.xib | 34 +- Cog.xcodeproj/project.pbxproj | 21 + .../DataModel.xcdatamodel/contents | 56 +++ Playlist/PlaylistController.h | 11 +- Playlist/PlaylistController.m | 208 ++++------ Playlist/PlaylistEntry.h | 187 ++------- Playlist/PlaylistEntry.m | 358 +++++++----------- Playlist/PlaylistLoader.h | 2 + Playlist/PlaylistLoader.m | 120 ++++-- Playlist/PlaylistView.m | 2 +- Spotlight/SpotlightPlaylistEntry.h | 12 +- Spotlight/SpotlightPlaylistEntry.m | 40 +- Utils/SQLiteStore.h | 9 + Utils/SQLiteStore.m | 295 +++++++-------- 19 files changed, 683 insertions(+), 776 deletions(-) create mode 100644 DataModel.xcdatamodeld/DataModel.xcdatamodel/contents diff --git a/Application/AppController.m b/Application/AppController.m index 556e9e08b..b744982e1 100644 --- a/Application/AppController.m +++ b/Application/AppController.m @@ -11,6 +11,7 @@ #import "PlaylistEntry.h" #import "PlaylistLoader.h" #import "PlaylistView.h" +#import "SQLiteStore.h" #import "SpotlightWindowController.h" #import "StringToURLTransformer.h" #import @@ -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 *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"; diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index fc822412d..de329f446 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -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]; diff --git a/Application/PlaybackEventController.h b/Application/PlaybackEventController.h index f443cd030..012c9ef0a 100644 --- a/Application/PlaybackEventController.h +++ b/Application/PlaybackEventController.h @@ -10,7 +10,6 @@ #import #import "PlaybackController.h" -#import "PlaylistEntry.h" @class AudioScrobbler; diff --git a/Application/PlaybackEventController.m b/Application/PlaybackEventController.m index 7d71bd883..0f89b3c0f 100644 --- a/Application/PlaybackEventController.m +++ b/Application/PlaybackEventController.m @@ -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; diff --git a/AudioScrobbler/AudioScrobbler.m b/AudioScrobbler/AudioScrobbler.m index f18b7f134..96e10f884 100644 --- a/AudioScrobbler/AudioScrobbler.m +++ b/AudioScrobbler/AudioScrobbler.m @@ -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 { diff --git a/Base.lproj/SpotlightPanel.xib b/Base.lproj/SpotlightPanel.xib index 7d5df1bed..13d70c474 100644 --- a/Base.lproj/SpotlightPanel.xib +++ b/Base.lproj/SpotlightPanel.xib @@ -1,8 +1,8 @@ - + - + @@ -130,9 +130,9 @@ DQ - + - + @@ -189,23 +189,13 @@ DQ - + - + - - - - - - - - - - - - + + @@ -288,18 +278,18 @@ DQ - + title year artist album genre - length + lengthText track - spotlightTrack + trackText - + diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 3eb9b4753..7f4cdfc60 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -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 = ""; }; 8377C6B727B900F000E8BC0F /* SpectrumItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpectrumItem.h; path = Visualization/SpectrumItem.h; sourceTree = ""; }; 8377C6B827B900F000E8BC0F /* SpectrumItem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SpectrumItem.m; path = Visualization/SpectrumItem.m; sourceTree = ""; }; + 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 = ""; }; 837E5ABB2852D5B80020D205 /* Bugsnag.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Bugsnag.xcodeproj; path = "ThirdParty/bugsnag-cocoa/Bugsnag.xcodeproj"; sourceTree = ""; }; 8381A09027C5F72F00A1C530 /* SHA256Digest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SHA256Digest.h; sourceTree = ""; }; 8381A09127C5F72F00A1C530 /* SHA256Digest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SHA256Digest.m; sourceTree = ""; }; @@ -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 = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; } diff --git a/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents new file mode 100644 index 000000000..b0312d583 --- /dev/null +++ b/DataModel.xcdatamodeld/DataModel.xcdatamodel/contents @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Playlist/PlaylistController.h b/Playlist/PlaylistController.h index 383d8e0dc..3fce1f833 100644 --- a/Playlist/PlaylistController.h +++ b/Playlist/PlaylistController.h @@ -6,10 +6,14 @@ // Copyright 2005 Vincent Spader All rights reserved. // -#import "DNDArrayController.h" #import +#import #import +#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 *_Nonnull persistentArtStorage; + // Private Methods +- (void)commitPersistentStore; - (void)updateTotalTime; - (void)updatePlaylistIndexes; - (IBAction)stopAfterCurrent:(id _Nullable)sender; diff --git a/Playlist/PlaylistController.m b/Playlist/PlaylistController.m index 91901a94b..030c0ac79 100644 --- a/Playlist/PlaylistController.m +++ b/Playlist/PlaylistController.m @@ -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 *__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) { diff --git a/Playlist/PlaylistEntry.h b/Playlist/PlaylistEntry.h index e24884b06..9449e46a5 100644 --- a/Playlist/PlaylistEntry.h +++ b/Playlist/PlaylistEntry.h @@ -8,173 +8,58 @@ #import -@interface PlaylistEntry : NSObject { - 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 diff --git a/Playlist/PlaylistEntry.m b/Playlist/PlaylistEntry.m index 158d459d5..c8544451c 100644 --- a/Playlist/PlaylistEntry.m +++ b/Playlist/PlaylistEntry.m @@ -6,73 +6,35 @@ // Copyright 2005 Vincent Spader All rights reserved. // +#import + +#import + #import "PlaylistEntry.h" + #import "AVIFDecoder.h" +#import "SHA256Digest.h" #import "SecondsFormatter.h" -@implementation PlaylistEntry +extern NSPersistentContainer *__persistentContainer; +extern NSMutableDictionary *__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 diff --git a/Playlist/PlaylistLoader.h b/Playlist/PlaylistLoader.h index 53e941f31..e5f6b72bf 100644 --- a/Playlist/PlaylistLoader.h +++ b/Playlist/PlaylistLoader.h @@ -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; diff --git a/Playlist/PlaylistLoader.m b/Playlist/PlaylistLoader.m index 76cb6278d..dbf6f50ee 100644 --- a/Playlist/PlaylistLoader.m +++ b/Playlist/PlaylistLoader.m @@ -10,6 +10,9 @@ #include +#import "Cog-Swift.h" +#import + #import "AppController.h" #import "PlaylistController.h" #import "PlaylistEntry.h" @@ -36,6 +39,8 @@ #import "RedundantPlaylistDataStore.h" +extern NSMutableDictionary *__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]; diff --git a/Playlist/PlaylistView.m b/Playlist/PlaylistView.m index 959080a3d..7edd7c084 100644 --- a/Playlist/PlaylistView.m +++ b/Playlist/PlaylistView.m @@ -221,7 +221,7 @@ NSMutableArray *selectedURLs = [NSMutableArray arrayWithCapacity:capacity]; for(PlaylistEntry *pe in entries) { - [selectedURLs addObject:[pe URL]]; + [selectedURLs addObject:pe.url]; } NSError *error; diff --git a/Spotlight/SpotlightPlaylistEntry.h b/Spotlight/SpotlightPlaylistEntry.h index 03c8028f9..9fb6ee7c5 100644 --- a/Spotlight/SpotlightPlaylistEntry.h +++ b/Spotlight/SpotlightPlaylistEntry.h @@ -6,16 +6,10 @@ // Copyright 2008 Matthew Leon Grinshpun. All rights reserved. // -#import "PlaylistEntry.h" #import -@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 diff --git a/Spotlight/SpotlightPlaylistEntry.m b/Spotlight/SpotlightPlaylistEntry.m index 08fc84d19..469f3f85d 100644 --- a/Spotlight/SpotlightPlaylistEntry.m +++ b/Spotlight/SpotlightPlaylistEntry.m @@ -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 diff --git a/Utils/SQLiteStore.h b/Utils/SQLiteStore.h index 2d9eb5fa1..4659d81c2 100644 --- a/Utils/SQLiteStore.h +++ b/Utils/SQLiteStore.h @@ -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 diff --git a/Utils/SQLiteStore.m b/Utils/SQLiteStore.m index dbd23286f..eae5465dc 100644 --- a/Utils/SQLiteStore.m +++ b/Utils/SQLiteStore.m @@ -7,11 +7,15 @@ #import +#import + #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];