1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
6458b7402b
Adjust version number 2020-01-20 13:38:08 -05:00
ae075914b3
Use NSString helper here 2020-01-20 13:36:08 -05:00
27425b76d6
No need for parentheses here 2020-01-20 13:36:06 -05:00
2932ab9e33
Eliminate some single line functions 2020-01-20 13:36:04 -05:00
f878daad2c
Create constants for sizes 2020-01-20 13:36:03 -05:00
555a99d5be
We don't need self here 2020-01-20 13:36:03 -05:00
b8698d2830
✂️ stray space
Co-Authored-By: louis-antonopoulos <louis@thoughtbot.com>
2020-01-20 13:36:02 -05:00
8f3e93db66
✂️ 2020-01-20 13:36:02 -05:00
3405df578b
Something something storyboard editor 2020-01-20 13:36:02 -05:00
0e996cbb4c
We can simplify this 2020-01-20 13:36:01 -05:00
26646ea88e
We don't need this at the moment 2020-01-20 13:36:01 -05:00
7b1728b521
Clear search field on disconnect 2020-01-20 13:36:00 -05:00
de5d3e94fb
Fix connection bug
TODO: We really should do something better about informing the user of
connection errors and the like.
2020-01-20 13:36:00 -05:00
b581f297c9
Refactor connection logic
The app would crash when connection settings were changed. This
refactors the connection logic to be consistent with the rest of the
mpdclient command structure. This ultimately fixes the bug.
2020-01-20 13:35:59 -05:00
d075a06c41
Fix queuepos out of bounds error
Sometimes the status gets updated before the queue has been retrieved.
In these cases we want to skip setting the current song until it has
been.
2020-01-20 13:35:50 -05:00
9f948df141
Use the search query UI state 2020-01-20 13:35:49 -05:00
04d4c77f8d
Update version to prealpha 2020-01-20 13:35:49 -05:00
1dae55c2b8
Clear album are when a cell gets reused
This prevents the wrong album art showing up for albums that don't have
any album are.
2020-01-20 13:35:48 -05:00
1d5d87c75b
Responding to repeated space can get us into trouble 2020-01-20 13:35:48 -05:00
91fa2f62dc
We're not using these any more 2019-12-06 17:52:58 -05:00
80597cdd56
Disable Musicbrainz artwork pref 2019-12-06 17:51:06 -05:00
e2149a2f3e
Add PNG's and change the search order for album art 2019-12-06 17:51:06 -05:00
c201ebaab6
Fix bug where playing a song or album crashes the app
Playing an album would only crash the app if the queue was empty before
trying to play.
2019-12-06 17:51:06 -05:00
bed09eb888
Fix bug with getting tracks for compilations 2019-12-06 17:51:05 -05:00
b712a8d00d
Use Kingfisher to fetch the notification album art 2019-12-06 17:51:05 -05:00
4322a25b8b
Get currently playing art using Kingfisher 2019-12-06 17:51:05 -05:00
d407f1e5f9
Get album browser artwork using Kingfisher 2019-12-06 17:51:05 -05:00
01428d8126
WIP 2019-12-06 17:51:05 -05:00
42d274058f
WIP: Get album metadata when fetching albums 2019-12-06 17:51:04 -05:00
4af3e7aead
WIP: Search 2019-12-06 17:51:04 -05:00
87d4f33b09
WIP: Search 2019-12-06 17:51:04 -05:00
37 changed files with 334 additions and 529 deletions

View File

