From 53b759b723d7bc2415ab019cada03bfa662fc455 Mon Sep 17 00:00:00 2001 From: Daniel Barber Date: Fri, 19 Jul 2019 12:02:37 -0400 Subject: [PATCH] Can drag an album onto the queue --- Persephone.xcodeproj/project.pbxproj | 8 +++ ...wController+NSCollectionViewDelegate.swift | 2 +- .../Controllers/QueueViewController.swift | 2 +- Persephone/DataSources/QueueDataSource.swift | 69 ++++++++++++------- .../Extensions/MPDClient+Album.swift | 11 +-- .../Extensions/MPDClient+Command.swift | 6 ++ .../Extensions/MPDClient+Queue.swift | 16 +++++ .../Extensions/MPDClient+Songs.swift | 28 ++++++++ Persephone/MPDClient/Models/MPDCommand.swift | 1 + Persephone/MPDClient/Models/MPDSong.swift | 26 +------ Persephone/MPDClient/Models/MPDTag.swift | 36 ++++++++++ 11 files changed, 145 insertions(+), 60 deletions(-) create mode 100644 Persephone/MPDClient/Extensions/MPDClient+Songs.swift create mode 100644 Persephone/MPDClient/Models/MPDTag.swift diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index bfb1827..1decbdb 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; }; E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.swift */; }; E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; }; + E42A4D4F22E20D7D001C6CAD /* MPDTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */; }; + 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 */; }; E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; }; @@ -233,6 +235,8 @@ E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = ""; }; E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = ""; }; E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = ""; }; + E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDTag.swift; sourceTree = ""; }; + 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 = ""; }; E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = ""; }; @@ -438,6 +442,7 @@ E41E5304223BFB0700173814 /* MPDClient+Error.swift */, E41E5302223BF9C300173814 /* MPDClient+Idle.swift */, E41E5300223BF99300173814 /* MPDClient+Queue.swift */, + E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */, E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */, E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */, @@ -635,6 +640,7 @@ E4EB2378220F10B8008C70C0 /* MPDPair.swift */, E4E8CC9922075D370024217A /* MPDSong.swift */, E4A642D922090CBE00067D21 /* MPDStatus.swift */, + E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */, ); path = Models; sourceTree = ""; @@ -897,6 +903,7 @@ E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */, E4B11BA92274EDE30075461B /* Loading.swift in Sources */, E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */, + E42A4D4F22E20D7D001C6CAD /* MPDTag.swift in Sources */, E43AC1F522C6A4F4001E483C /* DraggedAlbum.swift in Sources */, E40FE71B221B904300A4223F /* NSEvent.swift in Sources */, E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */, @@ -984,6 +991,7 @@ E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */, E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */, E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */, + E42A4D5122E2167E001C6CAD /* MPDClient+Songs.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Persephone/Controllers/AlbumViewController+NSCollectionViewDelegate.swift b/Persephone/Controllers/AlbumViewController+NSCollectionViewDelegate.swift index e5e5eb2..2a52547 100644 --- a/Persephone/Controllers/AlbumViewController+NSCollectionViewDelegate.swift +++ b/Persephone/Controllers/AlbumViewController+NSCollectionViewDelegate.swift @@ -11,7 +11,7 @@ import AppKit extension AlbumViewController: NSCollectionViewDelegate { func registerForDragAndDrop(_ collectionView: NSCollectionView) { collectionView.registerForDraggedTypes([.albumPasteboardType]) - collectionView.setDraggingSourceOperationMask(.every, forLocal: true) + collectionView.setDraggingSourceOperationMask(.copy, forLocal: true) } func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt index: Int) -> NSPasteboardWriting? { diff --git a/Persephone/Controllers/QueueViewController.swift b/Persephone/Controllers/QueueViewController.swift index 660aa7f..36e2ac7 100644 --- a/Persephone/Controllers/QueueViewController.swift +++ b/Persephone/Controllers/QueueViewController.swift @@ -24,7 +24,7 @@ class QueueViewController: NSViewController { queueView.dataSource = dataSource queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle - queueView.registerForDraggedTypes([.songPasteboardType]) + queueView.registerForDraggedTypes([.songPasteboardType, .albumPasteboardType]) queueView.draggingDestinationFeedbackStyle = .regular } diff --git a/Persephone/DataSources/QueueDataSource.swift b/Persephone/DataSources/QueueDataSource.swift index a2820b4..2c1b7ee 100644 --- a/Persephone/DataSources/QueueDataSource.swift +++ b/Persephone/DataSources/QueueDataSource.swift @@ -57,47 +57,68 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource { var newQueuePos = index - 1 guard newQueuePos >= 0, - let draggingTypes = info.draggingPasteboard.types, - draggingTypes.contains(.songPasteboardType), - let pasteboardItem = info.draggingPasteboard.pasteboardItems?.first, - let draggedSong = pasteboardItem.draggedSong(forType: .songPasteboardType) + let draggingTypes = info.draggingPasteboard.types else { return [] } - switch draggedSong.type { - case let .queueItem(queuePos): - if newQueuePos > queuePos { newQueuePos -= 1 } - - guard queuePos != newQueuePos + if draggingTypes.contains(.songPasteboardType) { + guard let pasteboardItem = info.draggingPasteboard.pasteboardItems?.first, + let draggedSong = pasteboardItem.draggedSong(forType: .songPasteboardType) else { return [] } - return .move - case .albumSongItem: + switch draggedSong.type { + case let .queueItem(queuePos): + if newQueuePos > queuePos { newQueuePos -= 1 } + + guard queuePos != newQueuePos + else { return [] } + + return .move + case .albumSongItem: + return .copy + } + } else if draggingTypes.contains(.albumPasteboardType) { return .copy } + + return [] } func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { var newQueuePos = index - 1 - guard let draggingTypes = info.draggingPasteboard.types, - draggingTypes.contains(.songPasteboardType), - let data = info.draggingPasteboard.data(forType: .songPasteboardType), - let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data) + guard let draggingTypes = info.draggingPasteboard.types else { return false } - switch draggedSong.type { - case let .queueItem(queuePos): - if newQueuePos > queuePos { newQueuePos -= 1 } - - guard queuePos != newQueuePos + if draggingTypes.contains(.songPasteboardType) { + guard let data = info.draggingPasteboard.data(forType: .songPasteboardType), + let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data) else { return false } - App.mpdClient.moveSongInQueue(at: queuePos, to: newQueuePos) - return true - case let .albumSongItem(uri): - App.mpdClient.addSongToQueue(songUri: uri, at: newQueuePos) + switch draggedSong.type { + case let .queueItem(queuePos): + if newQueuePos > queuePos { newQueuePos -= 1 } + + guard queuePos != newQueuePos + else { return false } + + App.mpdClient.moveSongInQueue(at: queuePos, to: newQueuePos) + return true + case let .albumSongItem(uri): + App.mpdClient.addSongToQueue(songUri: uri, at: newQueuePos) + return true + } + } else if draggingTypes.contains(.albumPasteboardType) { + guard let data = info.draggingPasteboard.data(forType: .albumPasteboardType), + let draggedAlbum = try? PropertyListDecoder().decode(DraggedAlbum.self, from: data) + else { return false } + + let mpdAlbum = MPDClient.MPDAlbum(title: draggedAlbum.title, artist: draggedAlbum.artist) + + App.mpdClient.addAlbumToQueue(album: mpdAlbum, at: newQueuePos) return true } + + return false } func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) { diff --git a/Persephone/MPDClient/Extensions/MPDClient+Album.swift b/Persephone/MPDClient/Extensions/MPDClient+Album.swift index b5ca72c..9a909f9 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Album.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Album.swift @@ -101,16 +101,7 @@ extension MPDClient { func albumSongs(for album: MPDAlbum, callback: ([MPDSong]) -> Void) { guard isConnected else { return } - var songs: [MPDSong] = [] - - mpd_search_db_songs(self.connection, true) - mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title) - mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist) - mpd_search_commit(self.connection) - - while let song = mpd_recv_song(self.connection) { - songs.append(MPDSong(song)) - } + let songs = searchSongs([MPDTag.album: album.title, MPDTag.artist: album.artist]) callback(songs) } diff --git a/Persephone/MPDClient/Extensions/MPDClient+Command.swift b/Persephone/MPDClient/Extensions/MPDClient+Command.swift index cff97af..9d96093 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Command.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Command.swift @@ -81,6 +81,12 @@ extension MPDClient { else { return } sendAddSongToQueue(uri: songUri, at: queuePos) + case .addAlbumToQueue: + guard let album = userData["album"] as? MPDAlbum, + let queuePos = userData["queuePos"] as? Int + else { return } + sendAddAlbumToQueue(album: album, at: queuePos) + // Album commands case .fetchAllAlbums: allAlbums() diff --git a/Persephone/MPDClient/Extensions/MPDClient+Queue.swift b/Persephone/MPDClient/Extensions/MPDClient+Queue.swift index 4828f02..c4f908c 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Queue.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Queue.swift @@ -38,6 +38,10 @@ extension MPDClient { enqueueCommand(command: .addSongToQueue, userData: ["uri": songUri, "queuePos": queuePos]) } + func addAlbumToQueue(album: MPDAlbum, at queuePos: Int) { + enqueueCommand(command: .addAlbumToQueue, userData: ["album": album, "queuePos": queuePos]) + } + func sendPlayTrack(at queuePos: Int) { mpd_run_play_pos(self.connection, UInt32(queuePos)) } @@ -80,4 +84,16 @@ extension MPDClient { func sendAddSongToQueue(uri: String, at queuePos: Int) { mpd_run_add_id_to(self.connection, uri, UInt32(queuePos)) } + + func sendAddAlbumToQueue(album: MPDAlbum, at queuePos: Int) { + let songs = searchSongs([MPDTag.album: album.title, MPDTag.artist: album.artist]) + + var insertPos = UInt32(queuePos) + + for song in songs { + mpd_run_add_id_to(self.connection, song.uri, insertPos) + + insertPos += 1 + } + } } diff --git a/Persephone/MPDClient/Extensions/MPDClient+Songs.swift b/Persephone/MPDClient/Extensions/MPDClient+Songs.swift new file mode 100644 index 0000000..c05fc85 --- /dev/null +++ b/Persephone/MPDClient/Extensions/MPDClient+Songs.swift @@ -0,0 +1,28 @@ +// +// MPDClient+Songs.swift +// Persephone +// +// Created by Daniel Barber on 2019/7/19. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation +import mpdclient + +extension MPDClient { + func searchSongs(_ terms: [MPDClient.MPDTag: String]) -> [MPDSong] { + var songs: [MPDSong] = [] + + mpd_search_db_songs(self.connection, true) + for (tag, term) in terms { + mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, tag.mpdTag(), term) + } + mpd_search_commit(self.connection) + + while let song = mpd_recv_song(self.connection) { + songs.append(MPDSong(song)) + } + + return songs + } +} diff --git a/Persephone/MPDClient/Models/MPDCommand.swift b/Persephone/MPDClient/Models/MPDCommand.swift index def0fd5..7ba12a9 100644 --- a/Persephone/MPDClient/Models/MPDCommand.swift +++ b/Persephone/MPDClient/Models/MPDCommand.swift @@ -35,6 +35,7 @@ extension MPDClient { case removeSong case moveSongInQueue case addSongToQueue + case addAlbumToQueue // Album commands case fetchAllAlbums diff --git a/Persephone/MPDClient/Models/MPDSong.swift b/Persephone/MPDClient/Models/MPDSong.swift index a907e30..84d0a15 100644 --- a/Persephone/MPDClient/Models/MPDSong.swift +++ b/Persephone/MPDClient/Models/MPDSong.swift @@ -12,26 +12,6 @@ import mpdclient extension MPDClient { class MPDSong { let song: OpaquePointer - - enum TagType: Int { - case unknown = -1 - case artist, album, albumArtist, title, track, name, - genre, date, composer, performer, comment, disc - - case musicBrainzArtistId - case musicBrainzAlbumId - case musicBrainzAlbumArtistId - case musicBrainzTrackId - case musicBrainzReleaseTrackId - - case originalDate - - case artistSort - case albumArtistSort - case albumSort - - case tagCount - } init(_ song: OpaquePointer) { self.song = song @@ -68,10 +48,8 @@ extension MPDClient { } } - func getTag(_ tagType: TagType) -> String { - let mpdTagType = mpd_tag_type(rawValue: Int32(tagType.rawValue)) - - guard let tag = mpd_song_get_tag(song, mpdTagType, 0) + func getTag(_ tagType: MPDTag) -> String { + guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0) else { return "" } return String(cString: tag) diff --git a/Persephone/MPDClient/Models/MPDTag.swift b/Persephone/MPDClient/Models/MPDTag.swift new file mode 100644 index 0000000..554828c --- /dev/null +++ b/Persephone/MPDClient/Models/MPDTag.swift @@ -0,0 +1,36 @@ +// +// MPDTag.swift +// Persephone +// +// Created by Daniel Barber on 2019/7/19. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation +import mpdclient + +extension MPDClient { + enum MPDTag: Int { + case unknown = -1 + case artist, album, albumArtist, title, track, name, + genre, date, composer, performer, comment, disc + + case musicBrainzArtistId + case musicBrainzAlbumId + case musicBrainzAlbumArtistId + case musicBrainzTrackId + case musicBrainzReleaseTrackId + + case originalDate + + case artistSort + case albumArtistSort + case albumSort + + case tagCount + + func mpdTag() -> mpd_tag_type { + return mpd_tag_type(rawValue: Int32(self.rawValue)) + } + } +}