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];