@ -29,9 +29,6 @@
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; }; E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; };
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; }; E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; };
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; }; E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; };
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */; };
E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */; };
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */; };
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; }; E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.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 */; }; E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
@ -49,6 +46,7 @@
E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67A822909793007DCF55 /* AlbumDetailView.swift */; }; E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67A822909793007DCF55 /* AlbumDetailView.swift */; };
E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E43B67A922909793007DCF55 /* AlbumDetailView.xib */; }; E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E43B67A922909793007DCF55 /* AlbumDetailView.xib */; };
E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */; }; E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */; };
E43BECA0238835DC00CAF1EB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = E43BEC9F238835DC00CAF1EB /* Kingfisher */; };
E4405192227644340090CD6F /* MPDServerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4405191227644340090CD6F /* MPDServerController.swift */; }; E4405192227644340090CD6F /* MPDServerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4405191227644340090CD6F /* MPDServerController.swift */; };
E44051942278765A0090CD6F /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44051932278765A0090CD6F /* App.swift */; }; E44051942278765A0090CD6F /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44051932278765A0090CD6F /* App.swift */; };
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E440519B227BAF2E0090CD6F /* UIActions.swift */; }; E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E440519B227BAF2E0090CD6F /* UIActions.swift */; };
@ -74,7 +72,6 @@
E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E39C22B9CF0000CA8CBD /* NSView.swift */; }; E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E39C22B9CF0000CA8CBD /* NSView.swift */; };
E489E3A422B9D31800CA8CBD /* DraggedSongView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */; }; E489E3A422B9D31800CA8CBD /* DraggedSongView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */; };
E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */; }; E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */; };
E48E92D7235113DF00A5E1BB /* Metadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = E48E92D6235113DF00A5E1BB /* Metadata.swift */; };
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; }; E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E49A5482233E580800EED353 /* PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5481233E580800EED353 /* PromiseKit */; }; E49A5482233E580800EED353 /* PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5481233E580800EED353 /* PromiseKit */; };
E49A5485233E5ADC00EED353 /* Differ in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5484233E5ADC00EED353 /* Differ */; }; E49A5485233E5ADC00EED353 /* Differ in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5484233E5ADC00EED353 /* Differ */; };
@ -85,7 +82,6 @@
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; }; E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; }; E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; };
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* CoverArtService.swift */; };
E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; }; E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; };
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; }; E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; };
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; }; E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; };
@ -108,6 +104,7 @@
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; }; E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; }; E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; };
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; }; E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; };
E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DA820523D6236200C1EE58 /* NSSize.swift */; };
E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.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 */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; }; E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
@ -234,9 +231,6 @@
E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; }; E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; };
E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; }; E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; };
E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; }; E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; };
E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Caching.swift"; sourceTree = "<group>"; };
E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Remote.swift"; sourceTree = "<group>"; };
E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Filesystem.swift"; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; }; E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; }; E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; };
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; }; E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
@ -279,13 +273,11 @@
E489E39C22B9CF0000CA8CBD /* NSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = "<group>"; }; E489E39C22B9CF0000CA8CBD /* NSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = "<group>"; };
E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongView.swift; sourceTree = "<group>"; }; E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongView.swift; sourceTree = "<group>"; };
E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraggedSongView.xib; sourceTree = "<group>"; }; E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DraggedSongView.xib; sourceTree = "<group>"; };
E48E92D6235113DF00A5E1BB /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = "<group>"; };
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; }; E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongListView.swift; sourceTree = "<group>"; }; E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongListView.swift; sourceTree = "<group>"; };
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; }; 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>"; }; 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>"; }; E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E4A83BF3222207D50098FED6 /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = "<group>"; };
E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.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>"; }; 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>"; }; E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
@ -306,6 +298,7 @@
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; }; E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; }; E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; };
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = "<group>"; }; E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = "<group>"; };
E4DA820523D6236200C1EE58 /* NSSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSize.swift; sourceTree = "<group>"; };
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.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>"; }; 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>"; }; E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
@ -332,6 +325,7 @@
E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */, E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */,
E49A5485233E5ADC00EED353 /* Differ in Frameworks */, E49A5485233E5ADC00EED353 /* Differ in Frameworks */,
E4677C48233E60E70041474F /* MediaKeyTap in Frameworks */, E4677C48233E60E70041474F /* MediaKeyTap in Frameworks */,
E43BECA0238835DC00CAF1EB /* Kingfisher in Frameworks */,
E49A548E233E5B6000EED353 /* SwiftyJSON in Frameworks */, E49A548E233E5B6000EED353 /* SwiftyJSON in Frameworks */,
E4E96D13233E630800AFD36F /* PMKFoundation in Frameworks */, E4E96D13233E630800AFD36F /* PMKFoundation in Frameworks */,
E49A5482233E580800EED353 /* PromiseKit in Frameworks */, E49A5482233E580800EED353 /* PromiseKit in Frameworks */,
@ -432,6 +426,7 @@
E489E39822B85D0400CA8CBD /* NSPasteboard.swift */, E489E39822B85D0400CA8CBD /* NSPasteboard.swift */,
E489E39C22B9CF0000CA8CBD /* NSView.swift */, E489E39C22B9CF0000CA8CBD /* NSView.swift */,
E43AC1F022C68E6A001E483C /* NSPasteboardItem.swift */, E43AC1F022C68E6A001E483C /* NSPasteboardItem.swift */,
E4DA820523D6236200C1EE58 /* NSSize.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -448,9 +443,9 @@
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */, E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
E41E5300223BF99300173814 /* MPDClient+Queue.swift */, E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */, E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
E41E5306223C019100173814 /* MPDClient+Status.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */,
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */, E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -514,16 +509,6 @@
path = mpd; path = mpd;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E41E530C223EF4BA00173814 /* Extensions */ = {
isa = PBXGroup;
children = (
E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */,
E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */,
E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
E442CCC42347D5B900004E0C /* Components */ = { E442CCC42347D5B900004E0C /* Components */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -642,8 +627,6 @@
E4A83BF2222207BE0098FED6 /* Services */ = { E4A83BF2222207BE0098FED6 /* Services */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41E530C223EF4BA00173814 /* Extensions */,
E4A83BF3222207D50098FED6 /* CoverArtService.swift */,
E439109722640213002982E9 /* SongNotifierService.swift */, E439109722640213002982E9 /* SongNotifierService.swift */,
); );
path = Services; path = Services;
@ -736,7 +719,6 @@
E419E2862249B96600216A8C /* Song.swift */, E419E2862249B96600216A8C /* Song.swift */,
E47E2FDC2220A6D100F747E6 /* Time.swift */, E47E2FDC2220A6D100F747E6 /* Time.swift */,
E4B11B72226A6C770075461B /* TrackTimer.swift */, E4B11B72226A6C770075461B /* TrackTimer.swift */,
E48E92D6235113DF00A5E1BB /* Metadata.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -768,6 +750,7 @@
E49A548D233E5B6000EED353 /* SwiftyJSON */, E49A548D233E5B6000EED353 /* SwiftyJSON */,
E4677C47233E60E70041474F /* MediaKeyTap */, E4677C47233E60E70041474F /* MediaKeyTap */,
E4E96D12233E630800AFD36F /* PMKFoundation */, E4E96D12233E630800AFD36F /* PMKFoundation */,
E43BEC9F238835DC00CAF1EB /* Kingfisher */,
); );
productName = Persephone; productName = Persephone;
productReference = E40786182110CE6E006887B1 /* Persephone.app */; productReference = E40786182110CE6E006887B1 /* Persephone.app */;
@ -860,6 +843,7 @@
E49A548C233E5B6000EED353 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, E49A548C233E5B6000EED353 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
E4A9BEF9233E5F9000457785 /* XCRemoteSwiftPackageReference "MediaKeyTap" */, E4A9BEF9233E5F9000457785 /* XCRemoteSwiftPackageReference "MediaKeyTap" */,
E4E96D11233E630800AFD36F /* XCRemoteSwiftPackageReference "Foundation" */, E4E96D11233E630800AFD36F /* XCRemoteSwiftPackageReference "Foundation" */,
E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */,
); );
productRefGroup = E40786192110CE6E006887B1 /* Products */; productRefGroup = E40786192110CE6E006887B1 /* Products */;
projectDirPath = ""; projectDirPath = "";
@ -948,9 +932,7 @@
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */, E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */, E4B11B63226A4C510075461B /* AppReducer.swift in Sources */,
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */, E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */,
E442CCCD2347E73C00004E0C /* Artist.swift in Sources */, E442CCCD2347E73C00004E0C /* Artist.swift in Sources */,
E48E92D7235113DF00A5E1BB /* Metadata.swift in Sources */,
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */, E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */, E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */, E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */,
@ -961,6 +943,7 @@
E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */, E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */,
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */, E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */,
E450AD7E222620A10091BED3 /* Album.swift in Sources */, E450AD7E222620A10091BED3 /* Album.swift in Sources */,
E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */,
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
E43AC1F822C7065A001E483C /* AlbumCoverButton.swift in Sources */, E43AC1F822C7065A001E483C /* AlbumCoverButton.swift in Sources */,
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */, E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */,
@ -1005,18 +988,15 @@
E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */, E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */, E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */, E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */,
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */, E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
E4B11B66226A4F830075461B /* PlayerState.swift in Sources */, E4B11B66226A4F830075461B /* PlayerState.swift in Sources */,
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */, E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */,
E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */, E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */,
E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */, E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */,
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */,
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */, E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */,
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */, E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */,
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */,
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */, E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */, E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */, E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
@ -1193,10 +1173,10 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BDEE7ZBFZ3; DEVELOPMENT_TEAM = 8E7TQ638ZD;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Persephone/Info.plist; INFOPLIST_FILE = Persephone/Info.plist;
@ -1208,6 +1188,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/libmpdclient/output", "$(PROJECT_DIR)/libmpdclient/output",
); );
MARKETING_VERSION = "0.14.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1221,10 +1202,10 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = BDEE7ZBFZ3; DEVELOPMENT_TEAM = 8E7TQ638ZD;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = Persephone/Info.plist; INFOPLIST_FILE = Persephone/Info.plist;
@ -1236,6 +1217,7 @@
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/libmpdclient/output", "$(PROJECT_DIR)/libmpdclient/output",
); );
MARKETING_VERSION = "0.14.0-alpha";
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@ -1389,6 +1371,14 @@
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */ /* Begin XCRemoteSwiftPackageReference section */
E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.10.1;
};
};
E49A5480233E580800EED353 /* XCRemoteSwiftPackageReference "PromiseKit" */ = { E49A5480233E580800EED353 /* XCRemoteSwiftPackageReference "PromiseKit" */ = {
isa = XCRemoteSwiftPackageReference; isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mxcl/PromiseKit"; repositoryURL = "https://github.com/mxcl/PromiseKit";
@ -1448,6 +1438,11 @@
/* End XCRemoteSwiftPackageReference section */ /* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */ /* Begin XCSwiftPackageProductDependency section */
E43BEC9F238835DC00CAF1EB /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
E4677C47233E60E70041474F /* MediaKeyTap */ = { E4677C47233E60E70041474F /* MediaKeyTap */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = E4A9BEF9233E5F9000457785 /* XCRemoteSwiftPackageReference "MediaKeyTap" */; package = E4A9BEF9233E5F9000457785 /* XCRemoteSwiftPackageReference "MediaKeyTap" */;

View File

@ -28,6 +28,15 @@
"version": "3.3.3" "version": "3.3.3"
} }
}, },
{
"package": "Kingfisher",
"repositoryURL": "https://github.com/onevcat/Kingfisher.git",
"state": {
"branch": null,
"revision": "8ef6ca8b1b767ac2400762ed2f2bf75ddea3de5b",
"version": "5.10.1"
}
},
{ {
"package": "MediaKeyTap", "package": "MediaKeyTap",
"repositoryURL": "https://github.com/danbee/MediaKeyTap", "repositoryURL": "https://github.com/danbee/MediaKeyTap",

View File

@ -23,9 +23,6 @@ class AppDelegate: NSObject,
@IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem! @IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem!
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
connectToMPDServer()
instantiateControllers()
mediaKeyTap = MediaKeyTap(delegate: self) mediaKeyTap = MediaKeyTap(delegate: self)
mediaKeyTap?.start() mediaKeyTap?.start()
@ -34,23 +31,10 @@ class AppDelegate: NSObject,
$0.uiState $0.uiState
} }
} }
}
func connectToMPDServer() {
let mpdServer = App.store.state.preferencesState.mpdServer
App.mpdClient = MPDClient(
host: mpdServer.hostOrDefault,
port: mpdServer.portOrDefault,
withDelegate: App.mpdServerDelegate
)
App.mpdClient.connect()
}
func instantiateControllers() {
_ = App.mpdServerController
_ = App.userNotificationsController _ = App.userNotificationsController
App.mpdServerController.connect()
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {

View File

@ -23,34 +23,6 @@ class AlbumDataSource: NSObject, NSCollectionViewDataSource {
albumViewItem.view.wantsLayer = true albumViewItem.view.wantsLayer = true
albumViewItem.setAlbum(albums[indexPath.item]) albumViewItem.setAlbum(albums[indexPath.item])
switch albums[indexPath.item].coverArt {
case .notLoaded:
App.mpdClient.getAlbumFirstSong(for: albums[indexPath.item].mpdAlbum) { mpdSong in
guard let mpdSong = mpdSong else { return }
DispatchQueue.main.async {
App.store.dispatch(
UpdateAlbumMetaData(
metadata: Metadata(date: mpdSong.date),
albumIndex: indexPath.item
)
)
}
CoverArtService(song: Song(mpdSong: mpdSong))
.fetchCoverArt()
.done { image in
DispatchQueue.main.async {
App.store.dispatch(
UpdateCoverArtAction(coverArt: image, albumIndex: indexPath.item)
)
}
}
}
default:
break
}
return albumViewItem return albumViewItem
} }
} }

