1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00
This commit is contained in:
Daniel Barber 2022-01-09 01:27:38 +00:00 committed by GitHub
commit 057f0cc060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 430 additions and 15 deletions

View File

@ -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)
}
}

View File

@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
<capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -620,7 +621,7 @@
<menuItem title="Show Sidebar" keyEquivalent="s" id="kIP-vf-haE">
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
<connections>
<action selector="toggleSourceList:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
<action selector="toggleSidebar:" target="Ady-hI-5gd" id="iwa-gc-5KM"/>
</connections>
</menuItem>
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
@ -772,14 +773,13 @@
<subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll">
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn identifier="songTitleColumn" width="222" minWidth="128" maxWidth="1000" id="0Co-uF-CCB">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Title">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
@ -809,14 +809,14 @@
<rect key="frame" x="1" y="20" width="222" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView translatesAutoresizingMaskIntoConstraints="NO" id="o8i-cz-hIP">
<imageView ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="o8i-cz-hIP">
<rect key="frame" x="3" y="0.0" width="17" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="17" id="UFf-Fg-9Qg"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" imageScaling="proportionallyDown" image="playButton" id="ckK-gW-Vhx"/>
</imageView>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="i0h-bn-auJ">
<textField verticalHuggingPriority="750" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="i0h-bn-auJ">
<rect key="frame" x="25" y="0.0" width="197" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="ei8-1e-ErK">
<font key="font" metaFont="system"/>
@ -842,7 +842,6 @@
</tableColumn>
<tableColumn identifier="songArtistColumn" width="222" minWidth="128" maxWidth="1000" id="SPM-QP-DX8">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell>

View File

@ -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!
}

View File

