mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Compare commits
9 Commits
41120dd16c
...
34f941017f
| Author | SHA1 | Date | |
|---|---|---|---|
| 34f941017f | |||
| 225d511543 | |||
| 47c273c7ad | |||
| 98e741d9fe | |||
| c0da221074 | |||
| 6ccaef91a1 | |||
| c4e5f7408a | |||
| b46cbf229f | |||
| 063b8da202 |
@ -101,6 +101,7 @@
|
||||
E4B11BC02275EE150075461B /* QueueActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BBF2275EE150075461B /* QueueActions.swift */; };
|
||||
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; };
|
||||
E4B3DF6523D66A4400728F6B /* QueueSongCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */; };
|
||||
E4B46F8F2402E89800152157 /* MPDError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B46F8E2402E89800152157 /* MPDError.swift */; };
|
||||
E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */; };
|
||||
E4BB7F8F23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */; };
|
||||
E4BB7F9323E9150A00906E2F /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F9223E9150A00906E2F /* CoverArtService.swift */; };
|
||||
@ -120,6 +121,9 @@
|
||||
E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7623411AE300D45FF9 /* ArtistListActions.swift */; };
|
||||
E4F26F7923411B1500D45FF9 /* ArtistReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7823411B1500D45FF9 /* ArtistReducer.swift */; };
|
||||
E4F26F7B23411D5400D45FF9 /* MPDClient+Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7A23411D5400D45FF9 /* MPDClient+Artist.swift */; };
|
||||
E4F2EFEE24076A2700198159 /* ServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2EFED24076A2700198159 /* ServerState.swift */; };
|
||||
E4F2EFF024076B0900198159 /* ServerActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2EFEF24076B0900198159 /* ServerActions.swift */; };
|
||||
E4F2EFF224076B5E00198159 /* ServerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2EFF124076B5E00198159 /* ServerReducer.swift */; };
|
||||
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; };
|
||||
E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* QueueItem.swift */; };
|
||||
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; };
|
||||
@ -300,6 +304,7 @@
|
||||
E4B11BBF2275EE150075461B /* QueueActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueActions.swift; sourceTree = "<group>"; };
|
||||
E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = "<group>"; };
|
||||
E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongCoverView.swift; sourceTree = "<group>"; };
|
||||
E4B46F8E2402E89800152157 /* MPDError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDError.swift; sourceTree = "<group>"; };
|
||||
E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerDelegate.swift; sourceTree = "<group>"; };
|
||||
E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDAlbumArtImageDataProvider.swift; sourceTree = "<group>"; };
|
||||
E4BB7F9223E9150A00906E2F /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = "<group>"; };
|
||||
@ -320,6 +325,9 @@
|
||||
E4F26F7623411AE300D45FF9 /* ArtistListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistListActions.swift; sourceTree = "<group>"; };
|
||||
E4F26F7823411B1500D45FF9 /* ArtistReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistReducer.swift; sourceTree = "<group>"; };
|
||||
E4F26F7A23411D5400D45FF9 /* MPDClient+Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Artist.swift"; sourceTree = "<group>"; };
|
||||
E4F2EFED24076A2700198159 /* ServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerState.swift; sourceTree = "<group>"; };
|
||||
E4F2EFEF24076B0900198159 /* ServerActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerActions.swift; sourceTree = "<group>"; };
|
||||
E4F2EFF124076B5E00198159 /* ServerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerReducer.swift; sourceTree = "<group>"; };
|
||||
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; };
|
||||
E4F6B462221E125900ACF42A /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = "<group>"; };
|
||||
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; };
|
||||
@ -660,6 +668,7 @@
|
||||
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */,
|
||||
E4FF718F227601B400D4C412 /* PreferencesReducer.swift */,
|
||||
E4B11B74226CC4D30075461B /* QueueReducer.swift */,
|
||||
E4F2EFF124076B5E00198159 /* ServerReducer.swift */,
|
||||
E440519D227BB0720090CD6F /* UIReducer.swift */,
|
||||
);
|
||||
path = Reducers;
|
||||
@ -676,6 +685,7 @@
|
||||
E4B11B65226A4F830075461B /* PlayerState.swift */,
|
||||
E4FF718D2276010E00D4C412 /* PreferencesState.swift */,
|
||||
E4B11B67226A4FA00075461B /* QueueState.swift */,
|
||||
E4F2EFED24076A2700198159 /* ServerState.swift */,
|
||||
E440519F227BB0AB0090CD6F /* UIState.swift */,
|
||||
);
|
||||
path = State;
|
||||
@ -689,6 +699,7 @@
|
||||
E4B11BBD2275EDAA0075461B /* PlayerActions.swift */,
|
||||
E4FF71912276029000D4C412 /* PreferencesActions.swift */,
|
||||
E4B11BBF2275EE150075461B /* QueueActions.swift */,
|
||||
E4F2EFEF24076B0900198159 /* ServerActions.swift */,
|
||||
E440519B227BAF2E0090CD6F /* UIActions.swift */,
|
||||
);
|
||||
path = Actions;
|
||||
@ -707,6 +718,7 @@
|
||||
children = (
|
||||
E4EB237A220F7CF1008C70C0 /* MPDAlbum.swift */,
|
||||
E45962C52241A78500FC1A1E /* MPDCommand.swift */,
|
||||
E4B46F8E2402E89800152157 /* MPDError.swift */,
|
||||
E4C8B53D22349002009A20F3 /* MPDIdle.swift */,
|
||||
E4EB2378220F10B8008C70C0 /* MPDPair.swift */,
|
||||
E4E8CC9922075D370024217A /* MPDSong.swift */,
|
||||
@ -974,6 +986,8 @@
|
||||
E450AD7E222620A10091BED3 /* Album.swift in Sources */,
|
||||
E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */,
|
||||
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
|
||||
E4F2EFEE24076A2700198159 /* ServerState.swift in Sources */,
|
||||
E4B46F8F2402E89800152157 /* MPDError.swift in Sources */,
|
||||
E43AC1F822C7065A001E483C /* AlbumCoverButton.swift in Sources */,
|
||||
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */,
|
||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
||||
@ -991,6 +1005,7 @@
|
||||
E4FF71922276029000D4C412 /* PreferencesActions.swift in Sources */,
|
||||
E489E39922B85D0400CA8CBD /* NSPasteboard.swift in Sources */,
|
||||
E43AC1F122C68E6A001E483C /* NSPasteboardItem.swift in Sources */,
|
||||
E4F2EFF224076B5E00198159 /* ServerReducer.swift in Sources */,
|
||||
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
|
||||
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
|
||||
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */,
|
||||
@ -1021,6 +1036,7 @@
|
||||
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
|
||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
||||
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
|
||||
E4F2EFF024076B0900198159 /* ServerActions.swift in Sources */,
|
||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
||||
E4B11B66226A4F830075461B /* PlayerState.swift in Sources */,
|
||||
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */,
|
||||
@ -1179,7 +1195,7 @@
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
@ -1221,7 +1237,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libmpdclient/output",
|
||||
);
|
||||
MARKETING_VERSION = "0.16.1-alpha";
|
||||
MARKETING_VERSION = "0.17.0-alpha";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1238,7 +1254,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 54DPC63K8R;
|
||||
@ -1253,7 +1269,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/libmpdclient/output",
|
||||
);
|
||||
MARKETING_VERSION = "0.16.1-alpha";
|
||||
MARKETING_VERSION = "0.17.0-alpha";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1298,7 +1314,7 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 54DPC63K8R;
|
||||
@ -1348,7 +1364,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_IDENTITY = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_IDENTITY = "Mac Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = 54DPC63K8R;
|
||||
|
||||
@ -16,6 +16,8 @@ class AppDelegate: NSObject,
|
||||
MediaKeyTapDelegate {
|
||||
var mediaKeyTap: MediaKeyTap?
|
||||
|
||||
@IBOutlet weak var connectMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var disconnectMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var mainWindowMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
|
||||
@IBOutlet weak var playSelectedSongMenuItem: NSMenuItem!
|
||||
@ -28,13 +30,13 @@ class AppDelegate: NSObject,
|
||||
|
||||
App.store.subscribe(self) {
|
||||
$0.select {
|
||||
$0.uiState
|
||||
($0.serverState, $0.uiState)
|
||||
}
|
||||
}
|
||||
|
||||
_ = App.userNotificationsController
|
||||
|
||||
App.mpdServerController.connect()
|
||||
_ = App.mpdServerController
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
@ -105,6 +107,11 @@ class AppDelegate: NSObject,
|
||||
playSelectedSongNextMenuItem.isEnabled = selectedSong != nil
|
||||
addSelectedSongToQueueMenuItem.isEnabled = selectedSong != nil
|
||||
}
|
||||
|
||||
func setConnectMenuItemsState(connected: Bool) {
|
||||
connectMenuItem.isEnabled = !connected
|
||||
disconnectMenuItem.isEnabled = connected
|
||||
}
|
||||
|
||||
func handle(mediaKey: MediaKey, event: KeyEvent) {
|
||||
switch mediaKey {
|
||||
@ -117,6 +124,13 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func connectMenuAction(_ sender: NSMenuItem) {
|
||||
App.mpdServerController.connect()
|
||||
}
|
||||
@IBAction func disconnectMenuAction(_ sender: NSMenuItem) {
|
||||
App.mpdServerController.disconnect()
|
||||
}
|
||||
|
||||
@IBAction func updateDatabase(_ sender: NSMenuItem) {
|
||||
App.mpdClient.updateDatabase()
|
||||
}
|
||||
@ -182,11 +196,14 @@ class AppDelegate: NSObject,
|
||||
}
|
||||
|
||||
extension AppDelegate: StoreSubscriber {
|
||||
typealias StoreSubscriberStateType = UIState
|
||||
typealias StoreSubscriberStateType = (
|
||||
serverState: ServerState, uiState: UIState
|
||||
)
|
||||
|
||||
func newState(state: UIState) {
|
||||
updateDatabaseMenuItem.isEnabled = !state.databaseUpdating
|
||||
setMainWindowStateMenuItem(state: state.mainWindowState)
|
||||
setSongMenuItemsState(selectedSong: state.selectedSong)
|
||||
func newState(state: StoreSubscriberStateType) {
|
||||
updateDatabaseMenuItem.isEnabled = !state.uiState.databaseUpdating
|
||||
setMainWindowStateMenuItem(state: state.uiState.mainWindowState)
|
||||
setSongMenuItemsState(selectedSong: state.uiState.selectedSong)
|
||||
setConnectMenuItemsState(connected: state.serverState.connected)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,4 +12,5 @@ extension Notification.Name {
|
||||
static let didConnect = Notification.Name("MPDClientDidConnect")
|
||||
static let willDisconnect = Notification.Name("MPDClientWillDisconnect")
|
||||
static let didReloadAlbumArt = Notification.Name("MPDDidReloadAlbumArt")
|
||||
static let didRaiseError = Notification.Name("MPDDidRaiseError")
|
||||
}
|
||||
|
||||
@ -6,15 +6,28 @@
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AppKit
|
||||
|
||||
class MPDServerDelegate: MPDClientDelegate {
|
||||
func didConnect(mpdClient: MPDClient) {
|
||||
NotificationCenter.default.post(name: .didConnect, object: nil)
|
||||
DispatchQueue.main.async {
|
||||
App.store.dispatch(UpdateConnectedState(connected: true))
|
||||
}
|
||||
}
|
||||
|
||||
func willDisconnect(mpdClient: MPDClient) {
|
||||
NotificationCenter.default.post(name: .willDisconnect, object: nil)
|
||||
DispatchQueue.main.async {
|
||||
App.store.dispatch(UpdateConnectedState(connected: false))
|
||||
}
|
||||
}
|
||||
|
||||
func didRaiseError(
|
||||
mpdClient: MPDClient,
|
||||
error: MPDClient.MPDError
|
||||
) {
|
||||
NotificationCenter.default.post(name: .didRaiseError, object: error)
|
||||
}
|
||||
|
||||
func didUpdateStatus(mpdClient: MPDClient, status: MPDClient.MPDStatus) {
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
<items>
|
||||
<menuItem title="Persephone" id="1Xt-HY-uBw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Persephone" systemMenu="apple" id="uQy-DD-JDr">
|
||||
<menu key="submenu" title="Persephone" systemMenu="apple" autoenablesItems="NO" id="uQy-DD-JDr">
|
||||
<items>
|
||||
<menuItem title="About Persephone" id="5kV-Vb-QxS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
@ -29,7 +29,19 @@
|
||||
<segue destination="xYu-7w-E5x" kind="show" identifier="Preferences" id="OTW-56-v9E"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
|
||||
<menuItem title="Connect" keyEquivalent="c" id="VlA-Re-OZs">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="connectMenuAction:" target="Voe-Tx-rLC" id="Psl-MI-Dbx"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Disconnect" enabled="NO" keyEquivalent="d" id="yjK-qU-C6d">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="disconnectMenuAction:" target="Voe-Tx-rLC" id="smu-DN-Ubp"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="vcx-U6-vzu"/>
|
||||
<menuItem title="Services" id="NMo-om-nkz">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
|
||||
@ -61,6 +73,50 @@
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Edit" id="vqD-eY-QGd">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Edit" id="TuB-7j-hDK">
|
||||
<items>
|
||||
<menuItem title="Undo" keyEquivalent="z" id="Qfn-k5-qMJ">
|
||||
<connections>
|
||||
<action selector="undo:" target="Ady-hI-5gd" id="eEs-gZ-3LG"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Redo" keyEquivalent="Z" id="jTO-Fc-mMc">
|
||||
<connections>
|
||||
<action selector="redo:" target="Ady-hI-5gd" id="VB9-9R-Kbp"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="iGg-SA-H7J"/>
|
||||
<menuItem title="Cut" keyEquivalent="x" id="wMs-2i-IFp">
|
||||
<connections>
|
||||
<action selector="cut:" target="Ady-hI-5gd" id="kNV-s2-6rJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy" keyEquivalent="c" id="6Xf-49-5o2">
|
||||
<connections>
|
||||
<action selector="copy:" target="Ady-hI-5gd" id="QzD-pM-sbg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste" keyEquivalent="v" id="FPx-H8-gor">
|
||||
<connections>
|
||||
<action selector="paste:" target="Ady-hI-5gd" id="zK6-ON-cCD"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Delete" id="42c-Ut-F4e">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="delete:" target="Ady-hI-5gd" id="XbT-8a-cop"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Select All" keyEquivalent="a" id="oF5-zh-KZt">
|
||||
<connections>
|
||||
<action selector="selectAll:" target="Ady-hI-5gd" id="3gW-FK-DJV"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Database" id="usv-UH-vkC">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Database" autoenablesItems="NO" id="rFP-zL-1X4">
|
||||
@ -178,6 +234,8 @@
|
||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Persephone" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="addSelectedSongToQueueMenuItem" destination="JFH-jT-sBp" id="9dy-sJ-XYS"/>
|
||||
<outlet property="connectMenuItem" destination="VlA-Re-OZs" id="Zzt-yq-mtp"/>
|
||||
<outlet property="disconnectMenuItem" destination="yjK-qU-C6d" id="qel-dM-Gbl"/>
|
||||
<outlet property="mainWindowMenuItem" destination="1Sq-L7-znT" id="dC6-yY-6Ss"/>
|
||||
<outlet property="playSelectedSongMenuItem" destination="dyT-9E-DRY" id="UY2-SN-YMF"/>
|
||||
<outlet property="playSelectedSongNextMenuItem" destination="Q8j-jr-IOp" id="Jqh-ia-sMK"/>
|
||||
@ -375,7 +433,7 @@
|
||||
<splitViewController id="fnD-7K-pHK" customClass="MainSplitViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<splitViewItems>
|
||||
<splitViewItem holdingPriority="255" behavior="contentList" id="CWo-v7-gd2"/>
|
||||
<splitViewItem id="Mdr-U0-Vci"/>
|
||||
<splitViewItem id="bAy-ec-iQI"/>
|
||||
</splitViewItems>
|
||||
<splitView key="splitView" dividerStyle="thin" vertical="YES" id="g34-ef-XN0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
@ -387,7 +445,7 @@
|
||||
<connections>
|
||||
<outlet property="splitView" destination="g34-ef-XN0" id="YEc-LL-DoS"/>
|
||||
<segue destination="KIP-rq-4dM" kind="relationship" relationship="splitItems" id="Vmb-hY-d12"/>
|
||||
<segue destination="SjO-VS-1bb" kind="relationship" relationship="splitItems" id="peC-gL-WJ1"/>
|
||||
<segue destination="gPn-fP-LFc" kind="relationship" relationship="splitItems" id="eC9-b4-YWt"/>
|
||||
</connections>
|
||||
</splitViewController>
|
||||
<customObject id="Dag-kO-ps3" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
@ -501,53 +559,6 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1411" y="-420"/>
|
||||
</scene>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="VvW-vT-alQ">
|
||||
<objects>
|
||||
<customObject id="MSG-y7-cKU" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<viewController id="SjO-VS-1bb" sceneMemberID="viewController">
|
||||
<view key="view" id="BRY-0R-F3u">
|
||||
<rect key="frame" x="0.0" y="0.0" width="478" height="558"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<tabView type="noTabsNoBorder" initialItem="XgS-cX-SDH" translatesAutoresizingMaskIntoConstraints="NO" id="ARv-cj-xlz">
|
||||
<rect key="frame" x="0.0" y="0.0" width="478" height="558"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Albums" identifier="" id="XgS-cX-SDH">
|
||||
<view key="view" id="hB7-hN-SbB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="478" height="558"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView translatesAutoresizingMaskIntoConstraints="NO" id="moE-bb-Zvg">
|
||||
<rect key="frame" x="-3" y="-3" width="481" height="561"/>
|
||||
<connections>
|
||||
<segue destination="gPn-fP-LFc" kind="embed" id="2iB-9y-I9h"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="moE-bb-Zvg" firstAttribute="top" secondItem="hB7-hN-SbB" secondAttribute="top" id="DUI-jy-8D7"/>
|
||||
<constraint firstItem="moE-bb-Zvg" firstAttribute="leading" secondItem="hB7-hN-SbB" secondAttribute="leading" constant="-3" id="dCS-Kx-2UP"/>
|
||||
<constraint firstAttribute="trailing" secondItem="moE-bb-Zvg" secondAttribute="trailing" id="qey-4e-xfu"/>
|
||||
<constraint firstAttribute="bottom" secondItem="moE-bb-Zvg" secondAttribute="bottom" constant="-3" id="ziA-Xh-dlz"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
</tabViewItems>
|
||||
</tabView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="ARv-cj-xlz" secondAttribute="trailing" id="nXk-bi-ua4"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ARv-cj-xlz" secondAttribute="bottom" id="nzf-Jw-Bpk"/>
|
||||
<constraint firstItem="ARv-cj-xlz" firstAttribute="top" secondItem="BRY-0R-F3u" secondAttribute="top" id="v6W-0L-kQ1"/>
|
||||
<constraint firstItem="ARv-cj-xlz" firstAttribute="leading" secondItem="BRY-0R-F3u" secondAttribute="leading" id="w2Z-xv-Fwz"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="1436" y="238"/>
|
||||
</scene>
|
||||
<!--General-->
|
||||
<scene sceneID="xTC-Y5-Agk">
|
||||
<objects>
|
||||
@ -912,7 +923,7 @@
|
||||
</viewController>
|
||||
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="2038" y="87"/>
|
||||
<point key="canvasLocation" x="1485" y="182"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
|
||||
@ -38,13 +38,14 @@ class WindowController: NSWindowController {
|
||||
|
||||
App.store.subscribe(self) {
|
||||
$0.select {
|
||||
($0.playerState, $0.uiState)
|
||||
($0.serverState, $0.playerState, $0.uiState)
|
||||
}
|
||||
}
|
||||
|
||||
App.store.dispatch(MainWindowDidOpenAction())
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(reportError), name: .didRaiseError, object: nil)
|
||||
|
||||
trackProgress.font = .timerFont
|
||||
trackRemaining.font = .timerFont
|
||||
@ -76,9 +77,18 @@ class WindowController: NSWindowController {
|
||||
}
|
||||
}
|
||||
|
||||
func setShuffleRepeatState(_ state: PlayerState) {
|
||||
shuffleState.state = state.shuffleState ? .on : .off
|
||||
repeatState.state = state.repeatState ? .on : .off
|
||||
func setShuffleRepeatState(
|
||||
_ serverState: ServerState,
|
||||
_ playerState: PlayerState
|
||||
) {
|
||||
shuffleState.isEnabled = serverState.connected
|
||||
repeatState.isEnabled = serverState.connected
|
||||
shuffleState.state = playerState.shuffleState ? .on : .off
|
||||
repeatState.state = playerState.repeatState ? .on : .off
|
||||
}
|
||||
|
||||
func setSearchState(_ serverState: ServerState) {
|
||||
searchQuery.isEnabled = serverState.connected
|
||||
}
|
||||
|
||||
func setTrackProgressControls(_ playerState: PlayerState) {
|
||||
@ -144,10 +154,56 @@ class WindowController: NSWindowController {
|
||||
|
||||
@objc func willDisconnect() {
|
||||
DispatchQueue.main.async {
|
||||
App.store.dispatch(SetSearchQuery(searchQuery: ""))
|
||||
App.store.dispatch(ResetStatusAction())
|
||||
self.searchQuery.stringValue = ""
|
||||
}
|
||||
}
|
||||
|
||||
@objc func reportError(_ notification: NSNotification) {
|
||||
guard let error = notification.object as? MPDClient.MPDError
|
||||
else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard let window = NSApplication.shared.mainWindow ?? self.window
|
||||
else { return }
|
||||
|
||||
let alert = NSAlert(error: error)
|
||||
alert.messageText = error.message
|
||||
|
||||
alert.alertStyle = error.recovered ? .warning : .critical
|
||||
|
||||
print(error.mpdError)
|
||||
|
||||
switch error.mpdError {
|
||||
case MPD_ERROR_MALFORMED,
|
||||
MPD_ERROR_ARGUMENT:
|
||||
alert.informativeText = "Please check the mpd log for more details."
|
||||
case MPD_ERROR_SYSTEM,
|
||||
MPD_ERROR_TIMEOUT:
|
||||
alert.informativeText = "Is the mpd server running?"
|
||||
case MPD_ERROR_RESOLVER:
|
||||
alert.informativeText = "Check your network connection."
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if !error.recovered {
|
||||
alert.addButton(withTitle: "Reconnect")
|
||||
alert.addButton(withTitle: "Dismiss")
|
||||
}
|
||||
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
switch response {
|
||||
case .alertFirstButtonReturn:
|
||||
if !error.recovered {
|
||||
App.mpdServerController.connect()
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Refactor this using a gesture recognizer
|
||||
@IBAction func changeTrackProgress(_ sender: NSSlider) {
|
||||
@ -224,12 +280,15 @@ extension WindowController: NSWindowDelegate {
|
||||
}
|
||||
|
||||
extension WindowController: StoreSubscriber {
|
||||
typealias StoreSubscriberStateType = (playerState: PlayerState, uiState: UIState)
|
||||
typealias StoreSubscriberStateType = (
|
||||
serverState: ServerState, playerState: PlayerState, uiState: UIState
|
||||
)
|
||||
|
||||
func newState(state: StoreSubscriberStateType) {
|
||||
DispatchQueue.main.async {
|
||||
self.setTransportControlState(state.playerState)
|
||||
self.setShuffleRepeatState(state.playerState)
|
||||
self.setShuffleRepeatState(state.serverState, state.playerState)
|
||||
self.setSearchState(state.serverState)
|
||||
self.setTrackProgressControls(state.playerState)
|
||||
self.setDatabaseUpdatingIndicator(state.uiState)
|
||||
self.setVolumeControlIcon(state.playerState)
|
||||
|
||||
@ -13,6 +13,8 @@ extension MPDClient {
|
||||
command: MPDCommand,
|
||||
userData: Dictionary<String, Any> = [:]
|
||||
) {
|
||||
guard command == .connect || isConnected else { return }
|
||||
|
||||
switch command {
|
||||
|
||||
case .connect:
|
||||
@ -157,7 +159,9 @@ extension MPDClient {
|
||||
let commandOperation = BlockOperation() { [unowned self] in
|
||||
self.sendCommand(command: command, userData: userData)
|
||||
|
||||
self.idle(forceIdle)
|
||||
if self.checkError() {
|
||||
self.idle(forceIdle)
|
||||
}
|
||||
}
|
||||
|
||||
commandOperation.queuePriority = priority
|
||||
|
||||
@ -11,16 +11,19 @@ import mpdclient
|
||||
|
||||
extension MPDClient {
|
||||
func createConnection(host: String, port: Int) {
|
||||
guard let connection = mpd_connection_new(host, UInt32(port), 10000),
|
||||
mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS
|
||||
else { return }
|
||||
connection = mpd_connection_new(host, UInt32(port), 10000)
|
||||
let error = mpd_connection_get_error(connection)
|
||||
|
||||
guard error == MPD_ERROR_SUCCESS else {
|
||||
_ = handleError(mpdError: error)
|
||||
return
|
||||
}
|
||||
|
||||
self.isConnected = true
|
||||
|
||||
guard let status = mpd_run_status(connection)
|
||||
else { return }
|
||||
|
||||
self.connection = connection
|
||||
self.status = MPDStatus(status)
|
||||
|
||||
delegate?.didConnect(mpdClient: self)
|
||||
@ -50,4 +53,10 @@ extension MPDClient {
|
||||
func disconnect() {
|
||||
enqueueCommand(command: .disconnect)
|
||||
}
|
||||
|
||||
func resetConnection() {
|
||||
delegate?.willDisconnect(mpdClient: self)
|
||||
mpd_connection_free(connection)
|
||||
self.isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,38 @@ import Foundation
|
||||
import mpdclient
|
||||
|
||||
extension MPDClient {
|
||||
func checkError() -> Bool {
|
||||
let error = mpd_connection_get_error(connection)
|
||||
|
||||
if error != MPD_ERROR_SUCCESS {
|
||||
return handleError(mpdError: error)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handleError(mpdError: mpd_error) -> Bool {
|
||||
guard let errorMessage = mpd_connection_get_error_message(connection)
|
||||
else { return true }
|
||||
|
||||
let message = String(cString: errorMessage)
|
||||
|
||||
let recovered = mpd_connection_clear_error(connection)
|
||||
|
||||
let error = MPDError(
|
||||
mpdError: mpdError,
|
||||
recovered: recovered,
|
||||
message: message
|
||||
)
|
||||
delegate?.didRaiseError(mpdClient: self, error: error)
|
||||
|
||||
if !recovered {
|
||||
resetConnection()
|
||||
}
|
||||
|
||||
return recovered
|
||||
}
|
||||
|
||||
func getLastErrorMessage() -> String? {
|
||||
if mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS {
|
||||
return nil
|
||||
|
||||
@ -11,6 +11,8 @@ import mpdclient
|
||||
|
||||
extension MPDClient {
|
||||
func noIdle() {
|
||||
guard isConnected else { return }
|
||||
|
||||
do {
|
||||
idleLock.lock()
|
||||
defer { idleLock.unlock() }
|
||||
@ -22,6 +24,8 @@ extension MPDClient {
|
||||
}
|
||||
|
||||
func idle(_ force: Bool = false) {
|
||||
guard isConnected else { return }
|
||||
|
||||
let shouldIdle: Bool
|
||||
|
||||
do {
|
||||
@ -50,8 +54,8 @@ extension MPDClient {
|
||||
wasIdle = isIdle
|
||||
isIdle = false
|
||||
}
|
||||
|
||||
if wasIdle {
|
||||
|
||||
if checkError() && wasIdle {
|
||||
if mpdIdle.contains(.database) {
|
||||
self.fetchAllAlbums()
|
||||
}
|
||||
|
||||
18
Persephone/MPDClient/Models/MPDError.swift
Normal file
18
Persephone/MPDClient/Models/MPDError.swift
Normal file
@ -0,0 +1,18 @@
|
||||
//
|
||||
// MPDError.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2/23/20.
|
||||
// Copyright © 2020 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import mpdclient
|
||||
|
||||
extension MPDClient {
|
||||
struct MPDError: Error {
|
||||
let mpdError: mpd_error
|
||||
let recovered: Bool
|
||||
let message: String
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,8 @@ import Foundation
|
||||
protocol MPDClientDelegate {
|
||||
func didConnect(mpdClient: MPDClient)
|
||||
func willDisconnect(mpdClient: MPDClient)
|
||||
|
||||
func didRaiseError(mpdClient: MPDClient, error: MPDClient.MPDError)
|
||||
|
||||
func didUpdateStatus(mpdClient: MPDClient, status: MPDClient.MPDStatus)
|
||||
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import ReSwift
|
||||
|
||||
struct UpdateCurrentSongAction: Action {
|
||||
@ -17,6 +16,8 @@ struct UpdateElapsedTimeAction: Action {
|
||||
var elapsedTimeMs: UInt = 0
|
||||
}
|
||||
|
||||
struct ResetStatusAction: Action {}
|
||||
|
||||
struct UpdateStatusAction: Action {
|
||||
var status: MPDClient.MPDStatus
|
||||
}
|
||||
|
||||
13
Persephone/State/Actions/ServerActions.swift
Normal file
13
Persephone/State/Actions/ServerActions.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// ServerActions.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2/26/20.
|
||||
// Copyright © 2020 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import ReSwift
|
||||
|
||||
struct UpdateConnectedState: Action {
|
||||
var connected: Bool
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
import ReSwift
|
||||
|
||||
struct AppState: StateType {
|
||||
var serverState = ServerState()
|
||||
var playerState = PlayerState()
|
||||
var queueState = QueueState()
|
||||
var albumListState = AlbumListState()
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import ReSwift
|
||||
|
||||
struct PlayerState: StateType {
|
||||
|
||||
@ -10,6 +10,7 @@ import ReSwift
|
||||
|
||||
func appReducer(action: Action, state: AppState?) -> AppState {
|
||||
return AppState(
|
||||
serverState: serverReducer(action: action, state: state?.serverState),
|
||||
playerState: playerReducer(action: action, state: state?.playerState),
|
||||
queueState: queueReducer(action: action, state: state?.queueState),
|
||||
albumListState: albumListReducer(action: action, state: state?.albumListState),
|
||||
|
||||
@ -13,6 +13,14 @@ func playerReducer(action: Action, state: PlayerState?) -> PlayerState {
|
||||
var state = state ?? PlayerState()
|
||||
|
||||
switch action {
|
||||
case is ResetStatusAction:
|
||||
state.state = .unknown
|
||||
state.totalTime = 0
|
||||
state.elapsedTimeMs = 0
|
||||
state.shuffleState = false
|
||||
state.repeatState = false
|
||||
state.volume = -1
|
||||
|
||||
case let action as UpdateStatusAction:
|
||||
state.status = action.status
|
||||
state.state = action.status.state
|
||||
|
||||
23
Persephone/State/Reducers/ServerReducer.swift
Normal file
23
Persephone/State/Reducers/ServerReducer.swift
Normal file
@ -0,0 +1,23 @@
|
||||
//
|
||||
// ServerReducer.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2/26/20.
|
||||
// Copyright © 2020 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import ReSwift
|
||||
|
||||
func serverReducer(action: Action, state: ServerState?) -> ServerState {
|
||||
var state = state ?? ServerState()
|
||||
|
||||
switch action {
|
||||
case let action as UpdateConnectedState:
|
||||
state.connected = action.connected
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
13
Persephone/State/ServerState.swift
Normal file
13
Persephone/State/ServerState.swift
Normal file
@ -0,0 +1,13 @@
|
||||
//
|
||||
// ServerState.swift
|
||||
// Persephone
|
||||
//
|
||||
// Created by Daniel Barber on 2/26/20.
|
||||
// Copyright © 2020 Dan Barber. All rights reserved.
|
||||
//
|
||||
|
||||
import ReSwift
|
||||
|
||||
struct ServerState: StateType {
|
||||
var connected: Bool = false
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user