From 94d97ab6af464428c142176ffe2927e0fa609b8a Mon Sep 17 00:00:00 2001 From: Daniel Barber Date: Fri, 15 May 2020 13:11:33 -0400 Subject: [PATCH] Basic playlist interface implementation --- Mac/AppDelegate.swift | 46 ++++++- Mac/Base.lproj/Main.storyboard | 15 +-- .../SavePlaylistViewController.swift | 26 ++++ .../Window/Base.lproj/Main.storyboard | 122 +++++++++++++++++- Mac/Components/Window/WindowController.swift | 1 + Persephone.xcodeproj/project.pbxproj | 43 ++++++ Shared/Delegates/MPDServerDelegate.swift | 6 + .../Extensions/MPDClient+Command.swift | 14 ++ .../MPDClient/Extensions/MPDClient+Idle.swift | 3 + .../Extensions/MPDClient+Playlists.swift | 52 ++++++++ Shared/MPDClient/Models/MPDCommand.swift | 5 + Shared/MPDClient/Models/MPDPlaylist.swift | 31 +++++ Shared/MPDClient/Protocols/Delegate.swift | 2 + Shared/Models/Playlist.swift | 25 ++++ Shared/State/Actions/PlaylistActions.swift | 13 ++ Shared/State/AppState.swift | 1 + Shared/State/PlaylistState.swift | 13 ++ Shared/State/Reducers/AppReducer.swift | 3 +- Shared/State/Reducers/PlaylistReducer.swift | 24 ++++ 19 files changed, 430 insertions(+), 15 deletions(-) create mode 100644 Mac/Components/Playlists/SavePlaylistViewController.swift create mode 100644 Shared/MPDClient/Extensions/MPDClient+Playlists.swift create mode 100644 Shared/MPDClient/Models/MPDPlaylist.swift create mode 100644 Shared/Models/Playlist.swift create mode 100644 Shared/State/Actions/PlaylistActions.swift create mode 100644 Shared/State/PlaylistState.swift create mode 100644 Shared/State/Reducers/PlaylistReducer.swift diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index 39bc86f..461aac5 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -28,6 +28,8 @@ class AppDelegate: NSObject, @IBOutlet weak var playSelectedSongMenuItem: NSMenuItem! @IBOutlet weak var playSelectedSongNextMenuItem: NSMenuItem! @IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem! + + @IBOutlet weak var savePlaylistMenuItem: NSMenuItem! func applicationDidFinishLaunching(_ aNotification: Notification) { mediaKeyTap = MediaKeyTap(delegate: self) @@ -35,10 +37,12 @@ class AppDelegate: NSObject, App.store.subscribe(self) { $0.select { - ($0.serverState, $0.playerState, $0.uiState) + ($0.serverState, $0.playerState, $0.uiState, $0.playlistState) } } - + + NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil) + _ = App.userNotificationsController _ = App.mediaInfoController _ = App.playerStateInfoController @@ -137,6 +141,27 @@ class AppDelegate: NSObject, connectMenuItem.isEnabled = !connected disconnectMenuItem.isEnabled = connected } + + func setPlaylists(state: PlaylistState) { + guard let playlistsMenuItem = NSApplication.shared.mainMenu?.item(withTitle: "Playlists") + else { return } + + playlistsMenuItem.submenu?.items.forEach { item in + if item.tag == 1 { + playlistsMenuItem.submenu?.removeItem(item) + } + } + + state.playlists.forEach { playlist in + let playlistMenuItem = NSMenuItem( + title: playlist.path, + action: #selector(loadPlaylist), + keyEquivalent: "" + ) + playlistMenuItem.tag = 1 + playlistsMenuItem.submenu?.addItem(playlistMenuItem) + } + } func handle(mediaKey: MediaKey, event: KeyEvent) { switch mediaKey { @@ -148,6 +173,17 @@ class AppDelegate: NSObject, App.mpdClient.prevTrack() } } + + @objc func didConnect() { + App.mpdClient.fetchPlaylists() + } + + @objc func loadPlaylist(_ sender: NSMenuItem) { + let name = sender.title + + App.mpdClient.clearQueue() + App.mpdClient.loadQueueFromPlaylist(name: name) + } @IBAction func connectMenuAction(_ sender: NSMenuItem) { App.mpdServerController.connect() @@ -222,7 +258,10 @@ class AppDelegate: NSObject, extension AppDelegate: StoreSubscriber { typealias StoreSubscriberStateType = ( - serverState: ServerState, playerState: PlayerState, uiState: UIState + serverState: ServerState, + playerState: PlayerState, + uiState: UIState, + playlistState: PlaylistState ) func newState(state: StoreSubscriberStateType) { @@ -231,5 +270,6 @@ extension AppDelegate: StoreSubscriber { setSongMenuItemsState(selectedSong: state.uiState.selectedSong) setControlsMenuItemsState(state: state.playerState) setConnectMenuItemsState(connected: state.serverState.connected) + setPlaylists(state: state.playlistState) } } diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index 5a31378..8135373 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -620,7 +621,7 @@ - + @@ -772,14 +773,13 @@ - + - @@ -809,14 +809,14 @@ - + - + @@ -842,7 +842,6 @@ - diff --git a/Mac/Components/Playlists/SavePlaylistViewController.swift b/Mac/Components/Playlists/SavePlaylistViewController.swift new file mode 100644 index 0000000..ba57e6f --- /dev/null +++ b/Mac/Components/Playlists/SavePlaylistViewController.swift @@ -0,0 +1,26 @@ +// +// SavePlaylistViewController.swift +// Persephone +// +// Created by Dan Barber on 2020-4-11. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import AppKit + +class SavePlaylistViewController: NSViewController { + override func viewDidLoad() { + super.viewDidLoad() + } + + @IBAction func saveButtonAction(_ sender: Any) { + App.mpdClient.saveQueueToPlaylist(name: playlistName.stringValue) + self.dismiss(sender) + } + + @IBAction func cancelButtonAction(_ sender: Any) { + self.dismiss(sender) + } + + @IBOutlet var playlistName: NSTextFieldCell! +} diff --git a/Mac/Components/Window/Base.lproj/Main.storyboard b/Mac/Components/Window/Base.lproj/Main.storyboard index de01c5e..04c07ac 100644 --- a/Mac/Components/Window/Base.lproj/Main.storyboard +++ b/Mac/Components/Window/Base.lproj/Main.storyboard @@ -176,6 +176,20 @@ + + + + + + + + + + + + + + @@ -277,7 +291,7 @@ - + @@ -502,7 +516,7 @@ - + @@ -906,7 +920,7 @@ - + @@ -959,6 +973,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Mac/Components/Window/WindowController.swift b/Mac/Components/Window/WindowController.swift index cb88c25..5e9cb0b 100644 --- a/Mac/Components/Window/WindowController.swift +++ b/Mac/Components/Window/WindowController.swift @@ -31,6 +31,7 @@ class WindowController: NSWindowController { @IBOutlet weak var searchQuery: NSSearchField! + @IBOutlet weak var savePlaylistMenuItem: NSMenuItem! override func windowDidLoad() { super.windowDidLoad() window?.titleVisibility = .hidden diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index 0f37df4..4cfc6b7 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -40,6 +40,12 @@ E42A4D5122E2167E001C6CAD /* MPDClient+Songs.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */; }; E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; }; E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; }; + E42B18E024538473000C8DFD /* MPDClient+Playlists.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42B18DF24538473000C8DFD /* MPDClient+Playlists.swift */; }; + E42B18E124538473000C8DFD /* MPDClient+Playlists.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42B18DF24538473000C8DFD /* MPDClient+Playlists.swift */; }; + E42B18E324539110000C8DFD /* PlaylistState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42B18E224539110000C8DFD /* PlaylistState.swift */; }; + E42B18E424539110000C8DFD /* PlaylistState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42B18E224539110000C8DFD /* PlaylistState.swift */; }; + E42B18E6245391F6000C8DFD /* PlaylistActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42B18E5245391F6000C8DFD /* PlaylistActions.swift */; }; + E42B18E7245391F6000C8DFD /* PlaylistActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42B18E5245391F6000C8DFD /* PlaylistActions.swift */; }; E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; }; E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; }; E439109822640213002982E9 /* SongNotifierService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E439109722640213002982E9 /* SongNotifierService.swift */; }; @@ -264,6 +270,7 @@ E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; }; E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; + E4ABD9ED2442935700D7A8EF /* SavePlaylistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4ABD9EB2442935700D7A8EF /* SavePlaylistViewController.swift */; }; E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; }; E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; }; E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; }; @@ -289,6 +296,9 @@ E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; }; E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DA820523D6236200C1EE58 /* NSSize.swift */; }; E4DCCFAE23E4DB5D009A8113 /* MPDClientWrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */; }; + E4DFF2F82454E9A4001A89DD /* PlaylistReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DFF2F72454E9A4001A89DD /* PlaylistReducer.swift */; }; + E4DFF2FA2454ECDF001A89DD /* MPDPlaylist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DFF2F92454ECDF001A89DD /* MPDPlaylist.swift */; }; + E4DFF2FC2454EED1001A89DD /* Playlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DFF2FB2454EED1001A89DD /* Playlist.swift */; }; E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; }; E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; }; @@ -454,6 +464,9 @@ E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Songs.swift"; sourceTree = ""; }; E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + E42B18DF24538473000C8DFD /* MPDClient+Playlists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Playlists.swift"; sourceTree = ""; }; + E42B18E224539110000C8DFD /* PlaylistState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistState.swift; sourceTree = ""; }; + E42B18E5245391F6000C8DFD /* PlaylistActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistActions.swift; sourceTree = ""; }; E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = ""; }; E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; E439109722640213002982E9 /* SongNotifierService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongNotifierService.swift; sourceTree = ""; }; @@ -608,6 +621,7 @@ E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = ""; }; E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = ""; }; E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; + E4ABD9EB2442935700D7A8EF /* SavePlaylistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePlaylistViewController.swift; sourceTree = ""; }; E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = ""; }; E4B11B60226A4BFF0075461B /* PlayerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerReducer.swift; sourceTree = ""; }; E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = ""; }; @@ -635,6 +649,9 @@ E4DCCFAB23E4DB5D009A8113 /* Persephone-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Persephone-Bridging-Header.h"; sourceTree = ""; }; E4DCCFAC23E4DB5D009A8113 /* MPDClientWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPDClientWrapper.h; sourceTree = ""; }; E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = MPDClientWrapper.c; sourceTree = ""; }; + E4DFF2F72454E9A4001A89DD /* PlaylistReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistReducer.swift; sourceTree = ""; }; + E4DFF2F92454ECDF001A89DD /* MPDPlaylist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDPlaylist.swift; sourceTree = ""; }; + E4DFF2FB2454EED1001A89DD /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = ""; }; E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = ""; }; E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = ""; }; E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = ""; }; @@ -802,6 +819,7 @@ E41E5304223BFB0700173814 /* MPDClient+Error.swift */, E41E5302223BF9C300173814 /* MPDClient+Idle.swift */, E453825423FA347C007F6BFC /* MPDClient+Mixer.swift */, + E42B18DF24538473000C8DFD /* MPDClient+Playlists.swift */, E41E5300223BF99300173814 /* MPDClient+Queue.swift */, E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */, @@ -892,6 +910,8 @@ E442CCC42347D5B900004E0C /* Components */ = { isa = PBXGroup; children = ( + E4ABD9EA2442912100D7A8EF /* Playlists */, + E453824D23F9F700007F6BFC /* VolumeControl */, E442CCCB2347D77A00004E0C /* Browser */, E4A83BEC2221F5DD0098FED6 /* Preferences */, E442CCC62347D5E700004E0C /* Queue */, @@ -1226,6 +1246,14 @@ path = Services; sourceTree = ""; }; + E4ABD9EA2442912100D7A8EF /* Playlists */ = { + isa = PBXGroup; + children = ( + E4ABD9EB2442935700D7A8EF /* SavePlaylistViewController.swift */, + ); + path = Playlists; + sourceTree = ""; + }; E4B11B5F226A4BED0075461B /* Reducers */ = { isa = PBXGroup; children = ( @@ -1236,6 +1264,7 @@ E4B11B74226CC4D30075461B /* QueueReducer.swift */, E4F2EFF124076B5E00198159 /* ServerReducer.swift */, E440519D227BB0720090CD6F /* UIReducer.swift */, + E4DFF2F72454E9A4001A89DD /* PlaylistReducer.swift */, ); path = Reducers; sourceTree = ""; @@ -1252,6 +1281,7 @@ E4B11B67226A4FA00075461B /* QueueState.swift */, E4F2EFED24076A2700198159 /* ServerState.swift */, E440519F227BB0AB0090CD6F /* UIState.swift */, + E42B18E224539110000C8DFD /* PlaylistState.swift */, ); path = State; sourceTree = ""; @@ -1265,6 +1295,7 @@ E4B11BBF2275EE150075461B /* QueueActions.swift */, E4F2EFEF24076B0900198159 /* ServerActions.swift */, E440519B227BAF2E0090CD6F /* UIActions.swift */, + E42B18E5245391F6000C8DFD /* PlaylistActions.swift */, ); path = Actions; sourceTree = ""; @@ -1288,6 +1319,7 @@ E4E8CC9922075D370024217A /* MPDSong.swift */, E4A642D922090CBE00067D21 /* MPDStatus.swift */, E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */, + E4DFF2F92454ECDF001A89DD /* MPDPlaylist.swift */, ); path = Models; sourceTree = ""; @@ -1334,6 +1366,7 @@ E419E2862249B96600216A8C /* Song.swift */, E47E2FDC2220A6D100F747E6 /* Time.swift */, E4B11B72226A6C770075461B /* TrackTimer.swift */, + E4DFF2FB2454EED1001A89DD /* Playlist.swift */, ); path = Models; sourceTree = ""; @@ -1710,6 +1743,7 @@ E48059EA2426D73600362CF3 /* coutput.c in Sources */, E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */, E4805A0E2426D73600362CF3 /* cmount.c in Sources */, + E42B18E6245391F6000C8DFD /* PlaylistActions.swift in Sources */, E4FF71922276029000D4C412 /* PreferencesActions.swift in Sources */, E489E39922B85D0400CA8CBD /* NSPasteboard.swift in Sources */, E43AC1F122C68E6A001E483C /* NSPasteboardItem.swift in Sources */, @@ -1719,6 +1753,8 @@ E48059CC2426D73600362CF3 /* mixer.c in Sources */, E48059D22426D73600362CF3 /* partition.c in Sources */, E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */, + E4ABD9ED2442935700D7A8EF /* SavePlaylistViewController.swift in Sources */, + E4DFF2F82454E9A4001A89DD /* PlaylistReducer.swift in Sources */, E48059E02426D73600362CF3 /* password.c in Sources */, E48059EC2426D73600362CF3 /* stats.c in Sources */, E419E2872249B96600216A8C /* Song.swift in Sources */, @@ -1729,6 +1765,7 @@ E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */, E48059D62426D73600362CF3 /* ierror.c in Sources */, E4FF718E2276010E00D4C412 /* PreferencesState.swift in Sources */, + E42B18E324539110000C8DFD /* PlaylistState.swift in Sources */, E48059DE2426D73600362CF3 /* audio_format.c in Sources */, E439109822640213002982E9 /* SongNotifierService.swift in Sources */, E4B3DF6523D66A4400728F6B /* QueueSongCoverView.swift in Sources */, @@ -1751,6 +1788,7 @@ E4805A062426D73600362CF3 /* error.c in Sources */, E4805A122426D73600362CF3 /* rplaylist.c in Sources */, E4805A202426D73600362CF3 /* parser.c in Sources */, + E42B18E024538473000C8DFD /* MPDClient+Playlists.swift in Sources */, E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */, E4805A2A2426D73600362CF3 /* message.c in Sources */, E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */, @@ -1765,12 +1803,14 @@ 38BAC36B249CB1A7004BAEA4 /* AlbumDetailSongTitleView.swift in Sources */, E4F2EFF024076B0900198159 /* ServerActions.swift in Sources */, E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, + E4DFF2FA2454ECDF001A89DD /* MPDPlaylist.swift in Sources */, E4B11B66226A4F830075461B /* PlayerState.swift in Sources */, E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */, E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */, E4805A282426D73600362CF3 /* player.c in Sources */, E48059D02426D73600362CF3 /* send.c in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, + E4DFF2FC2454EED1001A89DD /* Playlist.swift in Sources */, E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */, E48059F22426D73600362CF3 /* capabilities.c in Sources */, E48059F82426D73600362CF3 /* rdirectory.c in Sources */, @@ -1822,6 +1862,7 @@ E4805A0F2426D73600362CF3 /* cmount.c in Sources */, E48059E32426D73600362CF3 /* mount.c in Sources */, E480511424255BAF00362CF3 /* DraggedSong.swift in Sources */, + E42B18E124538473000C8DFD /* MPDClient+Playlists.swift in Sources */, E48059CB2426D73600362CF3 /* list.c in Sources */, E4805A212426D73600362CF3 /* parser.c in Sources */, E48059EB2426D73600362CF3 /* coutput.c in Sources */, @@ -1848,6 +1889,7 @@ E480513D24255E7200362CF3 /* PlayerState.swift in Sources */, E480511D24255BD200362CF3 /* MPDClientWrapper.c in Sources */, E480512924255BDB00362CF3 /* MPDClient+Status.swift in Sources */, + E42B18E424539110000C8DFD /* PlaylistState.swift in Sources */, E48059FD2426D73600362CF3 /* neighbor.c in Sources */, E4805A252426D73600362CF3 /* iso8601.c in Sources */, E480512C24255BDF00362CF3 /* MPDCommand.swift in Sources */, @@ -1907,6 +1949,7 @@ E480512224255BDB00362CF3 /* MPDClient+Connection.swift in Sources */, E480511124255BA900362CF3 /* MachTime.swift in Sources */, E4805A112426D73600362CF3 /* tag.c in Sources */, + E42B18E7245391F6000C8DFD /* PlaylistActions.swift in Sources */, E4805A192426D73600362CF3 /* status.c in Sources */, E48059EF2426D73600362CF3 /* socket.c in Sources */, E480514B24255E7D00362CF3 /* PreferencesActions.swift in Sources */, diff --git a/Shared/Delegates/MPDServerDelegate.swift b/Shared/Delegates/MPDServerDelegate.swift index a2acf1c..a5c045c 100644 --- a/Shared/Delegates/MPDServerDelegate.swift +++ b/Shared/Delegates/MPDServerDelegate.swift @@ -69,4 +69,10 @@ class MPDServerDelegate: MPDClientDelegate { func didLoadArtists(mpdClient: MPDClient, artists: [String]) { } + + func didLoadPlaylists(mpdClient: MPDClient, playlists: [MPDClient.MPDPlaylist]) { + DispatchQueue.main.async { + App.store.dispatch(UpdatePlaylistsAction(playlists: playlists)) + } + } } diff --git a/Shared/MPDClient/Extensions/MPDClient+Command.swift b/Shared/MPDClient/Extensions/MPDClient+Command.swift index 8135766..49f2477 100644 --- a/Shared/MPDClient/Extensions/MPDClient+Command.swift +++ b/Shared/MPDClient/Extensions/MPDClient+Command.swift @@ -142,6 +142,20 @@ extension MPDClient { offset: offset, callback: callback ) + + // Playlist commands + case .fetchPlaylists: + playlists() + + case .saveQueueToPlaylist: + guard let name = userData["name"] as? String else { return } + + sendSaveQueueToPlaylist(name: name) + + case .loadQueueFromPlaylist: + guard let name = userData["name"] as? String else { return } + + sendLoadQueueFromPlaylist(name: name) } } diff --git a/Shared/MPDClient/Extensions/MPDClient+Idle.swift b/Shared/MPDClient/Extensions/MPDClient+Idle.swift index 362086e..5d8409b 100644 --- a/Shared/MPDClient/Extensions/MPDClient+Idle.swift +++ b/Shared/MPDClient/Extensions/MPDClient+Idle.swift @@ -59,6 +59,9 @@ extension MPDClient { if mpdIdle.contains(.database) { self.fetchAllAlbums() } + if mpdIdle.contains(.storedPlaylist) { + self.fetchPlaylists() + } if mpdIdle.contains(.queue) { self.fetchQueue() self.fetchStatus() diff --git a/Shared/MPDClient/Extensions/MPDClient+Playlists.swift b/Shared/MPDClient/Extensions/MPDClient+Playlists.swift new file mode 100644 index 0000000..fa0501b --- /dev/null +++ b/Shared/MPDClient/Extensions/MPDClient+Playlists.swift @@ -0,0 +1,52 @@ +// +// MPDClient+Playlists.swift +// Persephone +// +// Created by Dan Barber on 2020-4-24. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import Foundation +import mpdclient + +extension MPDClient { + func fetchPlaylists() { + enqueueCommand(command: .fetchPlaylists) + } + + func saveQueueToPlaylist(name: String) { + enqueueCommand( + command: .saveQueueToPlaylist, + userData: ["name": name] + ) + } + + func loadQueueFromPlaylist(name: String) { + enqueueCommand( + command: .loadQueueFromPlaylist, + userData: ["name": name] + ) + } + + func playlists() { + var playlists: [MPDPlaylist] = [] + + mpd_send_list_playlists(connection) + + while let playlist = mpd_recv_playlist(connection) { + let mpdPlaylist = MPDPlaylist(playlist) + + playlists.append(mpdPlaylist) + } + + self.delegate?.didLoadPlaylists(mpdClient: self, playlists: playlists) + } + + func sendSaveQueueToPlaylist(name: String) { + mpd_run_save(connection, name) + } + + func sendLoadQueueFromPlaylist(name: String) { + mpd_run_load(connection, name) + } +} diff --git a/Shared/MPDClient/Models/MPDCommand.swift b/Shared/MPDClient/Models/MPDCommand.swift index 906019b..5f1efb4 100644 --- a/Shared/MPDClient/Models/MPDCommand.swift +++ b/Shared/MPDClient/Models/MPDCommand.swift @@ -53,5 +53,10 @@ extension MPDClient { // Song commands case fetchAlbumArt + + // Playlist commands + case fetchPlaylists + case saveQueueToPlaylist + case loadQueueFromPlaylist } } diff --git a/Shared/MPDClient/Models/MPDPlaylist.swift b/Shared/MPDClient/Models/MPDPlaylist.swift new file mode 100644 index 0000000..3aa8793 --- /dev/null +++ b/Shared/MPDClient/Models/MPDPlaylist.swift @@ -0,0 +1,31 @@ +// +// MPDPlaylist.swift +// Persephone +// +// Created by Dan Barber on 2020-4-25. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import Foundation + +extension MPDClient { + class MPDPlaylist { + let playlist: OpaquePointer + + init(_ playlist: OpaquePointer) { + self.playlist = playlist + } + + deinit { + mpd_playlist_free(playlist) + } + + var path: UnsafePointer { + return mpd_playlist_get_path(playlist) + } + + var pathString: String { + return String(cString: path) + } + } +} diff --git a/Shared/MPDClient/Protocols/Delegate.swift b/Shared/MPDClient/Protocols/Delegate.swift index 95ad910..6eeca7a 100644 --- a/Shared/MPDClient/Protocols/Delegate.swift +++ b/Shared/MPDClient/Protocols/Delegate.swift @@ -25,4 +25,6 @@ protocol MPDClientDelegate { func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.MPDAlbum]) func didLoadArtists(mpdClient: MPDClient, artists: [String]) + + func didLoadPlaylists(mpdClient: MPDClient, playlists: [MPDClient.MPDPlaylist]) } diff --git a/Shared/Models/Playlist.swift b/Shared/Models/Playlist.swift new file mode 100644 index 0000000..dc1990d --- /dev/null +++ b/Shared/Models/Playlist.swift @@ -0,0 +1,25 @@ +// +// Playlist.swift +// Persephone +// +// Created by Dan Barber on 2020-4-25. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +struct Playlist { + var mpdPlaylist: MPDClient.MPDPlaylist + + init(mpdPlaylist: MPDClient.MPDPlaylist) { + self.mpdPlaylist = mpdPlaylist + } + + var path: String { + return mpdPlaylist.pathString + } +} + +extension Playlist: Equatable { + static func == (lhs: Playlist, rhs: Playlist) -> Bool { + return lhs.path == rhs.path + } +} diff --git a/Shared/State/Actions/PlaylistActions.swift b/Shared/State/Actions/PlaylistActions.swift new file mode 100644 index 0000000..8dcf8da --- /dev/null +++ b/Shared/State/Actions/PlaylistActions.swift @@ -0,0 +1,13 @@ +// +// PlaylistActions.swift +// Persephone +// +// Created by Dan Barber on 2020-4-24. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import ReSwift + +struct UpdatePlaylistsAction: Action { + var playlists: [MPDClient.MPDPlaylist] +} diff --git a/Shared/State/AppState.swift b/Shared/State/AppState.swift index 9caff16..9be1e38 100644 --- a/Shared/State/AppState.swift +++ b/Shared/State/AppState.swift @@ -15,4 +15,5 @@ struct AppState: StateType { var albumListState = AlbumListState() var preferencesState = PreferencesState() var uiState = UIState() + var playlistState = PlaylistState() } diff --git a/Shared/State/PlaylistState.swift b/Shared/State/PlaylistState.swift new file mode 100644 index 0000000..fd369ff --- /dev/null +++ b/Shared/State/PlaylistState.swift @@ -0,0 +1,13 @@ +// +// PlaylistState.swift +// Persephone +// +// Created by Dan Barber on 2020-4-24. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import ReSwift + +struct PlaylistState: StateType, Equatable { + var playlists: [Playlist] = [] +} diff --git a/Shared/State/Reducers/AppReducer.swift b/Shared/State/Reducers/AppReducer.swift index 2f826bc..d7e88f5 100644 --- a/Shared/State/Reducers/AppReducer.swift +++ b/Shared/State/Reducers/AppReducer.swift @@ -15,6 +15,7 @@ func appReducer(action: Action, state: AppState?) -> AppState { queueState: queueReducer(action: action, state: state?.queueState), albumListState: albumListReducer(action: action, state: state?.albumListState), preferencesState: preferencesReducer(action: action, state: state?.preferencesState), - uiState: uiReducer(action: action, state: state?.uiState) + uiState: uiReducer(action: action, state: state?.uiState), + playlistState: playlistReducer(action: action, state: state?.playlistState) ) } diff --git a/Shared/State/Reducers/PlaylistReducer.swift b/Shared/State/Reducers/PlaylistReducer.swift new file mode 100644 index 0000000..91a1212 --- /dev/null +++ b/Shared/State/Reducers/PlaylistReducer.swift @@ -0,0 +1,24 @@ +// +// PlaylistReducer.swift +// Persephone +// +// Created by Dan Barber on 2020-4-25. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import ReSwift + +func playlistReducer(action: Action, state: PlaylistState?) -> PlaylistState { + var state = state ?? PlaylistState() + + switch action { + case let action as UpdatePlaylistsAction: + state.playlists = action.playlists.map { Playlist(mpdPlaylist: $0) } + + default: + break + + } + + return state +}