@ -176,6 +176,20 @@
</items>
</menu>
</menuItem>
<menuItem title="Playlists" id="WzS-gW-tqH">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Playlists" autoenablesItems="NO" identifier="playlistsMenu" id="v7W-VT-YsS">
<items>
<menuItem title="Save Playlist…" id="lL7-Nu-DRz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<segue destination="3oS-GD-9H1" kind="modal" id="eUS-mx-Dph"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="cFk-06-bnH"/>
</items>
</menu>
</menuItem>
<menuItem title="Controls" id="hB8-hX-gD5">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Controls" autoenablesItems="NO" id="61K-cL-XWL">
@ -277,7 +291,7 @@
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-183" y="-242"/>
<point key="canvasLocation" x="-275" y="-242"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
@ -502,7 +516,7 @@
</windowController>
<customObject id="0sd-8B-etN" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="346" y="-242"/>
<point key="canvasLocation" x="349" y="-546"/>
</scene>
<!--General-->
<scene sceneID="5er-B6-hoB">
@ -906,7 +920,7 @@
</items>
</menu>
</objects>
<point key="canvasLocation" x="824.5" y="745.5"/>
<point key="canvasLocation" x="824.5" y="766"/>
</scene>
<!--Album View Controller-->
<scene sceneID="7Ua-Hj-zWt">
@ -959,6 +973,108 @@
</objects>
<point key="canvasLocation" x="1485" y="182"/>
</scene>
<!--Save Playlist-->
<scene sceneID="Aaj-nS-I3Z">
<objects>
<viewController title="Save Playlist" id="3oS-GD-9H1" customClass="SavePlaylistViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" misplaced="YES" id="dgg-SY-65p">
<rect key="frame" x="0.0" y="0.0" width="286" height="93"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<gridView xPlacement="leading" yPlacement="bottom" rowAlignment="none" rowSpacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="dtK-8L-o11">
<rect key="frame" x="0.0" y="0.0" width="286" height="95"/>
<rows>
<gridRow topPadding="20" id="ilS-gI-bD9"/>
<gridRow bottomPadding="17" id="dEZ-bX-GdR"/>
</rows>
<columns>
<gridColumn leadingPadding="20" id="NE8-vb-vlD"/>
<gridColumn trailingPadding="20" id="ES5-gL-nIM"/>
</columns>
<gridCells>
<gridCell row="ilS-gI-bD9" column="NE8-vb-vlD" xPlacement="trailing" yPlacement="center" id="r1d-Yn-TIF">
<textField key="contentView" horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="LIQ-7r-gbX">
<rect key="frame" x="18" y="57" width="44" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Name:" id="7UN-bH-wIn">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</gridCell>
<gridCell row="ilS-gI-bD9" column="ES5-gL-nIM" yPlacement="center" id="oSk-cx-yku">
<textField key="contentView" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="vfb-1b-g2p">
<rect key="frame" x="66" y="54" width="200" height="21"/>
<constraints>
<constraint firstAttribute="width" constant="200" id="UEE-eo-PUq"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" id="vhi-nK-mWd">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</gridCell>
<gridCell row="dEZ-bX-GdR" column="NE8-vb-vlD" id="bwK-5c-kB7"/>
<gridCell row="dEZ-bX-GdR" column="ES5-gL-nIM" xPlacement="trailing" id="iiE-dZ-nPw">
<stackView key="contentView" distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QAY-lb-fGF">
<rect key="frame" x="118" y="17" width="148" height="21"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gG3-Cp-l12">
<rect key="frame" x="-6" y="-7" width="82" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="NW6-Xn-CWZ">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancelButtonAction:" target="3oS-GD-9H1" id="7f1-6J-VDQ"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pUw-bS-at3" userLabel="Save">
<rect key="frame" x="72" y="-7" width="82" height="32"/>
<buttonCell key="cell" type="push" title="Save" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="HPK-qZ-pkt">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="saveButtonAction:" target="3oS-GD-9H1" id="iRy-nu-VaK"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="pUw-bS-at3" firstAttribute="width" secondItem="gG3-Cp-l12" secondAttribute="width" id="OLN-VK-JsL"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
</gridCell>
</gridCells>
</gridView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="dtK-8L-o11" secondAttribute="bottom" id="MuW-ZC-8WV"/>
<constraint firstItem="dtK-8L-o11" firstAttribute="leading" secondItem="dgg-SY-65p" secondAttribute="leading" id="XHm-e2-NVy"/>
<constraint firstItem="dtK-8L-o11" firstAttribute="top" secondItem="dgg-SY-65p" secondAttribute="top" id="hui-Vk-WXp"/>
<constraint firstAttribute="trailing" secondItem="dtK-8L-o11" secondAttribute="trailing" id="oNB-WL-1Fm"/>
</constraints>
</view>
<connections>
<outlet property="playlistName" destination="vhi-nK-mWd" id="SF8-1x-eqS"/>
</connections>
</viewController>
<customObject id="2co-Sp-RKj" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="349" y="-261"/>
</scene>
</scenes>
<resources>
<image name="NSPreferencesGeneral" width="32" height="32"/>

View File