View File

@ -7,6 +7,7 @@
// //
import AppKit import AppKit
import Kingfisher
class AlbumViewItem: NSCollectionViewItem { class AlbumViewItem: NSCollectionViewItem {
var observer: NSKeyValueObservation? var observer: NSKeyValueObservation?
@ -43,6 +44,7 @@ class AlbumViewItem: NSCollectionViewItem {
override func prepareForReuse() { override func prepareForReuse() {
super.prepareForReuse() super.prepareForReuse()
albumCoverView.image = .defaultCoverArt
AlbumDetailView.popover.close() AlbumDetailView.popover.close()
} }
@ -50,13 +52,22 @@ class AlbumViewItem: NSCollectionViewItem {
self.album = album self.album = album
albumTitle.stringValue = album.title albumTitle.stringValue = album.title
albumArtist.stringValue = album.artist albumArtist.stringValue = album.artist
setAlbumCover(album)
switch album.coverArt {
case .loaded(let coverArt):
albumCoverView.image = coverArt ?? .defaultCoverArt
default:
albumCoverView.image = .defaultCoverArt
} }
func setAlbumCover(_ album: Album) {
guard let imagePath = album.coverArtFilePath else { return }
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
albumCoverView.kf.setImage(
with: .provider(provider),
placeholder: NSImage.defaultCoverArt,
options: [
.processor(DownsamplingImageProcessor(size: .albumListCoverSize)),
.scaleFactor(2),
]
)
} }
func setAppearance(selected isSelected: Bool) { func setAppearance(selected isSelected: Bool) {

View File

@ -7,6 +7,7 @@
// //
import AppKit import AppKit
import Kingfisher
class AlbumDetailView: NSViewController { class AlbumDetailView: NSViewController {
var observer: NSKeyValueObservation? var observer: NSKeyValueObservation?
@ -49,18 +50,11 @@ class AlbumDetailView: NSViewController {
getAlbumSongs(for: album) getAlbumSongs(for: album)
let date = album.metadata?.date ?? "" let date = album.mpdAlbum.date ?? ""
albumTitle.stringValue = album.title albumTitle.stringValue = album.title
albumMetadata.stringValue = "\(album.artist) · \(date)" albumMetadata.stringValue = "\(album.artist) · \(date)"
switch album.coverArt {
case .loaded(let coverArt):
albumCoverView.image = coverArt ?? .defaultCoverArt
default:
albumCoverView.image = .defaultCoverArt
}
super.viewWillAppear() super.viewWillAppear()
} }
@ -86,6 +80,7 @@ class AlbumDetailView: NSViewController {
let queueLength = App.store.state.queueState.queue.count let queueLength = App.store.state.queueState.queue.count
App.mpdClient.appendSong(song.mpdSong) App.mpdClient.appendSong(song.mpdSong)
App.mpdClient.fetchQueue()
App.mpdClient.playTrack(at: queueLength) App.mpdClient.playTrack(at: queueLength)
} }
@ -95,6 +90,7 @@ class AlbumDetailView: NSViewController {
let queueLength = App.store.state.queueState.queue.count let queueLength = App.store.state.queueState.queue.count
App.mpdClient.appendSong(song.mpdSong) App.mpdClient.appendSong(song.mpdSong)
App.mpdClient.fetchQueue()
App.mpdClient.playTrack(at: queueLength) App.mpdClient.playTrack(at: queueLength)
} }
@ -132,20 +128,23 @@ class AlbumDetailView: NSViewController {
self.dataSource.albumSongs[1].song self.dataSource.albumSongs[1].song
else { return } else { return }
self.getBigCoverArt(song: song) self.getBigCoverArt(song: song, album: album)
} }
} }
func getBigCoverArt(song: Song) { func getBigCoverArt(song: Song, album: Album) {
let coverArtService = CoverArtService(song: song) guard let imagePath = album.coverArtFilePath else { return }
coverArtService.fetchBigCoverArt() let imageURL = URL(fileURLWithPath: imagePath)
.done(on: DispatchQueue.main) { [weak self] image in let provider = LocalFileImageDataProvider(fileURL: imageURL)
if let image = image { albumCoverView.kf.setImage(
self?.albumCoverView.image = image with: .provider(provider),
} placeholder: NSImage.defaultCoverArt,
} options: [
.cauterize() .processor(DownsamplingImageProcessor(size: .albumDetailCoverSize)),
.scaleFactor(2),
]
)
} }
func setAppearance() { func setAppearance() {

View File

@ -8,25 +8,42 @@
import AppKit import AppKit
import ReSwift import ReSwift
import Kingfisher
class CurrentCoverArtView: NSImageView { class CurrentCoverArtView: NSImageView {
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
super.init(coder: coder) super.init(coder: coder)
App.store.subscribe(self) { App.store.subscribe(self) {
$0.select { $0.playerState.currentArtwork } $0.select { $0.playerState.currentSong }
} }
} }
func setAlbumImage(_ album: Album) {
guard let imagePath = album.coverArtFilePath else { return }
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
kf.setImage(
with: .provider(provider),
placeholder: NSImage.defaultCoverArt,
options: [
.processor(DownsamplingImageProcessor(size: .currentlyPlayingCoverSize)),
.scaleFactor(2),
]
)
}
} }
extension CurrentCoverArtView: StoreSubscriber { extension CurrentCoverArtView: StoreSubscriber {
typealias StoreSubscriberStateType = NSImage? typealias StoreSubscriberStateType = Song?
func newState(state: NSImage?) { func newState(state: Song?) {
if let coverArt = state { guard let song = state else {
image = coverArt
} else {
image = .defaultCoverArt image = .defaultCoverArt
return
} }
setAlbumImage(song.album)
} }
} }

