From b5fd1207b89d7964e63af192e16a437c87ccf3cb Mon Sep 17 00:00:00 2001 From: Chris Moeller Date: Wed, 9 Oct 2013 08:45:16 -0700 Subject: [PATCH] Now saves and loads the default playlist in an XML plist format, so loaded metadata is cached --- Application/AppController.m | 27 +++++++-- Cog.xcodeproj/project.pbxproj | 6 ++ Playlist/PlaylistLoader.h | 2 + Playlist/PlaylistLoader.m | 102 +++++++++++++++++++++++++++++++++- Playlist/XmlContainer.h | 19 +++++++ Playlist/XmlContainer.m | 96 ++++++++++++++++++++++++++++++++ 6 files changed, 245 insertions(+), 7 deletions(-) create mode 100644 Playlist/XmlContainer.h create mode 100644 Playlist/XmlContainer.m diff --git a/Application/AppController.m b/Application/AppController.m index aa6face88..5e7e67550 100644 --- a/Application/AppController.m +++ b/Application/AppController.m @@ -248,8 +248,19 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ } [[playlistController undoManager] disableUndoRegistration]; - NSString *filename = @"~/Library/Application Support/Cog/Default.m3u"; - [playlistLoader addURL:[NSURL fileURLWithPath:[filename stringByExpandingTildeInPath]]]; + NSString *basePath = [@"~/Library/Application Support/Cog/" stringByExpandingTildeInPath]; + NSString *oldFilename = @"Default.m3u"; + NSString *newFilename = @"Default.xml"; + + 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]; int lastStatus = [[NSUserDefaults standardUserDefaults] integerForKey:@"lastPlaybackStatus"]; @@ -292,10 +303,14 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ [fileManager createDirectoryAtPath: folder withIntermediateDirectories:NO attributes:nil error:nil]; } - NSString *fileName = @"Default.m3u"; - - [playlistLoader saveM3u:[folder stringByAppendingPathComponent: fileName]]; - + NSString * fileName = @"Default.xml"; + + [playlistLoader saveXml:[folder stringByAppendingPathComponent: fileName]]; + + fileName = @"Default.m3u"; + + NSError *error; + [[NSFileManager defaultManager] removeItemAtPath:[folder stringByAppendingPathComponent:fileName] error:&error]; } - (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 458b472bc..fc52f03b9 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -147,6 +147,7 @@ 8359009D17FF06570060F3ED /* ArchiveSource.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8359FF3117FEF35D0060F3ED /* ArchiveSource.bundle */; }; 8360EF6D17F92E56005208A4 /* HighlyComplete.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8360EF0517F92B24005208A4 /* HighlyComplete.bundle */; }; 8375B36517FFEF130092A79F /* Opus.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 8375B05717FFEA410092A79F /* Opus.bundle */; }; + 8399D4E21805A55000B503B1 /* XmlContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = 8399D4E01805A55000B503B1 /* XmlContainer.m */; }; 83BCB8DE17FC971300760340 /* FFMPEG.bundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = B09E94350D747F7B0064F138 /* FFMPEG.bundle */; }; 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; @@ -748,6 +749,8 @@ 8359FF2C17FEF35C0060F3ED /* ArchiveSource.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ArchiveSource.xcodeproj; path = Plugins/ArchiveSource/ArchiveSource.xcodeproj; sourceTree = ""; }; 8360EF0017F92B23005208A4 /* HighlyComplete.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = HighlyComplete.xcodeproj; path = Plugins/HighlyComplete/HighlyComplete.xcodeproj; sourceTree = ""; }; 8375B05117FFEA400092A79F /* Opus.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Opus.xcodeproj; path = Plugins/Opus/Opus.xcodeproj; sourceTree = ""; }; + 8399D4E01805A55000B503B1 /* XmlContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XmlContainer.m; sourceTree = ""; }; + 8399D4E11805A55000B503B1 /* XmlContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XmlContainer.h; sourceTree = ""; }; 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 8E07AB760AAC930B00A4B32F /* PreferencesController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; name = PreferencesController.h; path = Preferences/PreferencesController.h; sourceTree = ""; }; 8E07AB770AAC930B00A4B32F /* PreferencesController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; name = PreferencesController.m; path = Preferences/PreferencesController.m; sourceTree = ""; }; @@ -1407,6 +1410,8 @@ 8E75752A09F31D5A0080F1EE /* Playlist */ = { isa = PBXGroup; children = ( + 8399D4E01805A55000B503B1 /* XmlContainer.m */, + 8399D4E11805A55000B503B1 /* XmlContainer.h */, 8E75752D09F31D5A0080F1EE /* PlaylistController.h */, 8E75752E09F31D5A0080F1EE /* PlaylistController.m */, 8E75753109F31D5A0080F1EE /* PlaylistView.h */, @@ -1952,6 +1957,7 @@ 17D1B25D0F633A4F00694C57 /* PreferencePluginController.m in Sources */, 171CB3DC0F63670D0047EF0A /* PreferencesWindow.m in Sources */, 1778D3CA0F645BF00037E7A0 /* MissingAlbumArtTransformer.m in Sources */, + 8399D4E21805A55000B503B1 /* XmlContainer.m in Sources */, 17FAEBAC0F662985007C8707 /* ToolTipTextField.m in Sources */, F6F96719102C709000D2C9B4 /* NSString+FinderCompare.m in Sources */, ); diff --git a/Playlist/PlaylistLoader.h b/Playlist/PlaylistLoader.h index 310684f1e..a88584c1b 100755 --- a/Playlist/PlaylistLoader.h +++ b/Playlist/PlaylistLoader.h @@ -16,6 +16,7 @@ typedef enum { kPlaylistM3u, kPlaylistPls, + kPlaylistXml, } PlaylistType; @interface PlaylistLoader : NSObject { @@ -39,6 +40,7 @@ typedef enum { - (BOOL)save:(NSString *)filename asType:(PlaylistType)type; - (BOOL)saveM3u:(NSString *)filename; - (BOOL)savePls:(NSString *)filename; +- (BOOL)saveXml:(NSString *)filename; // Read info for a playlist entry - (NSDictionary *)readEntryInfo:(PlaylistEntry *)pe; diff --git a/Playlist/PlaylistLoader.m b/Playlist/PlaylistLoader.m index 491f84dd1..39e3544eb 100755 --- a/Playlist/PlaylistLoader.m +++ b/Playlist/PlaylistLoader.m @@ -6,6 +6,8 @@ // Copyright 2007 Vincent Spader All rights reserved. // +#include + #import "PlaylistLoader.h" #import "PlaylistController.h" #import "PlaylistEntry.h" @@ -19,6 +21,8 @@ #import "CogAudio/AudioPropertiesReader.h" #import "CogAudio/AudioMetadataReader.h" +#import "XMlContainer.h" + @implementation PlaylistLoader - (id)init @@ -58,6 +62,10 @@ { return [self save:filename asType:kPlaylistPls]; } + else if ([ext isEqualToString:@"xml"]) + { + return [self save:filename asType:kPlaylistXml]; + } else { return [self save:filename asType:kPlaylistM3u]; @@ -74,6 +82,10 @@ { return [self savePls:filename]; } + else if (type == kPlaylistXml) + { + return [self saveXml:filename]; + } return NO; } @@ -147,6 +159,66 @@ return YES; } +NSMutableDictionary * dictionaryWithPropertiesOfObject(id obj, NSArray * filterList) +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + unsigned count; + objc_property_t *properties = class_copyPropertyList([obj class], &count); + + for (int i = 0; i < count; i++) { + NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])]; + if ([filterList containsObject:key]) continue; + + Class classObject = NSClassFromString([key capitalizedString]); + if (classObject) { + id subObj = dictionaryWithPropertiesOfObject([obj valueForKey:key], filterList); + [dict setObject:subObj forKey:key]; + } + else + { + id value = [obj valueForKey:key]; + if(value) [dict setObject:value forKey:key]; + } + } + + free(properties); + + return dict; +} + +- (BOOL)saveXml:(NSString *)filename +{ + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:filename createFile:YES]; + if (!fileHandle) { + return NO; + } + [fileHandle truncateFileAtOffset:0]; + + NSArray * filterList = [NSArray arrayWithObjects:@"display", @"length", @"path", @"filename", @"status", @"statusMessage", @"spam", @"stopAfter", @"shuffleIndex", @"index", @"current", @"queued", @"currentPosition", @"queuePosition", @"error", @"removed", @"url", nil]; + + NSMutableArray * topLevel = [[NSMutableArray alloc] init]; + + for (PlaylistEntry *pe in [playlistController content]) + { + NSMutableDictionary * dict = dictionaryWithPropertiesOfObject(pe, filterList); + + NSString *path = [self relativePathFrom:filename toURL:[pe URL]]; + + [dict setObject:path forKey:@"URL"]; + + [topLevel addObject:dict]; + } + + NSData * data = [NSPropertyListSerialization dataWithPropertyList:topLevel format:NSPropertyListXMLFormat_v1_0 options:0 error:0]; + + [fileHandle writeData:data]; + + [fileHandle closeFile]; + + return YES; +} + - (NSArray *)fileURLsAtPath:(NSString *)path { NSFileManager *manager = [NSFileManager defaultManager]; @@ -181,6 +253,7 @@ NSMutableArray *containedURLs = [NSMutableArray array]; NSMutableArray *fileURLs = [NSMutableArray array]; NSMutableArray *validURLs = [NSMutableArray array]; + NSArray *xmlURLs = nil; if (!urls) return [NSArray array]; @@ -235,6 +308,10 @@ //Make sure the container isn't added twice. [uniqueURLs addObjectsFromArray:containedURLs]; } + else if ([[[[url path] pathExtension] lowercaseString] isEqualToString:@"xml"]) + { + xmlURLs = [XmlContainer entriesForContainerURL:url]; + } else { [fileURLs addObject:url]; @@ -277,8 +354,11 @@ } //Create actual entries + int count = [validURLs count]; + if (xmlURLs) count += [xmlURLs count]; + int i; - NSMutableArray *entries = [NSMutableArray arrayWithCapacity:[validURLs count]]; + NSMutableArray *entries = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < [validURLs count]; i++) { NSURL *url = [validURLs objectAtIndex:i]; @@ -297,6 +377,24 @@ [pe release]; } + + for (i = 0; i < [xmlURLs count]; i++) + { + NSDictionary * entry = [xmlURLs objectAtIndex:i]; + + PlaylistEntry *pe; + if ([[entry objectForKey:@"URL"] isFileURL]) + pe = [[FilePlaylistEntry alloc] init]; + else + pe = [[PlaylistEntry alloc] init]; + + [pe setValuesForKeysWithDictionary:entry]; + pe.index = index+i+[validURLs count]; + pe.queuePosition = -1; + [entries addObject:pe]; + + [pe release]; + } NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, [entries count])]; @@ -312,6 +410,8 @@ { for (PlaylistEntry *pe in entries) { + if ([pe metadataLoaded]) continue; + NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init]; NSInvocationOperation *readEntryInfoOperation; diff --git a/Playlist/XmlContainer.h b/Playlist/XmlContainer.h new file mode 100644 index 000000000..057c1b945 --- /dev/null +++ b/Playlist/XmlContainer.h @@ -0,0 +1,19 @@ +// +// XmlContainer.h +// Xml +// +// Created by Christopher Snowhill on 10/9/13. +// Copyright 2013 __NoWork, Inc__. All rights reserved. +// + +#import + +@interface XmlContainer : NSObject { + +} + ++ (NSURL *)urlForPath:(NSString *)path relativeTo:(NSString *)baseFilename; + ++ (NSArray *)entriesForContainerURL:(NSURL *)url; + +@end diff --git a/Playlist/XmlContainer.m b/Playlist/XmlContainer.m new file mode 100644 index 000000000..91a2ec619 --- /dev/null +++ b/Playlist/XmlContainer.m @@ -0,0 +1,96 @@ +// +// XmlContainer.m +// Xml +// +// Created by Christopher Snowhill on 10/9/13. +// Copyright 2013 __NoWork, Inc__. All rights reserved. +// + +#import "XmlContainer.h" + +#import + +@implementation XmlContainer + ++ (NSURL *)urlForPath:(NSString *)path relativeTo:(NSString *)baseFilename +{ + 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; + } + } + NSLog(@"Fragment: %@", fragment); + + if (![unixPath hasPrefix:@"/"]) { + //Only relative paths would have windows backslashes. + [unixPath replaceOccurrencesOfString:@"\\" withString:@"/" options:0 range:NSMakeRange(0, [unixPath length])]; + + NSString *basePath = [[[baseFilename stringByStandardizingPath] stringByDeletingLastPathComponent] stringByAppendingString:@"/"]; + + [unixPath insertString:basePath atIndex:0]; + } + + //Append the fragment + NSURL *url = [NSURL URLWithString:[[[NSURL fileURLWithPath:unixPath] absoluteString] stringByAppendingString: fragment]]; + [unixPath release]; + return url; +} + ++ (NSArray *)entriesForContainerURL:(NSURL *)url +{ + if (![url isFileURL]) + return [NSArray array]; + + NSError *nserr; + + NSString *error; + + NSString *filename = [url path]; + + NSString * contents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&nserr]; + + NSData* plistData = [contents dataUsingEncoding:NSUTF8StringEncoding]; + + NSPropertyListFormat format; + NSArray* plist = [NSPropertyListSerialization propertyListFromData:plistData mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error]; + if(!plist){ + NSLog(@"Error: %@",error); + [error release]; + return nil; + } + + NSDictionary *entry; + NSEnumerator *e = [plist objectEnumerator]; + NSMutableArray *entries = [NSMutableArray array]; + + while ((entry = [e nextObject])) + { + NSMutableDictionary * preparedEntry = [NSMutableDictionary dictionaryWithDictionary:entry]; + + [preparedEntry setObject:[self urlForPath:[preparedEntry objectForKey:@"URL"] relativeTo:filename] forKey:@"URL"]; + + [entries addObject:[NSDictionary dictionaryWithDictionary:preparedEntry]]; + } + + return entries; +} + +@end