@ -30,6 +30,7 @@ class WindowController: NSWindowController {
@IBOutlet weak var searchQuery: NSSearchField!
@IBOutlet weak var savePlaylistMenuItem: NSMenuItem!
override func windowDidLoad() {
super.windowDidLoad()
window?.titleVisibility = .hidden

View File

@ -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 = "<group>"; };
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E42B18DF24538473000C8DFD /* MPDClient+Playlists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Playlists.swift"; sourceTree = "<group>"; };
E42B18E224539110000C8DFD /* PlaylistState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistState.swift; sourceTree = "<group>"; };
E42B18E5245391F6000C8DFD /* PlaylistActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistActions.swift; sourceTree = "<group>"; };
E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; };
E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
E439109722640213002982E9 /* SongNotifierService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongNotifierService.swift; sourceTree = "<group>"; };
@ -608,6 +621,7 @@
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = "<group>"; };
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E4ABD9EB2442935700D7A8EF /* SavePlaylistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePlaylistViewController.swift; sourceTree = "<group>"; };
E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerReducer.swift; sourceTree = "<group>"; };
E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
@ -635,6 +649,9 @@
E4DCCFAB23E4DB5D009A8113 /* Persephone-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Persephone-Bridging-Header.h"; sourceTree = "<group>"; };
E4DCCFAC23E4DB5D009A8113 /* MPDClientWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPDClientWrapper.h; sourceTree = "<group>"; };
E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = MPDClientWrapper.c; sourceTree = "<group>"; };
E4DFF2F72454E9A4001A89DD /* PlaylistReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistReducer.swift; sourceTree = "<group>"; };
E4DFF2F92454ECDF001A89DD /* MPDPlaylist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDPlaylist.swift; sourceTree = "<group>"; };
E4DFF2FB2454EED1001A89DD /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = "<group>"; };
E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
@ -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 = "<group>";
};
E4ABD9EA2442912100D7A8EF /* Playlists */ = {
isa = PBXGroup;
children = (
E4ABD9EB2442935700D7A8EF /* SavePlaylistViewController.swift */,
);
path = Playlists;
sourceTree = "<group>";
};
E4B11B5F226A4BED0075461B /* Reducers */ = {
isa = PBXGroup;
children = (
@ -1236,6 +1264,7 @@
E4B11B74226CC4D30075461B /* QueueReducer.swift */,
E4F2EFF124076B5E00198159 /* ServerReducer.swift */,
E440519D227BB0720090CD6F /* UIReducer.swift */,
E4DFF2F72454E9A4001A89DD /* PlaylistReducer.swift */,
);
path = Reducers;
sourceTree = "<group>";
@ -1252,6 +1281,7 @@
E4B11B67226A4FA00075461B /* QueueState.swift */,
E4F2EFED24076A2700198159 /* ServerState.swift */,
E440519F227BB0AB0090CD6F /* UIState.swift */,
E42B18E224539110000C8DFD /* PlaylistState.swift */,
);
path = State;
sourceTree = "<group>";
@ -1265,6 +1295,7 @@
E4B11BBF2275EE150075461B /* QueueActions.swift */,
E4F2EFEF24076B0900198159 /* ServerActions.swift */,
E440519B227BAF2E0090CD6F /* UIActions.swift */,
E42B18E5245391F6000C8DFD /* PlaylistActions.swift */,
);
path = Actions;
sourceTree = "<group>";
@ -1288,6 +1319,7 @@
E4E8CC9922075D370024217A /* MPDSong.swift */,
E4A642D922090CBE00067D21 /* MPDStatus.swift */,
E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */,
E4DFF2F92454ECDF001A89DD /* MPDPlaylist.swift */,
);
path = Models;
sourceTree = "<group>";
@ -1334,6 +1366,7 @@
E419E2862249B96600216A8C /* Song.swift */,
E47E2FDC2220A6D100F747E6 /* Time.swift */,
E4B11B72226A6C770075461B /* TrackTimer.swift */,
E4DFF2FB2454EED1001A89DD /* Playlist.swift */,
);
path = Models;
sourceTree = "<group>";
@ -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 */,

View File

@ -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))
}
}
}

View File

@ -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)
}
}

View File

@ -46,6 +46,9 @@ extension MPDClient {
if mpdIdle.contains(.database) {
self.fetchAllAlbums()
}
if mpdIdle.contains(.storedPlaylist) {
self.fetchPlaylists()
}
if mpdIdle.contains(.queue) {
self.fetchQueue()
self.fetchStatus()

View File

@ -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)
}
}

View File

@ -53,5 +53,10 @@ extension MPDClient {
// Song commands
case fetchAlbumArt
// Playlist commands
case fetchPlaylists
case saveQueueToPlaylist
case loadQueueFromPlaylist
}
}

View File

@ -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<Int8> {
return mpd_playlist_get_path(playlist)
}
var pathString: String {
return String(cString: path)
}
}
}

View File

@ -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])
}

View File

@ -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
}
}

View File

@ -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]
}

View File

@ -15,4 +15,5 @@ struct AppState: StateType {
var albumListState = AlbumListState()
var preferencesState = PreferencesState()
var uiState = UIState()
var playlistState = PlaylistState()
}

View File

@ -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] = []
}

View File

@ -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)
)
}

View File

@ -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
}