View File

@ -0,0 +1,16 @@
//
// NSSize.swift
// Persephone
//
// Created by Daniel Barber on 1/20/20.
// Copyright © 2020 Dan Barber. All rights reserved.
//
import AppKit
extension NSSize {
static let albumListCoverSize = NSSize(width: 180, height: 180)
static let albumDetailCoverSize = NSSize(width: 500, height: 500)
static let currentlyPlayingCoverSize = albumDetailCoverSize
static let notificationCoverSize = albumListCoverSize
}

View File

@ -11,9 +11,24 @@ import ReSwift
class MPDServerController { class MPDServerController {
init() { init() {
// App.store.subscribe(self) { App.mpdClient = MPDClient(withDelegate: App.mpdServerDelegate)
// $0.select { $0.preferencesState.mpdServer }
// } App.store.subscribe(self) {
$0.select { $0.preferencesState.mpdServer }
}
}
func connect() {
let mpdServer = App.store.state.preferencesState.mpdServer
App.mpdClient.connect(
host: mpdServer.hostOrDefault,
port: mpdServer.portOrDefault
)
}
func disconnect() {
App.mpdClient.disconnect()
} }
} }
@ -21,7 +36,9 @@ extension MPDServerController: StoreSubscriber {
typealias StoreSubscriberStateType = MPDServer typealias StoreSubscriberStateType = MPDServer
func newState(state: MPDServer) { func newState(state: MPDServer) {
App.mpdClient.disconnect() guard App.mpdClient != nil else { return }
App.mpdClient.connect()
disconnect()
connect()
} }
} }

View File

