From 15f1aa2ebdc88e46d94851640ad0816f213c1c48 Mon Sep 17 00:00:00 2001 From: vspader Date: Fri, 9 Mar 2007 01:16:06 +0000 Subject: [PATCH] Cleaned up code. Added PlaylistLoader class properly, with support for m3u and pls formats. Changed default playlist to m3u. Added bugs GALORE! --- Application/AppController.h | 21 +- Application/AppController.m | 53 +-- Audio/AudioPlayer.h | 1 + Audio/AudioPlayer.m | 7 + Cog.xcodeproj/project.pbxproj | 44 +-- English.lproj/MainMenu.nib/classes.nib | 13 +- English.lproj/MainMenu.nib/info.nib | 10 +- English.lproj/MainMenu.nib/keyedobjects.nib | Bin 62656 -> 62672 bytes Feedback/FeedbackController.m | 6 +- FileDrawer/FileTreeController.h | 8 +- FileDrawer/FileTreeController.m | 22 +- FileDrawer/FileTreeWatcher.h | 2 + Playlist/DNDArrayController.m | 12 +- Playlist/PlaylistController.h | 38 +-- Playlist/PlaylistController.m | 354 ++------------------ Playlist/PlaylistEntry.h | 32 +- Playlist/PlaylistEntry.m | 14 +- Playlist/PlaylistLoader.h | 7 +- Playlist/PlaylistLoader.m | 274 +++++++++++++-- 19 files changed, 416 insertions(+), 502 deletions(-) diff --git a/Application/AppController.h b/Application/AppController.h index 574377e76..dd510828b 100644 --- a/Application/AppController.h +++ b/Application/AppController.h @@ -2,22 +2,23 @@ #import -#import "PlaybackController.h" - -#import "PlaylistController.h" -#import "PlaylistView.h" - -#import "FileTreeController.h" -#import "FileOutlineView.h" - #import "NDHotKeyEvent.h" -#import "AppleRemote.h" +@class PlaybackController; +@class PlaylistController; +@class PlaylistView; +@class FileTreeController; +@class FileOutlineView; +@class AppleRemote; +@class PlaylistLoader; + @interface AppController : NSObject { - IBOutlet PlaylistController *playlistController; IBOutlet PlaybackController *playbackController; + + IBOutlet PlaylistController *playlistController; + IBOutlet PlaylistLoader *playlistLoader; IBOutlet NSPanel *mainWindow; diff --git a/Application/AppController.m b/Application/AppController.m index 3a98603a9..f1844d071 100644 --- a/Application/AppController.m +++ b/Application/AppController.m @@ -1,5 +1,13 @@ #import "AppController.h" #import "KFTypeSelectTableView.h"" +#import "PlaybackController.h" +#import "PlaylistController.h" +#import "PlaylistView.h" +#import "FileTreeController.h" +#import "FileOutlineView.h" +#import "NDHotKeyEvent.h" +#import "AppleRemote.h" +#import "PlaylistLoader.h" @implementation AppController @@ -124,7 +132,7 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ [p setCanChooseDirectories:YES]; [p setAllowsMultipleSelection:YES]; - [p beginSheetForDirectory:nil file:nil types:[playlistController acceptableFileTypes] modalForWindow:mainWindow modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; + [p beginSheetForDirectory:nil file:nil types:[playlistLoader acceptableFileTypes] modalForWindow:mainWindow modalDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; // [p beginForDirectory:nil file:nil types:[playlistController acceptableFileTypes] modelessDelegate:self didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; /* if ([p runModalForTypes:[playlistController acceptableFileTypes]] == NSOKButton) @@ -138,7 +146,7 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ { if (returnCode == NSOKButton) { - [playlistController addPaths:[panel filenames] sort:YES]; + [playlistLoader addURLs:[panel URLs] sort:YES]; } // [panel release]; @@ -238,8 +246,8 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ } - NSString *filename = @"~/Library/Application Support/Cog/Default.playlist"; - [playlistController loadPlaylist:[filename stringByExpandingTildeInPath]]; + NSString *filename = @"~/Library/Application Support/Cog/Default.m3u"; + [playlistLoader loadM3u:[filename stringByExpandingTildeInPath]]; } - (void)applicationWillTerminate:(NSNotification *)aNotification @@ -257,18 +265,15 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ [fileManager createDirectoryAtPath: folder attributes: nil]; } - NSString *fileName = @"Default.playlist"; + NSString *fileName = @"Default.m3u"; - [playlistController savePlaylist:[folder stringByAppendingPathComponent: fileName]]; + [playlistLoader saveM3u:[folder stringByAppendingPathComponent: fileName]]; } - (IBAction)savePlaylist:(id)sender { - if ([playlistController playlistFilename] == nil) - [self savePlaylistAs:sender]; - - [playlistController savePlaylist:[playlistController playlistFilename]]; + [playlistLoader save]; } - (IBAction)savePlaylistAs:(id)sender { @@ -276,13 +281,11 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ p = [NSSavePanel savePanel]; - [p setAllowedFileTypes:[playlistController acceptablePlaylistTypes]]; + [p setAllowedFileTypes:[playlistLoader acceptablePlaylistTypes]]; - if ([p runModalForDirectory:nil file:[[playlistController playlistFilename] lastPathComponent]] == NSOKButton) + if ([p runModalForDirectory:nil file:[[playlistLoader currentFile] lastPathComponent]] == NSOKButton) { - [playlistController setPlaylistFilename:[p filename]]; - - [playlistController savePlaylist:[p filename]]; + [playlistLoader save:[p filename]]; } } @@ -295,11 +298,9 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ [p setCanChooseDirectories:NO]; [p setAllowsMultipleSelection:NO]; - if ([p runModalForTypes:[playlistController acceptablePlaylistTypes]] == NSOKButton) + if ([p runModalForTypes:[playlistLoader acceptablePlaylistTypes]] == NSOKButton) { - [playlistController setPlaylistFilename:[p filename]]; - - [playlistController loadPlaylist:[p filename]]; + [playlistLoader load:[p filename]]; } [mainWindow makeKeyAndOrderFront:self]; @@ -316,16 +317,24 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { DBLog(@"Adding path: %@", filename); - [playlistController addPaths:[NSArray arrayWithObject:filename] sort:NO]; + [playlistLoader addURLs:[NSArray arrayWithObject:[NSURL fileURLWithPath:filename]] sort:NO]; return YES; } - (void)application:(NSApplication *)theApplication openFiles:(NSArray *)filenames { - DBLog(@"Adding paths: %@", filenames); + //Need to convert to urls + NSMutableArray *urls = [NSMutableArray array]; + NSEnumerator *e = [filenames objectEnumerator]; + NSString *filename; + + while (filename = [e nextObject]) + { + [urls addObject:[NSURL fileURLWithPath:filename]]; + } + [playlistLoader addURLs:urls sort:YES]; - [playlistController addPaths:filenames sort:YES]; [theApplication replyToOpenOrPrint:NSApplicationDelegateReplySuccess]; } diff --git a/Audio/AudioPlayer.h b/Audio/AudioPlayer.h index 6dfe7b1c8..17975d5eb 100644 --- a/Audio/AudioPlayer.h +++ b/Audio/AudioPlayer.h @@ -45,6 +45,7 @@ - (void)setNextStream:(NSURL *)url withUserInfo:(id)userInfo; + (NSArray *)fileTypes; ++ (NSArray *)schemes; @end diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index a799c5360..8a63e4a7c 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -280,5 +280,12 @@ return [types allObjects]; } ++ (NSArray *)schemes +{ + PluginController *pluginController = [PluginController sharedPluginController]; + + return [[pluginController sources] allKeys]; +} + @end diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 6c1cc6b08..12553ff21 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 170680630B950158006BA573 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 170680620B950158006BA573 /* Growl.framework */; }; 170680840B950164006BA573 /* Growl.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 170680620B950158006BA573 /* Growl.framework */; }; 171678C00AC8C39E00C28CF3 /* SmartFolderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */; }; + 1755E1F80BA0D2B600CA3560 /* PlaylistLoader.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1755E1F60BA0D2B600CA3560 /* PlaylistLoader.h */; }; + 1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 1755E1F70BA0D2B600CA3560 /* PlaylistLoader.m */; }; 175D8B640B9A514C005B3CD9 /* CogAudio.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 175D8B630B9A514C005B3CD9 /* CogAudio.framework */; }; 175D8B670B9A5153005B3CD9 /* CogAudio.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 175D8B630B9A514C005B3CD9 /* CogAudio.framework */; }; 1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */; }; @@ -138,6 +140,7 @@ 175D8B670B9A5153005B3CD9 /* CogAudio.framework in CopyFiles */, 170680840B950164006BA573 /* Growl.framework in CopyFiles */, 17F94CCD0B8D090800A34E87 /* Sparkle.framework in CopyFiles */, + 1755E1F80BA0D2B600CA3560 /* PlaylistLoader.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -150,6 +153,8 @@ 170680620B950158006BA573 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Growl.framework; path = ThirdParty/Frameworks/Growl.framework; sourceTree = ""; }; 171678BD0AC8C39E00C28CF3 /* SmartFolderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmartFolderNode.h; sourceTree = ""; }; 171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SmartFolderNode.m; sourceTree = ""; }; + 1755E1F60BA0D2B600CA3560 /* PlaylistLoader.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PlaylistLoader.h; sourceTree = ""; }; + 1755E1F70BA0D2B600CA3560 /* PlaylistLoader.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PlaylistLoader.m; sourceTree = ""; }; 175D8B630B9A514C005B3CD9 /* CogAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CogAudio.framework; path = Audio/build/Release/CogAudio.framework; sourceTree = ""; }; 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobbler.h; sourceTree = ""; }; 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobbler.m; sourceTree = ""; }; @@ -613,6 +618,8 @@ children = ( 8E1296D80A2BA9CE00443124 /* PlaylistHeaderView.h */, 8E1296D90A2BA9CE00443124 /* PlaylistHeaderView.m */, + 1755E1F60BA0D2B600CA3560 /* PlaylistLoader.h */, + 1755E1F70BA0D2B600CA3560 /* PlaylistLoader.m */, 8E75752B09F31D5A0080F1EE /* DNDArrayController.h */, 8E75752C09F31D5A0080F1EE /* DNDArrayController.m */, 8E75752D09F31D5A0080F1EE /* PlaylistController.h */, @@ -638,7 +645,6 @@ 8EFFCD410AA093AF00C458A5 /* FileDrawer */ = { isa = PBXGroup; children = ( - 8EFFCD540AA093AF00C458A5 /* UKKQueue */, 8EFFCD440AA093AF00C458A5 /* FileIconCell.h */, 8EFFCD450AA093AF00C458A5 /* FileIconCell.m */, 8EFFCD500AA093AF00C458A5 /* PathIcon.h */, @@ -661,13 +667,6 @@ path = FileDrawer; sourceTree = ""; }; - 8EFFCD540AA093AF00C458A5 /* UKKQueue */ = { - isa = PBXGroup; - children = ( - ); - path = UKKQueue; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -796,6 +795,7 @@ 1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */, 1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */, 1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */, + 1755E1F90BA0D2B600CA3560 /* PlaylistLoader.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -836,21 +836,9 @@ buildSettings = { COPY_PHASE_STRIP = NO; FRAMEWORK_SEARCH_PATHS = ( - "$(SRCROOT)/Frameworks/CogAudio/build/Release", + "$(SRCROOT)/Audio/build/Release", "$(SRCROOT)/ThirdParty/Frameworks/", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_1)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_2)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_3)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_4)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_5)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_6)", ); - FRAMEWORK_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)/Frameworks/CogAudio/build/Debug\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_2 = "\"$(SRCROOT)/Audio/build/Release\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_3 = "\"$(SRCROOT)/Audio/build/Debug\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_4 = "\"$(SRCROOT)/Audio/build/Release\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_5 = "\"$(SRCROOT)/Audio/build/Release\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_6 = "\"$(SRCROOT)/Audio/build/Debug\""; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_ENABLE_OBJC_EXCEPTIONS = YES; @@ -881,21 +869,9 @@ i386, ); FRAMEWORK_SEARCH_PATHS = ( - "$(SRCROOT)/Frameworks/CogAudio/build/Release", + "$(SRCROOT)/Audio/build/Release", "$(SRCROOT)/ThirdParty/Frameworks/", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_1)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_2)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_3)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_4)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_5)", - "$(FRAMEWORK_SEARCH_PATHS_QUOTED_6)", ); - FRAMEWORK_SEARCH_PATHS_QUOTED_1 = "\"$(SRCROOT)/Frameworks/CogAudio/build/Debug\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_2 = "\"$(SRCROOT)/Audio/build/Release\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_3 = "\"$(SRCROOT)/Audio/build/Debug\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_4 = "\"$(SRCROOT)/Audio/build/Release\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_5 = "\"$(SRCROOT)/Audio/build/Release\""; - FRAMEWORK_SEARCH_PATHS_QUOTED_6 = "\"$(SRCROOT)/Audio/build/Debug\""; GCC_ENABLE_OBJC_EXCEPTIONS = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = NO; GCC_MODEL_TUNING = G5; diff --git a/English.lproj/MainMenu.nib/classes.nib b/English.lproj/MainMenu.nib/classes.nib index 8c8c8f6f5..7c9b6d260 100644 --- a/English.lproj/MainMenu.nib/classes.nib +++ b/English.lproj/MainMenu.nib/classes.nib @@ -41,6 +41,7 @@ playButton = NSButton; playbackController = PlaybackController; playlistController = PlaylistController; + playlistLoader = PlaylistLoader; playlistView = PlaylistView; prevButton = NSButton; repeatButton = NSButton; @@ -87,7 +88,7 @@ { CLASS = FileTreeController; LANGUAGE = ObjC; - OUTLETS = {playlistController = PlaylistController; }; + OUTLETS = {playlistLoader = PlaylistLoader; }; SUPERCLASS = NSTreeController; }, {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, @@ -127,16 +128,22 @@ }, { ACTIONS = { - showFileInFinder = id; - sortByPath = id; + showEntryInFinder = id; takeRepeatFromObject = id; takeShuffleFromObject = id; }; CLASS = PlaylistController; LANGUAGE = ObjC; + OUTLETS = {playlistLoader = PlaylistLoader; }; SUPERCLASS = DNDArrayController; }, {CLASS = PlaylistEntry; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + CLASS = PlaylistLoader; + LANGUAGE = ObjC; + OUTLETS = {playlistController = PlaylistController; }; + SUPERCLASS = NSObject; + }, { ACTIONS = {shufflePlaylist = id; sortByPath = id; toggleColumn = id; }; CLASS = PlaylistView; diff --git a/English.lproj/MainMenu.nib/info.nib b/English.lproj/MainMenu.nib/info.nib index 935210722..dff491ab7 100644 --- a/English.lproj/MainMenu.nib/info.nib +++ b/English.lproj/MainMenu.nib/info.nib @@ -15,7 +15,7 @@ 463 669 637 341 145 0 0 1680 1028 513 - 1026 274 125 137 0 0 1680 1028 + 352 836 125 137 0 0 1680 1028 IBFramework Version 446.1 @@ -32,12 +32,12 @@ 4 IBOpenObjects - 463 - 29 - 1156 - 1307 513 + 29 + 463 + 1156 21 + 1307 IBSystem Version 8L2127 diff --git a/English.lproj/MainMenu.nib/keyedobjects.nib b/English.lproj/MainMenu.nib/keyedobjects.nib index d5ce8cebf48a495049f58cdc14765966be78a981..8d48334cfff539f359e13bbcbb037db52b744dc8 100644 GIT binary patch delta 28671 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 delta 28642 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 T|2y^S{a -#import "FileTreeWatcher.h" -#import "PlaylistController.h" + +@class FileTreeWatcher; +@class PlaylistLoader; @interface FileTreeController : NSTreeController { - IBOutlet PlaylistController *playlistController; + IBOutlet PlaylistLoader *playlistLoader; NSString *rootPath; @@ -21,5 +22,6 @@ - (id)rootPath; - (void)setRootPath:(id)r; +- (void) refreshRoot; @end diff --git a/FileDrawer/FileTreeController.m b/FileDrawer/FileTreeController.m index 922491c6c..ce9c7ad5c 100644 --- a/FileDrawer/FileTreeController.m +++ b/FileDrawer/FileTreeController.m @@ -7,9 +7,11 @@ // #import "FileTreeController.h" +#import "FileTreeWatcher.h" #import "DirectoryNode.h" #import "ImageTextCell.h" #import "KFTypeSelectTableView.h" +#import "PlaylistLoader.h" @implementation FileTreeController @@ -109,7 +111,7 @@ - (NSArray *)acceptableFileTypes { - return [playlistController acceptableFileTypes]; + return [playlistLoader acceptableFileTypes]; } - (FileTreeWatcher *)watcher @@ -119,22 +121,21 @@ - (BOOL)outlineView:(NSOutlineView *)olv writeItems:(NSArray*)items toPasteboard:(NSPasteboard*)pboard { //Get selected paths - NSLog(@"Items: %@", items); NSMutableArray *paths = [NSMutableArray arrayWithCapacity:[items count]]; - id p; NSEnumerator *e = [items objectEnumerator]; + id p; while (p = [e nextObject]) { int i; PathNode *n = nil; NSIndexPath *ip = [p indexPath]; - NSLog(@"Content: %@", n); + for (i = 0; i < [ip length]; i++) { NSArray *a = (n == nil) ? [self content] : [n subpaths]; n = [a objectAtIndex:[ip indexAtPosition:i]]; } - NSLog(@"Path: %@", n); + [paths addObject:[n path]]; } @@ -193,17 +194,20 @@ //End type-select - (void)addSelectedToPlaylist { - NSMutableArray *paths = [[NSMutableArray alloc] init]; + NSMutableArray *urls = [[NSMutableArray alloc] init]; NSArray *nodes = [self selectedObjects]; NSEnumerator *e = [nodes objectEnumerator]; id n; while (n = [e nextObject]) { - [paths addObject:[n path]]; + NSURL *url = [[NSURL alloc] initFileURLWithPath:[n path]]; + [urls addObject:url]; + [url release]; } - [playlistController addPaths:paths sort:YES]; - [paths release]; + NSLog(@"Adding URLs: %@", urls); + [playlistLoader addURLs:urls sort:YES]; + [urls release]; } diff --git a/FileDrawer/FileTreeWatcher.h b/FileDrawer/FileTreeWatcher.h index 3aa4cda9c..f80b4c70e 100644 --- a/FileDrawer/FileTreeWatcher.h +++ b/FileDrawer/FileTreeWatcher.h @@ -19,4 +19,6 @@ - (void)addPath: (NSString *)path; - (void)removePath: (NSString *)path; +-(void) setDelegate: (id)d; + @end diff --git a/Playlist/DNDArrayController.m b/Playlist/DNDArrayController.m index e8c0576a8..e5bd6aef8 100755 --- a/Playlist/DNDArrayController.m +++ b/Playlist/DNDArrayController.m @@ -11,15 +11,8 @@ NSString *iTunesDropType = @"CorePasteboardFlavorType 0x6974756E"; - (void)awakeFromNib { // register for drag and drop - NSLog(@"AWOKE"); - [tableView registerForDraggedTypes:[NSArray arrayWithObjects:MovedRowsType, NSFilenamesPboardType, iTunesDropType, nil]]; - -// [tableView setVerticalMotionCanBeginDrag:YES]; -// [tableView setAllowsMultipleSelection:NO]; -// [super awakeFromNib]; -// DBLog(@"HERE: %@", [tableView registeredDraggedTypes]); } @@ -27,8 +20,6 @@ NSString *iTunesDropType = @"CorePasteboardFlavorType 0x6974756E"; writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pboard { - NSLog(@"WRITE ROWS"); - NSData *data; data = [NSKeyedArchiver archivedDataWithRootObject:rows]; @@ -66,11 +57,10 @@ NSString *iTunesDropType = @"CorePasteboardFlavorType 0x6974756E"; { row = 0; } - NSLog(@"ACCEPTATING"); + // if drag source is self, it's a move if ([info draggingSource] == tableView) { -// DBLog(@"ACCEPTATED"); NSArray *rows = [NSKeyedUnarchiver unarchiveObjectWithData:[[info draggingPasteboard] dataForType: MovedRowsType]]; NSIndexSet *indexSet = [self indexSetFromRows:rows]; diff --git a/Playlist/PlaylistController.h b/Playlist/PlaylistController.h index 3bee2101e..fb248b3d6 100644 --- a/Playlist/PlaylistController.h +++ b/Playlist/PlaylistController.h @@ -8,13 +8,13 @@ #import #import "DNDArrayController.h" -#import "PlaylistEntry.h" + +@class PlaylistLoader; +@class PlaylistEntry; @interface PlaylistController : DNDArrayController { - NSArray *acceptableFileTypes; - NSArray *acceptablePlaylistTypes; - - NSString *playlistFilename; + IBOutlet PlaylistLoader *playlistLoader; + NSString *totalTimeDisplay; NSMutableArray *shuffleList; @@ -27,22 +27,12 @@ int selectedRow; } -//All these return the number of things actually added //Private Methods -//- (int)addPath:(NSString *)path; -//- (int)insertPath:(NSString *)path atIndex:(int)index; -//- (int)insertFile:(NSString *)filename atIndex:(int)index; -//- (int)addFile:(NSString *)filename; - (void)updateIndexesFromRow:(int) row; - (void)updateTotalTime; //PUBLIC METHODS -- (void)addPaths:(NSArray *)paths sort:(BOOL)sort; -- (void)insertPaths:(NSArray *)paths atIndex:(int)index sort:(BOOL)sort; - -- (NSArray *)acceptableFileTypes; - - (void)setShuffle:(BOOL)s; - (BOOL)shuffle; - (void)setRepeat:(BOOL)r; @@ -51,7 +41,10 @@ - (IBAction)takeShuffleFromObject:(id)sender; - (IBAction)takeRepeatFromObject:(id)sender; -- (IBAction)sortByPath:(id)sender; +- (IBAction)sortByPath; +- (IBAction)randomizeList; + +- (IBAction)showEntryInFinder:(id)sender; - (void)setTotalTimeDisplay:(NSString *)ttd; - (NSString *)totalTimeDisplay; @@ -63,21 +56,10 @@ - (BOOL)next; - (BOOL)prev; -- (PlaylistEntry *)entryAtIndex:(int)i; - - (void)addShuffledListToBack; - (void)addShuffledListToFront; - (void)resetShuffleList; -//load/save playlist -- (void)loadPlaylist:(NSString *)filename; -- (void)savePlaylist:(NSString *)filename; - -- (NSString *)playlistFilename; -- (void)setPlaylistFilename:(NSString *)pf; -- (NSArray *)acceptablePlaylistTypes; - -- (IBAction)showFileInFinder:(id)sender; - - (void)handlePlaylistViewHeaderNotification:(NSNotification*)notif; + @end diff --git a/Playlist/PlaylistController.m b/Playlist/PlaylistController.m index 27c993ffc..197ed881d 100644 --- a/Playlist/PlaylistController.m +++ b/Playlist/PlaylistController.m @@ -6,6 +6,7 @@ // Copyright 2005 Vincent Spader All rights reserved. // +#import "PlaylistLoader.h" #import "PlaylistController.h" #import "PlaylistEntry.h" #import "Shuffle.h" @@ -22,11 +23,7 @@ if (self) { - acceptableFileTypes = [[AudioPlayer fileTypes] retain]; - acceptablePlaylistTypes = [[NSArray alloc] initWithObjects:@"playlist",nil]; - shuffleList = [[NSMutableArray alloc] init]; -// DBLog(@"DAH BUTTER CHORNAR: %@", history); } return self; @@ -40,150 +37,6 @@ [ns addObserver:self selector:@selector(handlePlaylistViewHeaderNotification:) name:@"PlaylistViewColumnSeparatorDoubleClick" object:nil]; } -- (NSArray *)filesAtPath:(NSString *)path -{ - BOOL isDir; - NSFileManager *manager; - - manager = [NSFileManager defaultManager]; - - NSLog(@"Checking if path is a directory: %@", path); - if ([manager fileExistsAtPath:path isDirectory:&isDir] && isDir == YES) - { - DBLog(@"path is directory"); - int j; - NSArray *subpaths; - NSMutableArray *validPaths = [[NSMutableArray alloc] init]; - - subpaths = [manager subpathsAtPath:path]; - - DBLog(@"Subpaths: %@", subpaths); - for (j = 0; j < [subpaths count]; j++) - { - NSString *filepath; - - filepath = [NSString pathWithComponents:[NSArray arrayWithObjects:path,[subpaths objectAtIndex:j],nil]]; - if ([manager fileExistsAtPath:filepath isDirectory:&isDir] && isDir == NO) - { - if ([acceptableFileTypes containsObject:[[filepath pathExtension] lowercaseString]] && [[NSFileManager defaultManager] fileExistsAtPath:filepath]) - { - [validPaths addObject:filepath]; - } - } - } - - return [validPaths autorelease]; - } - else - { - NSLog(@"path is a file"); - if ([acceptableFileTypes containsObject:[[path pathExtension] lowercaseString]] && [[NSFileManager defaultManager] fileExistsAtPath:path]) - { - NSLog(@"RETURNING THING"); - return [NSArray arrayWithObject:path]; - } - else - { - return nil; - } - } -} - -- (void)insertPaths:(NSArray *)paths atIndex:(int)index sort:(BOOL)sort -{ - NSArray *sortedFiles; - NSMutableArray *files = [[NSMutableArray alloc] init]; - NSMutableArray *entries= [[NSMutableArray alloc] init]; - int i; - - if (!paths) - return; - - if (index < 0) - index = 0; - - for(i=0; i < [paths count]; i++) - { - [files addObjectsFromArray:[self filesAtPath:[paths objectAtIndex:i]]]; - NSLog(@"files is: %i", [files count]); - } - - DBLog(@"Sorting paths"); - if (sort == YES) - { - sortedFiles = [files sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; - } - else - { - sortedFiles = files; - } - - for (i = 0; i < [sortedFiles count]; i++) - { - PlaylistEntry *pe = [[PlaylistEntry alloc] init]; - - [pe setURL:[NSURL fileURLWithPath:[sortedFiles objectAtIndex:i]]]; - [pe setIndex:index+i]; - [pe setTitle:[[sortedFiles objectAtIndex:i] lastPathComponent]]; -// [pe performSelectorOnMainThread:@selector(readTags) withObject:nil waitUntilDone:NO]; -// [pe performSelectorOnMainThread:@selector(readInfo) withObject:nil waitUntilDone:NO]; - - [entries addObject:pe]; - [pe release]; - } - - NSRange r = NSMakeRange(index, [entries count]); - NSLog(@"MAking range from: %i to %i", index, index + [entries count]); - NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:r]; - NSLog(@"INDex set: %@", is); - NSLog(@"Adding: %i files", [entries count]); - [self insertObjects:entries atArrangedObjectIndexes:is]; - - if (shuffle == YES) - [self resetShuffleList]; - - [self setSelectionIndex:index]; - - //Other thread will release entries....crazy crazy bad idea...whatever - [NSThread detachNewThreadSelector:@selector(readMetaData:) toTarget:self withObject:entries]; - - [files release]; - - return; -} - -- (void)readMetaData:(id)entries -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - int i; - for (i = 0; i < [entries count]; i++) - { - PlaylistEntry *pe =[entries objectAtIndex:i]; - - [pe readInfoThreaded]; - - [pe readTagsThreaded]; - - //Hack so the display gets updated - if (pe == [self currentEntry]) - [self performSelectorOnMainThread:@selector(setCurrentEntry:) withObject:[self currentEntry] waitUntilDone:YES]; - } - - [self performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO]; - - [entries release]; - [pool release]; -} - -- (void)addPaths:(NSArray *)paths sort:(BOOL)sort -{ - [self insertPaths:paths atIndex:[[self arrangedObjects] count] sort:sort]; -} - -- (NSArray *)acceptableFileTypes -{ - return acceptableFileTypes; -} - (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn @@ -199,27 +52,20 @@ row:(int)row dropOperation:(NSTableViewDropOperation)op { - int i; - NSLog(@"DROPPED"); [super tableView:tv acceptDrop:info row:row dropOperation:op]; if ([info draggingSource] == tableView) { - //DNDArrayController handles moving...still need to update the uhm...indices - NSLog(@"Archive stuff"); + //DNDArrayController handles moving...still need to update the indexes + + int i; NSArray *rows = [NSKeyedUnarchiver unarchiveObjectWithData:[[info draggingPasteboard] dataForType: MovedRowsType]]; - NSLog(@"Whatever"); - NSIndexSet *indexSet = [self indexSetFromRows:rows]; - int firstIndex = [indexSet firstIndex]; + int firstIndex = [[self indexSetFromRows:rows] firstIndex]; + if (firstIndex > row) - { i = row; - } else - { i = firstIndex; - } - NSLog(@"Updating indexes: %i", i); [self updateIndexesFromRow:i]; return YES; @@ -235,10 +81,18 @@ // Get files from a normal file drop (such as from Finder) if ([bestType isEqualToString:NSFilenamesPboardType]) { - NSArray *files = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; + NSMutableArray *urls = [[NSMutableArray alloc] init]; + + NSEnumerator *e = [[[info draggingPasteboard] propertyListForType:NSFilenamesPboardType] objectEnumerator]; + NSString *file; + while (file = [e nextObject]) + { + [urls addObject:[NSURL fileURLWithPath:file]]; + } - NSLog(@"INSERTING PATHS: %@", files); - [self insertPaths:files atIndex:row sort:YES]; + [playlistLoader insertURLs:urls atIndex:row sort:YES]; + + [urls release]; } // Get files from an iTunes drop @@ -246,26 +100,19 @@ NSDictionary *iTunesDict = [pboard propertyListForType:iTunesDropType]; NSDictionary *tracks = [iTunesDict valueForKey:@"Tracks"]; - // Convert the iTunes URLs to filenames - NSMutableArray *files = [[NSMutableArray alloc] init]; + // Convert the iTunes URLs to URLs....MWAHAHAH! + NSMutableArray *urls = [[NSMutableArray alloc] init]; + NSEnumerator *e = [[tracks allValues] objectEnumerator]; NSDictionary *trackInfo; - NSURL *url; while (trackInfo = [e nextObject]) { - url = [[NSURL alloc] initWithString:[trackInfo valueForKey:@"Location"]]; - if ([url isFileURL]) { - [files addObject:[url path]]; - } - - [url release]; + [urls addObject:[NSURL URLWithString:[trackInfo valueForKey:@"Location"]]]; } - NSLog(@"INSERTING ITUNES PATHS: %@", files); - [self insertPaths:files atIndex:row sort:YES]; - [files release]; + [playlistLoader insertURLs:urls atIndex:row sort:YES]; + [urls release]; } - NSLog(@"UPDATING"); [self updateIndexesFromRow:row]; [self updateTotalTime]; @@ -295,7 +142,6 @@ [ttd retain]; [totalTimeDisplay release]; totalTimeDisplay = ttd; - NSLog(@"Displaying: %@", ttd); } - (NSString *)totalTimeDisplay; @@ -305,7 +151,6 @@ - (void)updateIndexesFromRow:(int) row { -// DBLog(@"UPDATE INDEXES: %i", row); int j; for (j = row; j < [[self arrangedObjects] count]; j++) { @@ -318,20 +163,8 @@ - (void)removeObjectsAtArrangedObjectIndexes:(NSIndexSet *)indexes { - unsigned int *indexBuffer; - NSMutableArray *a = [[NSMutableArray alloc] init]; - int i; - - //10.3 fix - indexBuffer = malloc([indexes count]*sizeof(unsigned int)); - [indexes getIndexes:indexBuffer maxCount:[indexes count] inIndexRange:nil]; - for (i = 0; i < [indexes count]; i++) - { - NSLog(@"REMOVING FROM INDEX: %i", indexBuffer[i]); - [a addObject:[[self arrangedObjects] objectAtIndex:(indexBuffer[i])]]; - } - -// a = [[self arrangedObjects] objectsAtIndexes:indexes]; //10.4 only + NSLog(@"REMOVING"); + NSArray *a = [[self arrangedObjects] objectsAtIndexes:indexes]; //Screw 10.3 if ([a containsObject:currentEntry]) { @@ -344,8 +177,6 @@ if (shuffle == YES) [self resetShuffleList]; - - [a release]; } - (void)setSortDescriptors:(NSArray *)sortDescriptors @@ -363,7 +194,7 @@ [super setSortDescriptors:sortDescriptors]; } -- (IBAction)sortByPath:(id)sender +- (IBAction)sortByPath { NSSortDescriptor *s = [[NSSortDescriptor alloc] initWithKey:@"url" ascending:YES selector:@selector(compare:)]; @@ -378,7 +209,7 @@ [self updateIndexesFromRow:0]; } -- (void)randomizeList +- (IBAction)randomizeList { [self setSortDescriptors:nil]; @@ -423,9 +254,9 @@ return [[self arrangedObjects] objectAtIndex:i]; } + - (PlaylistEntry *)shuffledEntryAtIndex:(int)i { - // NSLog(@"SHUFFLE: %i %i %i %i", i, shuffleIndex, offset, [shuffleList count]); while (i < 0) { if (repeat == YES) @@ -455,67 +286,6 @@ return [shuffleList objectAtIndex:i]; } -/*- (PlaylistEntry *)entryAtOffset:(int)offset -{ - if (shuffle == YES) - { - int i = shuffleIndex; - i += offset; -// NSLog(@"SHUFFLE: %i %i %i %i", i, shuffleIndex, offset, [shuffleList count]); - while (i < 0) - { - if (repeat == YES) - { - [self addShuffledListToFront]; - //change i appropriately - i += [[self arrangedObjects] count]; - } - else - { - return nil; - } - } - while (i >= [shuffleList count]) - { - if (repeat == YES) - { - NSLog(@"Adding shuffled list to back!"); - [self addShuffledListToBack]; - } - else - { - return nil; - } - } - - return [shuffleList objectAtIndex:i]; - } - else - { - int i; - i = [currentEntry index]; - i += (offset-1); - - if (i < 0) - { - if (repeat == YES) - i += [[self arrangedObjects] count]; - else - return nil; - } - else if (i >= [[self arrangedObjects] count]) - { - if (repeat == YES) - i -= [[self arrangedObjects] count]; - else - return nil; - } - - return [[self arrangedObjects] objectAtIndex:i]; - } -} -*/ - - (PlaylistEntry *)getNextEntry:(PlaylistEntry *)pe { if (shuffle == YES) @@ -673,75 +443,18 @@ { [super setFilterPredicate:filterPredicate]; - int j; - for (j = 0; j < [[self content] count]; j++) - { - PlaylistEntry *p; - p = [[self content] objectAtIndex:j]; - - [p setIndex:-1]; - } - [self updateIndexesFromRow:0]; } -- (void)savePlaylist:(NSString *)filename -{ -// DBLog(@"SAVING PLAYLIST: %@", filename); - NSString *fileContents; - NSMutableArray *filenames = [NSMutableArray array]; - - NSEnumerator *enumerator; - PlaylistEntry *entry; - enumerator = [[self content] objectEnumerator]; - while (entry = [enumerator nextObject]) - { - [filenames addObject:[[entry url] path]]; - } - - fileContents = [filenames componentsJoinedByString:@"\n"]; - [fileContents writeToFile:filename atomically:NO]; -} - -- (void)loadPlaylist:(NSString *)filename -{ - NSString *fileContents; - - [self removeObjects:[self arrangedObjects]]; - fileContents = [NSString stringWithContentsOfFile:filename]; - if (fileContents) - { - NSArray *filenames = [fileContents componentsSeparatedByString:@"\n"]; -// DBLog(@"filenames: %@", filenames); - [self addPaths:filenames sort:NO]; - } -} - -- (NSArray *)acceptablePlaylistTypes -{ - return acceptablePlaylistTypes; -} - -- (NSString *)playlistFilename -{ - return playlistFilename; -} -- (void)setPlaylistFilename:(NSString *)pf -{ - [pf retain]; - [playlistFilename release]; - - playlistFilename = pf; -} - -- (IBAction)showFileInFinder:(id)sender +- (IBAction)showEntryInFinder:(id)sender { NSWorkspace* ws = [NSWorkspace sharedWorkspace]; if ([self selectionIndex] < 0) return; - PlaylistEntry* curr = [self entryAtIndex:[self selectionIndex]]; - [ws selectFile:[[curr url] path] inFileViewerRootedAtPath:[[curr url] path]]; + NSURL *url = [[self entryAtIndex:[self selectionIndex]] url]; + if ([url isFileURL]) + [ws selectFile:[url path] inFileViewerRootedAtPath:[url path]]; } - (void)handlePlaylistViewHeaderNotification:(NSNotification*)notif @@ -750,6 +463,9 @@ NSNumber *colIdx = [[notif userInfo] objectForKey:@"column"]; NSTableColumn *col = [[tv tableColumns] objectAtIndex:[colIdx intValue]]; + //Change to use NSSelectorFromString and NSMethodSignature returnType, see http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/chapter_5_section_7.html for info + //Maybe we can pull the column bindings out somehow, instead of selectorfromstring + // find which function to call on PlaylistEntry* SEL sel; NSString* identifier = [col identifier]; diff --git a/Playlist/PlaylistEntry.h b/Playlist/PlaylistEntry.h index c16bd8983..51da6d7fb 100644 --- a/Playlist/PlaylistEntry.h +++ b/Playlist/PlaylistEntry.h @@ -34,16 +34,16 @@ int displayIdx; } --(void)setIndex:(int)i; --(int)index; +- (void)setIndex:(int)i; +- (int)index; --(void)setShuffleIndex:(int)si; --(int)shuffleIndex; +- (void)setShuffleIndex:(int)si; +- (int)shuffleIndex; --(void)setURL:(NSURL *)u; --(NSURL *)url; --(void)setCurrent:(BOOL) b; --(BOOL)current; +- (void)setURL:(NSURL *)u; +- (NSURL *)url; +- (void)setCurrent:(BOOL) b; +- (BOOL)current; - (void)setArtist:(NSString *)s; - (NSString *)artist; @@ -57,10 +57,10 @@ - (NSString *)lengthString; - (void)setLengthString:(double)l; --(void)setYear:(NSString *)y; --(NSString *)year; --(void)setTrack:(int)y; --(int)track; +- (void)setYear:(NSString *)y; +- (NSString *)year; +- (void)setTrack:(int)y; +- (int)track; - (void)setLength:(double)l; - (void)setBitrate:(int) br; @@ -74,9 +74,9 @@ - (int)bitsPerSample; - (float)sampleRate; -- (void)readTags; -- (void)readTagsThreaded; -- (void)readInfo; -- (void)readInfoThreaded; +- (void)setMetadata: (NSDictionary *)m; +- (void)readMetadataThread; +- (void)setProperties: (NSDictionary *)p; +- (void)readPropertiesThread; @end diff --git a/Playlist/PlaylistEntry.m b/Playlist/PlaylistEntry.m index fc889c600..f85529e5b 100644 --- a/Playlist/PlaylistEntry.m +++ b/Playlist/PlaylistEntry.m @@ -164,7 +164,7 @@ return track; } -- (void)readInfoThreadedSetVariables:(NSDictionary *)dict +- (void)setProperties:(NSDictionary *)dict { [self setLength: [[dict objectForKey:@"length" ] doubleValue]]; [self setBitrate: [[dict objectForKey:@"bitrate" ] intValue]]; @@ -175,11 +175,11 @@ [self setLengthString:[[dict objectForKey:@"length"] doubleValue]]; } -- (void)readInfoThreaded +- (void)readPropertiesThread { NSDictionary *properties = [AudioPropertiesReader propertiesForURL:url]; - [self performSelectorOnMainThread:@selector(readInfoThreadedSetVariables:) withObject:properties waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(setProperties:) withObject:properties waitUntilDone:YES]; } - (NSString *)lengthString @@ -240,7 +240,7 @@ return sampleRate; } -- (void)readTagsThreadedSetVariables: (NSDictionary *)m +- (void)setMetadata: (NSDictionary *)m { NSString *ti = [m objectForKey:@"title"]; @@ -248,7 +248,7 @@ [self setTitle:[[url path] lastPathComponent]]; } else { - [self setTitle:[m objectForKey:@"title"]]; + [self setTitle:ti]; } [self setArtist:[m objectForKey:@"artist"]]; @@ -258,11 +258,11 @@ [self setTrack:[[m objectForKey:@"track"] intValue]]; } -- (void)readTagsThreaded +- (void)readMetadataThread { NSDictionary *metadata = [AudioMetadataReader metadataForURL:url]; - [self performSelectorOnMainThread:@selector(readTagsThreadedSetVariables:) withObject:metadata waitUntilDone:YES]; + [self performSelectorOnMainThread:@selector(setMetadata:) withObject:metadata waitUntilDone:YES]; } diff --git a/Playlist/PlaylistLoader.h b/Playlist/PlaylistLoader.h index ee8c5888b..67070026b 100755 --- a/Playlist/PlaylistLoader.h +++ b/Playlist/PlaylistLoader.h @@ -16,13 +16,15 @@ typedef enum { } PlaylistType; @interface PlaylistLoader : NSObject { - PlaylistController *playlistController; + IBOutlet PlaylistController *playlistController; PlaylistType currentType; //m3u or pls NSString *currentFile; } - +//load arrays of urls... +- (void)addURLs:(NSArray *)urls sort:(BOOL)sort; +- (void)insertURLs:(NSArray *)urls atIndex:(int)index sort:(BOOL)sort; //load playlist auto-determines type to be either pls or m3u. - (BOOL)load:(NSString *)filename; @@ -37,6 +39,7 @@ typedef enum { - (BOOL)saveM3u:(NSString *)filename; - (BOOL)savePls:(NSString *)filename; +- (NSArray *)acceptableFileTypes; - (NSArray *)acceptablePlaylistTypes; - (PlaylistType)currentType; diff --git a/Playlist/PlaylistLoader.m b/Playlist/PlaylistLoader.m index f35545e33..35f7beb2e 100755 --- a/Playlist/PlaylistLoader.m +++ b/Playlist/PlaylistLoader.m @@ -10,6 +10,8 @@ #import "PlaylistController.h" #import "PlaylistEntry.h" +#import "CogAudio/AudioPlayer.h" + @implementation PlaylistLoader //load/save playlist auto-determines type to be either pls or m3u. @@ -32,7 +34,7 @@ - (BOOL)save { - [self save:currentFilename asType:currentType]; + return [self save:currentFile asType:currentType]; } - (BOOL)save:(NSString *)filename @@ -62,20 +64,15 @@ return NO; } -- (BOOL)loadM3u:(NSString *)filename -{ -} - -- (NSString *)pathRelativeTo:(NSString *)filename forEntry:(PlaylistEntry *)pe +- (NSString *)relativePathFrom:(NSString *)filename toURL:(NSURL *)entryURL { NSString *basePath = [[[filename stringByStandardizingPath] stringByDeletingLastPathComponent] stringByAppendingString:@"/"]; - NSURL *entryURL = [pe url]; - if ([[entryURL scheme] isEqualToString:@"file"]) { + if ([entryURL isFileURL]) { //We want relative paths. NSMutableString *entryPath = [[[[entryURL path] stringByStandardizingPath] mutableCopy] autorelease]; - [entryPath replaceOccurrencesOfString:basePath withString:@"" options:(NSAnchoredSearch | NSLiteralSearch | NSCaseInsensitiveSearch) range:NSMakeRange(0, [entryURL length])]; + [entryPath replaceOccurrencesOfString:basePath withString:@"" options:(NSAnchoredSearch | NSLiteralSearch | NSCaseInsensitiveSearch) range:NSMakeRange(0, [entryPath length])]; return entryPath; } @@ -85,7 +82,56 @@ } } - return paths; +- (NSURL *)urlForPath:(NSString *)path relativeTo:(NSString *)baseFilename +{ + if ([path hasPrefix:@"/"]) { + return [NSURL fileURLWithPath:path]; + } + + NSEnumerator *e = [[AudioPlayer schemes] objectEnumerator]; + NSString *scheme; + while (scheme = [e nextObject]) + { + if ([path hasPrefix:[scheme stringByAppendingString:@"://"]]) + { + return [NSURL URLWithString:path]; + } + } + + NSString *basePath = [[[baseFilename stringByStandardizingPath] stringByDeletingLastPathComponent] stringByAppendingString:@"/"]; + + return [NSURL fileURLWithPath:[basePath stringByAppendingString:path]]; +} + + +- (BOOL)loadM3u:(NSString *)filename +{ + NSLog(@"Loading playlist: %@", filename); + + + NSError *error = nil; + NSString *contents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error]; + if (error || !contents) { + NSLog(@"Could not open file...%@ %@", contents, error); + return NO; + } + + NSString *entry; + NSEnumerator *e = [[contents componentsSeparatedByString:@"\n"] objectEnumerator]; + NSMutableArray *entries = [NSMutableArray array]; + + while (entry = [[e nextObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]) + { + if ([entry hasPrefix:@"#"] || [entry isEqualToString:@""]) //Ignore extra info + continue; + + //Need to add basePath, and convert to URL + [entries addObject:[self urlForPath:entry relativeTo:filename]]; + } + + [self addURLs:entries sort:NO]; + + return YES; } - (BOOL)saveM3u:(NSString *)filename @@ -94,18 +140,23 @@ if (!fileHandle) { return NO; } + [fileHandle truncateFileAtOffset:0]; + + NSLog(@"Saving: %@", filename); PlaylistEntry *pe; NSEnumerator *e = [[playlistController content] objectEnumerator]; while (pe = [e nextObject]) { - NSString *path = [self pathRelativeTo:filename forEntry:pe]; + NSString *path = [self relativePathFrom:filename toURL:[pe url]]; [fileHandle writeData:[[path stringByAppendingString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]]; } + [fileHandle closeFile]; + [self setCurrentFile:filename]; - [self setType:kPlaylistM3u]; + [self setCurrentType:kPlaylistM3u]; return YES; } @@ -113,34 +164,37 @@ - (BOOL)loadPls:(NSString *)filename { NSError *error; - NSStringEncoding enc; - NSString *contents = [NSString stringWithContentsOfFile:filename encoding:&enc error:&error]; + NSString *contents = [NSString stringWithContentsOfFile:filename encoding:NSUTF8StringEncoding error:&error]; if (error || !contents) { return NO; } NSString *entry; NSEnumerator *e = [[contents componentsSeparatedByString:@"\n"] objectEnumerator]; + NSMutableArray *entries = [NSMutableArray array]; - while (entry = [e nextObject]) + while (entry = [[e nextObject] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]) { + NSScanner *scanner = [[NSScanner alloc] initWithString:entry]; NSString *lhs = nil; - if (![scanner scanUpToString:@"=" intoString:&lhs]) //get LHS - continue; - if (![scanner scanString:@"=" intoString:nil]) //skip the = - continue; - NSString *nameString = nil; - if (![scanner scanUpToString:@"" intoString:&rhs]) //get RHS - continue; + NSString *rhs = nil; - if (![lhs isEqualToString:@"File"]) + if (![scanner scanUpToString:@"=" intoString:&lhs] || // get LHS + ![scanner scanString:@"=" intoString:nil] || // skip the = + ![scanner scanUpToString:@"" intoString:&rhs] || // get RHS + ![lhs isEqualToString:@"File"]) // We only want file entries + { + [scanner release]; continue; - - //get url if its a file? -// [entries addObject:nameString]; + } + + //need to add basepath if its a file, and convert to URL + [entries addObject:[self urlForPath:rhs relativeTo:filename]]; + + [scanner release]; } - [playlistController addURLs:urls]; + [self addURLs:entries sort:NO]; return YES; } @@ -151,28 +205,188 @@ if (!fileHandle) { return NO; } + [fileHandle truncateFileAtOffset:0]; + + [fileHandle writeData:[[NSString stringWithFormat:@"[playlist]\nnumberOfEntries=%i\n\n",[[playlistController content] count]] dataUsingEncoding:NSUTF8StringEncoding]]; NSEnumerator *e = [[playlistController content] objectEnumerator]; PlaylistEntry *pe; int i = 1; while (pe = [e nextObject]) { - NSString *path = [self pathRelativeTo:filename forEntry:pe]; + NSString *path = [self relativePathFrom:filename toURL:[pe url]]; NSString *entry = [NSString stringWithFormat:@"File%i=%@\n",i,path]; [fileHandle writeData:[entry dataUsingEncoding:NSUTF8StringEncoding]]; i++; } + [fileHandle writeData:[@"\nVERSION=2" dataUsingEncoding:NSUTF8StringEncoding]]; + [fileHandle closeFile]; + [self setCurrentFile:filename]; - [self setType:kPlaylistM3u]; + [self setCurrentType:kPlaylistM3u]; return YES; } +- (NSArray *)fileURLsAtPath:(NSString *)path +{ + NSFileManager *manager = [NSFileManager defaultManager]; + + NSMutableArray *urls = [NSMutableArray array]; + + NSString *subpath; + NSArray *subpaths = [manager subpathsAtPath:path]; + NSEnumerator *e = [subpaths objectEnumerator]; + + while(subpath = [e nextObject]) + { + NSString *absoluteSubpath = [NSString pathWithComponents:[NSArray arrayWithObjects:path,subpath,nil]]; + + BOOL isDir; + if ( [manager fileExistsAtPath:absoluteSubpath isDirectory:&isDir] && isDir == NO) + { + [urls addObject:[NSURL fileURLWithPath:absoluteSubpath]]; + } + } + + return urls; +} + +- (void)insertURLs:(NSArray *)urls atIndex:(int)index sort:(BOOL)sort +{ + NSMutableArray *allURLs = [[NSMutableArray alloc] init]; + NSMutableArray *validURLs = [[NSMutableArray alloc] init]; + NSArray *finalURLs; + + if (!urls) + return; + + if (index < 0) + index = 0; + + NSLog(@"URLS: %@", urls); + NSEnumerator *urlEnumerator = [urls objectEnumerator]; + NSURL *url; + while (url = [urlEnumerator nextObject]) + { + if ([url isFileURL]) { + BOOL isDir; + if ([[NSFileManager defaultManager] fileExistsAtPath:[url path] isDirectory:&isDir]) + { + if (isDir == YES) + { + //Get subpaths + [allURLs addObjectsFromArray:[self fileURLsAtPath:[url path]]]; + } + else + { + //File url + [allURLs addObject:url]; + } + } + } + else + { + //Non-file URL.. + [allURLs addObject:url]; + } + } + + + urlEnumerator = [allURLs objectEnumerator]; + while (url = [urlEnumerator nextObject]) + { + if (![[AudioPlayer schemes] containsObject:[url scheme]]) + continue; + + //Need a better way to determine acceptable file types than basing it on extensions. + if (![[self acceptableFileTypes] containsObject:[[[url path] pathExtension] lowercaseString]]) + continue; + + [validURLs addObject:url]; + } + + finalURLs = validURLs; + if (sort == YES) + { + NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"absoluteString" ascending:YES]; + + finalURLs = [validURLs sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; + + [sortDescriptor release]; + } + + //Create actual entries + int i; + NSMutableArray *entries = [NSMutableArray array]; + for (i = 0; i < [finalURLs count]; i++) + { + PlaylistEntry *pe = [[PlaylistEntry alloc] init]; + NSURL *url = [finalURLs objectAtIndex:i]; + + [pe setURL:url]; + [pe setIndex:index+i]; + [pe setTitle:[[url path] lastPathComponent]]; + + [entries addObject:pe]; + + [pe release]; + } + + NSIndexSet *is = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(index, [entries count])]; + + [playlistController insertObjects:entries atArrangedObjectIndexes:is]; + + //Select the first entry in the group that was just added + [playlistController setSelectionIndex:index]; + + //Other thread for reading things... + [NSThread detachNewThreadSelector:@selector(readEntriesInfoThread:) toTarget:self withObject:entries]; + + [allURLs release]; + [validURLs release]; + + return; +} + +- (void)readEntriesInfoThread:(NSArray *)entries +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSEnumerator *e = [entries objectEnumerator]; + PlaylistEntry *pe; + while (pe = [e nextObject]) + { + [pe readPropertiesThread]; + + [pe readMetadataThread]; + + //Hack so the display gets updated + if (pe == [playlistController currentEntry]) + [playlistController performSelectorOnMainThread:@selector(setCurrentEntry:) withObject:[playlistController currentEntry] waitUntilDone:YES]; + } + + + [playlistController performSelectorOnMainThread:@selector(updateTotalTime) withObject:nil waitUntilDone:NO]; + + [pool release]; +} + +- (void)addURLs:(NSArray *)urls sort:(BOOL)sort +{ + [self insertURLs:urls atIndex:[[playlistController content] count] sort:sort]; +} + +- (NSArray *)acceptableFileTypes +{ + return [AudioPlayer fileTypes]; +} + - (NSArray *)acceptablePlaylistTypes { - return [NSArray arrayWithObject:@"m3u",@"pls",nil]; + return [NSArray arrayWithObjects:@"m3u",@"pls",nil]; } - (PlaylistType)currentType