@ -8,6 +8,7 @@
import Foundation import Foundation
import ReSwift import ReSwift
import Kingfisher
class UserNotificationsController { class UserNotificationsController {
init() { init() {
@ -18,18 +19,28 @@ class UserNotificationsController {
func notifyTrack(_ state: Song?) { func notifyTrack(_ state: Song?) {
guard let currentSong = state, guard let currentSong = state,
let coverArtFilePath = currentSong.album.coverArtFilePath,
let status = App.mpdClient.status, let status = App.mpdClient.status,
status.state == .playing status.state == .playing
else { return } else { return }
let coverArtService = CoverArtService(song: currentSong) let imageURL = URL(fileURLWithPath: coverArtFilePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
coverArtService.fetchBigCoverArt() _ = KingfisherManager.shared.retrieveImage(
.done() { with: .provider(provider),
SongNotifierService(song: currentSong, image: $0) options: [
.processor(DownsamplingImageProcessor(size: .notificationCoverSize)),
.scaleFactor(2),
]
) { result in
switch result {
case .success(let value):
SongNotifierService(song: currentSong, image: value.image)
.deliver() .deliver()
case .failure:
break
}
} }
.cauterize()
} }
} }

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -302,6 +302,21 @@
</connections> </connections>
</button> </button>
</toolbarItem> </toolbarItem>
<toolbarItem implicitItemIdentifier="7C15D391-03F5-42C7-AC6D-6E17549C698E" label="Search" paletteLabel="Search" sizingBehavior="auto" id="FRe-rR-Ulo">
<nil key="toolTip"/>
<searchField key="view" wantsLayer="YES" verticalHuggingPriority="750" textCompletion="NO" id="xfU-Xe-eno">
<rect key="frame" x="0.0" y="14" width="96" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="F3N-3P-tS3">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
<connections>
<action selector="handleSearchQuery:" target="B8D-0N-5wS" id="BPh-1O-bUU"/>
</connections>
</searchField>
</toolbarItem>
</allowedToolbarItems> </allowedToolbarItems>
<defaultToolbarItems> <defaultToolbarItems>
<toolbarItem reference="p3r-ty-Pxf"/> <toolbarItem reference="p3r-ty-Pxf"/>
@ -314,6 +329,7 @@
<toolbarItem reference="s1h-EC-nvL"/> <toolbarItem reference="s1h-EC-nvL"/>
<toolbarItem reference="5U7-UV-xn2"/> <toolbarItem reference="5U7-UV-xn2"/>
<toolbarItem reference="9ol-aR-mzv"/> <toolbarItem reference="9ol-aR-mzv"/>
<toolbarItem reference="FRe-rR-Ulo"/>
</defaultToolbarItems> </defaultToolbarItems>
</toolbar> </toolbar>
<connections> <connections>
@ -323,6 +339,7 @@
<connections> <connections>
<outlet property="databaseUpdatingIndicator" destination="LpV-iM-o6t" id="y0T-eR-ygY"/> <outlet property="databaseUpdatingIndicator" destination="LpV-iM-o6t" id="y0T-eR-ygY"/>
<outlet property="repeatState" destination="OqH-lV-sAg" id="DPC-Ff-Srr"/> <outlet property="repeatState" destination="OqH-lV-sAg" id="DPC-Ff-Srr"/>
<outlet property="searchQuery" destination="xfU-Xe-eno" id="d8u-sH-13V"/>
<outlet property="shuffleState" destination="E8L-uK-XT0" id="dCF-hm-dBs"/> <outlet property="shuffleState" destination="E8L-uK-XT0" id="dCF-hm-dBs"/>
<outlet property="trackProgress" destination="kx6-xm-TAN" id="XDv-Th-Agj"/> <outlet property="trackProgress" destination="kx6-xm-TAN" id="XDv-Th-Agj"/>
<outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/> <outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/>
@ -392,7 +409,7 @@
<tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5"> <tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5">
<rect key="frame" x="0.0" y="0.0" width="418" height="300"/> <rect key="frame" x="0.0" y="0.0" width="418" height="300"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<font key="font" metaFont="message"/> <font key="font" metaFont="system"/>
<connections> <connections>
<outlet property="delegate" destination="zhe-qh-Mal" id="LUL-qN-JlP"/> <outlet property="delegate" destination="zhe-qh-Mal" id="LUL-qN-JlP"/>
</connections> </connections>
@ -436,7 +453,7 @@
</textField> </textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pRL-MG-1Be"> <button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pRL-MG-1Be">
<rect key="frame" x="160" y="94" width="264" height="18"/> <rect key="frame" x="160" y="94" width="264" height="18"/>
<buttonCell key="cell" type="check" title="Fetch missing artwork from MusicBrainz" bezelStyle="regularSquare" imagePosition="left" inset="2" id="LpD-Ew-HMd"> <buttonCell key="cell" type="check" title="Fetch missing artwork from MusicBrainz" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="LpD-Ew-HMd">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
</buttonCell> </buttonCell>
@ -543,9 +560,6 @@
<constraint firstItem="ARv-cj-xlz" firstAttribute="leading" secondItem="BRY-0R-F3u" secondAttribute="leading" id="w2Z-xv-Fwz"/> <constraint firstItem="ARv-cj-xlz" firstAttribute="leading" secondItem="BRY-0R-F3u" secondAttribute="leading" id="w2Z-xv-Fwz"/>
</constraints> </constraints>
</view> </view>
<connections>
<outlet property="browseTabView" destination="ARv-cj-xlz" id="h93-fi-yY7"/>
</connections>
</viewController> </viewController>
</objects> </objects>
<point key="canvasLocation" x="1436" y="238"/> <point key="canvasLocation" x="1436" y="238"/>
@ -633,26 +647,26 @@
<objects> <objects>
<viewController id="KIP-rq-4dM" customClass="QueueViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <viewController id="KIP-rq-4dM" customClass="QueueViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<splitView key="view" dividerStyle="thin" id="84I-w3-Mxl"> <splitView key="view" dividerStyle="thin" id="84I-w3-Mxl">
<rect key="frame" x="0.0" y="0.0" width="328" height="548"/> <rect key="frame" x="0.0" y="0.0" width="328" height="547"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7"> <scrollView misplaced="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="17" horizontalPageScroll="10" verticalLineScroll="17" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7">
<rect key="frame" x="0.0" y="0.0" width="328" height="219"/> <rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<clipView key="contentView" drawsBackground="NO" id="WI8-Pw-03L"> <clipView key="contentView" drawsBackground="NO" id="WI8-Pw-03L">
<rect key="frame" x="0.0" y="0.0" width="328" height="219"/> <rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="14" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll" customClass="QueueView" customModule="Persephone" customModuleProvider="target"> <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="14" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll" customClass="QueueView" customModule="Persephone" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="328" height="219"/> <rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="0.0"/> <size key="intercellSpacing" width="3" height="0.0"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns> <tableColumns>
<tableColumn identifier="songTitleColumn" width="200" minWidth="128" maxWidth="1000" id="0Co-uF-CCB"> <tableColumn identifier="songTitleColumn" width="200" minWidth="128" maxWidth="1000" id="0Co-uF-CCB">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Title"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Title">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="message" size="11"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell> </tableHeaderCell>
@ -733,7 +747,7 @@
</tableColumn> </tableColumn>
<tableColumn identifier="songArtistColumn" width="122" minWidth="64" maxWidth="1000" id="SPM-QP-DX8"> <tableColumn identifier="songArtistColumn" width="122" minWidth="64" maxWidth="1000" id="SPM-QP-DX8">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Artist">
<font key="font" metaFont="smallSystem"/> <font key="font" metaFont="message" size="11"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</tableHeaderCell> </tableHeaderCell>
@ -791,8 +805,8 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
</scrollView> </scrollView>
<customView id="iUb-eV-Qws"> <customView misplaced="YES" id="iUb-eV-Qws">
<rect key="frame" x="0.0" y="220" width="328" height="328"/> <rect key="frame" x="0.0" y="220" width="328" height="327"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY" customClass="CurrentCoverArtView" customModule="Persephone" customModuleProvider="target"> <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY" customClass="CurrentCoverArtView" customModule="Persephone" customModuleProvider="target">

View File

@ -27,7 +27,7 @@ class WindowController: NSWindowController {
@IBOutlet var shuffleState: NSButton! @IBOutlet var shuffleState: NSButton!
@IBOutlet var repeatState: NSButton! @IBOutlet var repeatState: NSButton!
@IBOutlet var browseViewControls: NSSegmentedControl! @IBOutlet weak var searchQuery: NSSearchField!
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
@ -42,6 +42,8 @@ class WindowController: NSWindowController {
App.store.dispatch(MainWindowDidOpenAction()) App.store.dispatch(MainWindowDidOpenAction())
NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil)
trackProgress.font = .timerFont trackProgress.font = .timerFont
trackRemaining.font = .timerFont trackRemaining.font = .timerFont
} }
@ -49,7 +51,9 @@ class WindowController: NSWindowController {
override func keyDown(with event: NSEvent) { override func keyDown(with event: NSEvent) {
switch event.keyCode { switch event.keyCode {
case NSEvent.keyCodeSpace: case NSEvent.keyCodeSpace:
if !event.isARepeat {
App.mpdClient.playPause() App.mpdClient.playPause()
}
default: default:
nextResponder?.keyDown(with: event) nextResponder?.keyDown(with: event)
} }
@ -117,6 +121,13 @@ class WindowController: NSWindowController {
trackRemaining.stringValue = time.formattedTime trackRemaining.stringValue = time.formattedTime
} }
@objc func willDisconnect() {
DispatchQueue.main.async {
App.store.dispatch(SetSearchQuery(searchQuery: ""))
self.searchQuery.stringValue = ""
}
}
// TODO: Refactor this using a gesture recognizer // TODO: Refactor this using a gesture recognizer
@IBAction func changeTrackProgress(_ sender: NSSlider) { @IBAction func changeTrackProgress(_ sender: NSSlider) {
guard let event = NSApplication.shared.currentEvent guard let event = NSApplication.shared.currentEvent
@ -161,6 +172,10 @@ class WindowController: NSWindowController {
@IBAction func handleRepeatButton(_ sender: NSButton) { @IBAction func handleRepeatButton(_ sender: NSButton) {
App.mpdClient.setRepeatState(repeatState: sender.state == .on) App.mpdClient.setRepeatState(repeatState: sender.state == .on)
} }
@IBAction func handleSearchQuery(_ sender: NSSearchField) {
App.store.dispatch(SetSearchQuery(searchQuery: sender.stringValue))
}
} }
extension WindowController: NSWindowDelegate { extension WindowController: NSWindowDelegate {

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.13.0-alpha</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>

View File

@ -11,7 +11,11 @@ import mpdclient
extension MPDClient { extension MPDClient {
func fetchAllAlbums() { func fetchAllAlbums() {
enqueueCommand(command: .fetchAllAlbums) enqueueCommand(command: .fetchAlbums, userData: ["filter": ""])
}
func fetchAlbums(filter: String) {
enqueueCommand(command: .fetchAlbums, userData: ["filter": filter])
} }
func playAlbum(_ album: MPDAlbum) { func playAlbum(_ album: MPDAlbum) {
@ -41,6 +45,7 @@ extension MPDClient {
priority: .normal, priority: .normal,
userData: ["songs": songs] userData: ["songs": songs]
) )
self.enqueueCommand(command: .fetchQueue)
self.enqueueCommand( self.enqueueCommand(
command: .playTrack, command: .playTrack,
priority: .normal, priority: .normal,
@ -49,27 +54,32 @@ extension MPDClient {
} }
} }
func allAlbums() { func albums(filter: String) {
var albums: [MPDAlbum] = [] var albums: [MPDAlbum] = []
var artist: String = ""
mpd_search_db_tags(self.connection, MPD_TAG_ALBUM) mpd_search_db_songs(self.connection, false)
mpd_search_add_group_tag(self.connection, MPD_TAG_ALBUM_ARTIST) if filter != "" {
mpd_search_add_expression(
self.connection,
"(any contains '\(filter)')"
)
}
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_TRACK, "1")
mpd_search_commit(self.connection) mpd_search_commit(self.connection)
while let pair = mpd_recv_pair(self.connection) { while let song = mpd_recv_song(self.connection) {
let pair = MPDPair(pair) let mpdSong = MPDSong(song)
switch pair.name { let mpdAlbum = MPDAlbum(
case "AlbumArtist": title: mpdSong.album.title,
artist = pair.value artist: mpdSong.artist,
case "Album": date: mpdSong.date,
albums.append(MPDAlbum(title: pair.value, artist: artist)) path: mpdSong.path
default: )
break if (mpdAlbum != albums.last) {
albums.append(mpdAlbum)
} }
mpd_return_pair(self.connection, pair.pair)
} }
self.delegate?.didLoadAlbums(mpdClient: self, albums: albums) self.delegate?.didLoadAlbums(mpdClient: self, albums: albums)
@ -101,7 +111,7 @@ extension MPDClient {
func albumSongs(for album: MPDAlbum, callback: ([MPDSong]) -> Void) { func albumSongs(for album: MPDAlbum, callback: ([MPDSong]) -> Void) {
guard isConnected else { return } guard isConnected else { return }
let songs = searchSongs([MPDTag.album: album.title, MPDTag.artist: album.artist]) let songs = searchSongs([MPDTag.album: album.title, MPDTag.albumArtist: album.artist])
callback(songs) callback(songs)
} }

View File

@ -15,6 +15,14 @@ extension MPDClient {
) { ) {
switch command { switch command {
case .connect:
guard let host = userData["host"] as? String,
let port = userData["port"] as? Int
else { return }
createConnection(host: host, port: port)
case .disconnect:
freeConnection()
// Transport commands // Transport commands
case .prevTrack: case .prevTrack:
sendPreviousTrack() sendPreviousTrack()
@ -92,8 +100,9 @@ extension MPDClient {
allArtists() allArtists()
// Album commands // Album commands
case .fetchAllAlbums: case .fetchAlbums:
allAlbums() guard let filter = userData["filter"] as? String else { return }
albums(filter: filter)
case .playAlbum: case .playAlbum:
guard let album = userData["album"] as? MPDAlbum else { return } guard let album = userData["album"] as? MPDAlbum else { return }
sendPlayAlbum(album) sendPlayAlbum(album)

View File

@ -10,8 +10,7 @@ import Foundation
import mpdclient import mpdclient
extension MPDClient { extension MPDClient {
func makeConnectionOperation(host: String, port: Int) -> BlockOperation { func createConnection(host: String, port: Int) {
BlockOperation { [unowned self] in
guard let connection = mpd_connection_new(host, UInt32(port), 10000), guard let connection = mpd_connection_new(host, UInt32(port), 10000),
mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS
else { return } else { return }
@ -24,26 +23,31 @@ extension MPDClient {
self.connection = connection self.connection = connection
self.status = MPDStatus(status) self.status = MPDStatus(status)
self.delegate?.didConnect(mpdClient: self) delegate?.didConnect(mpdClient: self)
self.delegate?.didUpdateStatus(mpdClient: self, status: self.status!) delegate?.didUpdateStatus(mpdClient: self, status: self.status!)
self.idle()
}
} }
func connect() { func freeConnection() {
commandQueue.addOperation(connectionOperation)
}
func disconnect() {
guard isConnected else { return } guard isConnected else { return }
noIdle()
commandQueue.addOperation { [unowned self] in
self.delegate?.willDisconnect(mpdClient: self) self.delegate?.willDisconnect(mpdClient: self)
mpd_connection_free(self.connection) mpd_connection_free(self.connection)
self.isConnected = false self.isConnected = false
} }
func connect(host: String, port: Int) {
let commandOperation = BlockOperation() { [unowned self] in
self.sendCommand(command: .connect, userData: ["host": host, "port": port])
if self.isConnected {
self.idle()
}
}
commandQueue.addOperation(commandOperation)
}
func disconnect() {
enqueueCommand(command: .disconnect)
} }
} }

View File

@ -22,9 +22,8 @@ class MPDClient {
let commandQueue = OperationQueue() let commandQueue = OperationQueue()
init(host: String, port: Int, withDelegate delegate: MPDClientDelegate?) { init(withDelegate delegate: MPDClientDelegate?) {
commandQueue.maxConcurrentOperationCount = 1 commandQueue.maxConcurrentOperationCount = 1
self.delegate = delegate self.delegate = delegate
self.connectionOperation = makeConnectionOperation(host: host, port: port)
} }
} }

View File

@ -12,5 +12,13 @@ extension MPDClient {
struct MPDAlbum: Equatable { struct MPDAlbum: Equatable {
let title: String let title: String
let artist: String let artist: String
var date: String?
var path: String?
static func == (lhs: MPDAlbum, rhs: MPDAlbum) -> Bool {
return lhs.title == rhs.title &&
lhs.artist == rhs.artist &&
lhs.date == rhs.date
}
} }
} }

View File

@ -10,6 +10,9 @@ import Foundation
extension MPDClient { extension MPDClient {
enum MPDCommand { enum MPDCommand {
case connect
case disconnect
// Transport commands // Transport commands
case prevTrack case prevTrack
case nextTrack case nextTrack
@ -41,7 +44,7 @@ extension MPDClient {
case fetchAllArtists case fetchAllArtists
// Album commands // Album commands
case fetchAllAlbums case fetchAlbums
case playAlbum case playAlbum
case getAlbumFirstSong case getAlbumFirstSong
case getAlbumSongs case getAlbumSongs

View File

@ -36,7 +36,9 @@ extension MPDClient {
var album: MPDAlbum { var album: MPDAlbum {
return MPDAlbum( return MPDAlbum(
title: getTag(.album), title: getTag(.album),
artist: artist artist: artist,
date: date,
path: path
) )
} }
@ -52,6 +54,11 @@ extension MPDClient {
return getTag(.date) return getTag(.date)
} }
var path: String {
return NSString(string: uriString)
.deletingLastPathComponent
}
func getTag(_ tagType: MPDTag) -> String { func getTag(_ tagType: MPDTag) -> String {
guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0) guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0)
else { return "" } else { return "" }

View File

@ -11,8 +11,6 @@ import CryptoSwift
struct Album { struct Album {
var mpdAlbum: MPDClient.MPDAlbum var mpdAlbum: MPDClient.MPDAlbum
var coverArt: Loading<NSImage?> = .notLoaded
var metadata: Metadata?
init(mpdAlbum: MPDClient.MPDAlbum) { init(mpdAlbum: MPDClient.MPDAlbum) {
self.mpdAlbum = mpdAlbum self.mpdAlbum = mpdAlbum
@ -26,14 +24,41 @@ struct Album {
return mpdAlbum.artist return mpdAlbum.artist
} }
var date: String {
guard let date = mpdAlbum.date else { return "" }
return date
}
var hash: String { var hash: String {
return "\(title) - \(artist)".sha1() return "\(title) - \(artist)".sha1()
} }
var coverArtFilenames: [String] {
return [
"cover.jpg",
"folder.jpg",
"\(artist) - \(title).jpg",
"cover.png",
"folder.png",
"\(artist) - \(title ).png",
]
}
var coverArtFilePath: String? {
let musicDir = App.store.state.preferencesState.expandedMpdLibraryDir
guard let albumPath = mpdAlbum.path else { return nil }
return coverArtFilenames
.lazy
.map { "\(musicDir)/\(albumPath)/\($0)" }
.first {
FileManager.default.fileExists(atPath: $0)
}
}
} }
extension Album: Equatable { extension Album: Equatable {
static func == (lhs: Album, rhs: Album) -> Bool { static func == (lhs: Album, rhs: Album) -> Bool {
return (lhs.mpdAlbum == rhs.mpdAlbum) && return lhs.mpdAlbum == rhs.mpdAlbum
(lhs.coverArt ~= rhs.coverArt)
} }
} }

View File

@ -19,6 +19,6 @@ struct Artist {
extension Artist: Equatable { extension Artist: Equatable {
static func == (lhs: Artist, rhs: Artist) -> Bool { static func == (lhs: Artist, rhs: Artist) -> Bool {
return (lhs.name == rhs.name) return lhs.name == rhs.name
} }
} }

View File

@ -27,7 +27,7 @@ struct MPDServer {
extension MPDServer: Equatable { extension MPDServer: Equatable {
static func == (lhs: MPDServer, rhs: MPDServer) -> Bool { static func == (lhs: MPDServer, rhs: MPDServer) -> Bool {
return (lhs.host == rhs.host) && return lhs.host == rhs.host &&
(lhs.port == rhs.port) lhs.port == rhs.port
} }
} }

View File

@ -1,13 +0,0 @@
//
// Metadata.swift
// Persephone
//
// Created by Daniel Barber on 2019/10/11.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct Metadata {
var date: String?
}

View File

@ -1,70 +0,0 @@
//
// CoverArtService.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/23.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import PromiseKit
class CoverArtService {
let song: Song
let album: Album
let cachedArtworkSize = 180
let cachedArtworkQuality: CGFloat = 0.5
let bigArtworkSize = 600
var session = URLSession(configuration: .default)
let coverArtQueue = DispatchQueue(label: "coverArtQueue", qos: .utility)
init(song: Song) {
self.song = song
self.album = song.album
}
func fetchBigCoverArt() -> Promise<NSImage?> {
return firstly {
self.getArtworkFromFilesystem()
}.then { (image: NSImage?) -> Promise<NSImage?> in
image.map(Promise.value) ?? self.getRemoteArtwork()
}.recover { (_) -> Guarantee<NSImage?> in
return .value(nil)
}
}
func fetchCoverArt() -> Guarantee<NSImage?> {
return firstly {
self.getCachedArtwork()
}.then { (artwork: NSImage?) -> Promise<NSImage?> in
artwork.map(Promise.value) ?? self.getArtworkFromFilesystem()
}.then { (artwork: NSImage?) -> Promise<NSImage?> in
artwork.map(Promise.value) ?? self.getRemoteArtwork()
}.compactMap(on: coverArtQueue) {
return self.sizeAndCacheImage($0).map(Optional.some)
}.recover { _ in
return .value(nil)
}
}
func sizeAndCacheImage(_ image: NSImage?) -> NSImage? {
switch image {
case nil:
self.cacheArtwork(data: Data())
return image
case let image:
if self.isArtworkCached() {
return image
} else {
let sizedImage = image?.toFitBox(
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
)
self.cacheArtwork(data: sizedImage?.jpegData(compressionQuality: self.cachedArtworkQuality))
return sizedImage
}
}
}
}

View File

@ -1,51 +0,0 @@
//
// CoverArtService+Caching.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/17.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import PromiseKit
extension CoverArtService {
static let cacheDir = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(Bundle.main.bundleIdentifier!)
func getCachedArtwork() -> Promise<NSImage?> {
return Promise { seal in
coverArtQueue.async {
if self.isArtworkCached() {
let cacheFilePath = CoverArtService.cacheDir.appendingPathComponent(self.album.hash).path
let data = FileManager.default.contents(atPath: cacheFilePath)
let image = NSImage(data: data ?? Data()) ?? NSImage.defaultCoverArt
seal.fulfill(image)
} else {
seal.fulfill(nil)
}
}
}
}
func cacheArtwork(data: Data?) {
coverArtQueue.async {
guard let bundleIdentifier = Bundle.main.bundleIdentifier,
let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(bundleIdentifier)
else { return }
let cacheFilePath = cacheDir.appendingPathComponent(self.album.hash).path
if !self.isArtworkCached() {
FileManager.default.createFile(atPath: cacheFilePath, contents: data, attributes: nil)
}
}
}
func isArtworkCached() -> Bool {
let cacheFilePath = CoverArtService.cacheDir.appendingPathComponent(album.hash).path
return FileManager.default.fileExists(atPath: cacheFilePath)
}
}

View File

@ -1,77 +0,0 @@
//
// CoverArtService+Filesystem.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/17.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import PromiseKit
extension CoverArtService {
var coverArtFilenames: [String] {
return [
"folder.jpg",
"cover.jpg",
"\(album.artist) - \(album.title).jpg"
]
}
var musicDir: String {
return App.store.state.preferencesState.expandedMpdLibraryDir
}
func getArtworkFromFilesystem() -> Promise<NSImage?> {
return Promise { seal in
coverArtQueue.async {
guard let artworkPath = self.fileSystemArtworkFilePath()
else { seal.fulfill(nil); return }
let image = self.tryImage(artworkPath)
seal.fulfill(image)
}
}
}
func saveArtworkToFilesystem(data: Data?) {
let artworkFileName = coverArtFilenames.first!
if self.fileSystemArtworkFilePath() == nil {
FileManager.default.createFile(
atPath: "\(self.musicDir)/\(self.songPath)/\(artworkFileName)",
contents: data,
attributes: nil
)
}
}
func fileSystemArtworkFilePath() -> String? {
let musicDir = App.store.state.preferencesState.expandedMpdLibraryDir
return self.coverArtFilenames
.lazy
.map { "\(musicDir)/\(self.songPath)/\($0)" }
.first {
FileManager.default.fileExists(atPath: $0)
}
}
var songPath: String {
return song
.mpdSong
.uriString
.split(separator: "/")
.dropLast()
.joined(separator: "/")
}
func tryImage(_ filePath: String) -> NSImage? {
guard let data = FileManager.default.contents(atPath: filePath),
let image = NSImage(data: data)
else { return nil }
return image
}
}

View File

@ -1,62 +0,0 @@
//
// CoverArtService+Remote.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/17.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import SwiftyJSON
import PromiseKit
import PMKFoundation
extension CoverArtService {
enum RemoteArtworkError: Error {
case noArtworkAvailable
case notConfigured
}
func getRemoteArtwork() -> Promise<NSImage?> {
return Promise { seal in
if App.store.state.preferencesState .fetchMissingArtworkFromInternet {
coverArtQueue.async {
let coverArtWorkItem = DispatchWorkItem {
self.getArtworkFromMusicBrainz().map(Optional.some).pipe(to: seal.resolve)
}
CoverArtQueue.shared.addToQueue(workItem: coverArtWorkItem)
}
} else {
throw RemoteArtworkError.notConfigured
}
}
}
func getArtworkFromMusicBrainz() -> Promise<NSImage> {
var search = URLComponents(string: "https://musicbrainz.org/ws/2/release/")!
search.query = "query=artist:\(album.artist) AND release:\(album.title) AND country:US&limit=1&fmt=json"
return firstly {
URLSession.shared.dataTask(.promise, with: search.url!).validate()
}.compactMap {
JSON($0.data)
}.compactMap {
$0["releases"][0]["id"].string
}.compactMap {
URLComponents(string: "https://coverartarchive.org/release/\($0)/front-500")?.url
}.then { (url: URL?) -> Promise<(data: Data, response: URLResponse)> in
return URLSession.shared.dataTask(.promise, with: url!).validate()
}.compactMap {
NSImage(data: $0.data)?.toFitBox(
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
)
}.recover { error -> Promise<NSImage> in
if case PMKHTTPError.badStatusCode(404, _, _) = error {
throw RemoteArtworkError.noArtworkAvailable
} else {
throw error
}
}
}
}

View File

@ -9,18 +9,6 @@
import AppKit import AppKit
import ReSwift import ReSwift
struct ResetAlbumListCoverArtAction: Action {}
struct UpdateCoverArtAction: Action {
var coverArt: NSImage?
var albumIndex: Int
}
struct UpdateAlbumMetaData: Action {
var metadata: Metadata
var albumIndex: Int
}
struct UpdateAlbumListAction: Action { struct UpdateAlbumListAction: Action {
var albums: [MPDClient.MPDAlbum] var albums: [MPDClient.MPDAlbum]
} }

View File

@ -9,10 +9,6 @@
import AppKit import AppKit
import ReSwift import ReSwift
struct UpdateCurrentCoverArtAction: Action {
var coverArt: NSImage?
}
struct UpdateCurrentSongAction: Action { struct UpdateCurrentSongAction: Action {
var currentSong: Song? var currentSong: Song?
} }

View File

@ -25,3 +25,7 @@ struct SetSelectedQueueItem: Action {
struct SetSelectedSong: Action { struct SetSelectedSong: Action {
let selectedSong: Song? let selectedSong: Song?
} }
struct SetSearchQuery: Action {
let searchQuery: String
}

View File

@ -12,7 +12,6 @@ import ReSwift
struct PlayerState: StateType { struct PlayerState: StateType {
var status: MPDClient.MPDStatus? var status: MPDClient.MPDStatus?
var currentSong: Song? var currentSong: Song?
var currentArtwork: NSImage?
var state: MPDClient.MPDStatus.State? var state: MPDClient.MPDStatus.State?
var shuffleState: Bool = false var shuffleState: Bool = false

View File

@ -15,26 +15,6 @@ func albumListReducer(action: Action, state: AlbumListState?) -> AlbumListState
case let action as UpdateAlbumListAction: case let action as UpdateAlbumListAction:
state.albums = action.albums.map { Album(mpdAlbum: $0) } state.albums = action.albums.map { Album(mpdAlbum: $0) }
case let action as UpdateCoverArtAction:
state.albums[action.albumIndex].coverArt = .loaded(action.coverArt)
case let action as UpdateAlbumMetaData:
state.albums[action.albumIndex].metadata = action.metadata
case is ResetAlbumListCoverArtAction:
state.albums = state.albums.map {
var album = $0
switch album.coverArt {
case .loaded(let coverArt):
if coverArt == nil {
album.coverArt = .notLoaded
}
default:
album.coverArt = .notLoaded
}
return album
}
default: default:
break break

View File

@ -39,29 +39,6 @@ func playerReducer(action: Action, state: PlayerState?) -> PlayerState {
case let action as UpdateCurrentSongAction: case let action as UpdateCurrentSongAction:
state.currentSong = action.currentSong state.currentSong = action.currentSong
if let currentSong = state.currentSong {
let coverArtService = CoverArtService(song: currentSong)
coverArtService.fetchBigCoverArt()
.done() { image in
DispatchQueue.main.async {
if let image = image {
App.store.dispatch(UpdateCurrentCoverArtAction(coverArt: image))
} else {
App.store.dispatch(UpdateCurrentCoverArtAction(coverArt: .defaultCoverArt))
}
}
}
.cauterize()
} else {
DispatchQueue.main.async {
App.store.dispatch(UpdateCurrentCoverArtAction(coverArt: .defaultCoverArt))
}
}
case let action as UpdateCurrentCoverArtAction:
state.currentArtwork = action.coverArt
case let action as UpdateElapsedTimeAction: case let action as UpdateElapsedTimeAction:
state.elapsedTimeMs = action.elapsedTimeMs state.elapsedTimeMs = action.elapsedTimeMs

View File

@ -36,7 +36,7 @@ func queueReducer(action: Action, state: QueueState?) -> QueueState {
if oldSongRowPos >= 0 { if oldSongRowPos >= 0 {
state.queue[oldSongRowPos].isPlaying = false state.queue[oldSongRowPos].isPlaying = false
} }
if newSongRowPos >= 0 { if newSongRowPos >= 0 && state.queue.count > newSongRowPos {
state.queue[newSongRowPos].isPlaying = true state.queue[newSongRowPos].isPlaying = true
DispatchQueue.main.async { DispatchQueue.main.async {

View File

@ -6,6 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import AppKit
import ReSwift import ReSwift
func uiReducer(action: Action, state: UIState?) -> UIState { func uiReducer(action: Action, state: UIState?) -> UIState {
@ -33,6 +34,12 @@ func uiReducer(action: Action, state: UIState?) -> UIState {
case let action as SetSelectedQueueItem: case let action as SetSelectedQueueItem:
state.selectedQueueItem = action.selectedQueueItem state.selectedQueueItem = action.selectedQueueItem
case let action as SetSearchQuery:
state.searchQuery = action.searchQuery
DispatchQueue.main.async {
App.mpdClient.fetchAlbums(filter: state.searchQuery)
}
default: default:
break break
} }

View File

@ -22,4 +22,6 @@ struct UIState: StateType {
var selectedSong: Song? var selectedSong: Song?
var selectedQueueItem: QueueItem? var selectedQueueItem: QueueItem?
var searchQuery: String = ""
} }