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

Compare commits

..

18 Commits

Author SHA1 Message Date
caf7f990e0
Utility QoS is better for album art 2019-04-01 20:11:19 -04:00
d087200fb3
Update changelog 2019-04-01 19:50:21 -04:00
5a8bc4a836
Some songs don't have an AlbumArtist listed 2019-04-01 19:46:56 -04:00
fc99c59d53
Stop album view jumping occasionally 2019-04-01 19:45:51 -04:00
0fb1831ffd
Update screenshot 2019-03-31 18:08:11 -04:00
87dafef15a
Update version number 2019-03-31 17:47:27 -04:00
f1c9f25296
Remove saving to filesystem for now 2019-03-31 17:47:16 -04:00
50e58f7cac
Use CrytoSwift instead of string extension 2019-03-31 17:33:41 -04:00
f014c4f929
Ensure artwork is fetched when the pref is changed 2019-03-31 17:33:10 -04:00
01371db6cb
Add config options for saving artwork to filesystem 2019-03-29 17:18:36 -04:00
025830b950
Only get artwork from the internet if the pref is set 2019-03-29 17:18:20 -04:00
29ab98b28c
Save fetched remote artwork to album directory 2019-03-29 16:51:42 -04:00
6bec0c170d
Big album art display works!
Still to be done: if an image does not exist on the filesystem it will
keep fetching it remotely. We probably shouldn't do this.
2019-03-29 15:28:46 -04:00
812af07c1a
Move to Swift 5 2019-03-27 09:08:02 -04:00
4d99aeabc6
Upgrade dependencies 2019-03-27 09:06:49 -04:00
da5bafd7fd
Compiler couldn't infer type here 2019-03-27 09:06:35 -04:00
2fb0ceeaee
Finish AlbumArtService refactor 2019-03-25 22:39:40 -04:00
27c0e32e48
WIP: Refactor album art service
We need to make it more flexible and less coupled
2019-03-25 22:23:07 -04:00
40 changed files with 655 additions and 409 deletions

View File

@ -1,5 +1,17 @@
# Changelog # Changelog
## 0.11.0-alpha - 2019-04-01
### Added
- Display the artwork of the currently playing track underneath the queue
### Changed
- Fix a bug that caused the album view to jump about occasionally
- Fix a bug that caused the incorrect artwork to be displayed for albums that
have the same name as another album
## 0.10.3a - 2019-03-25 ## 0.10.3a - 2019-03-25
### Changed ### Changed

View File

@ -1,3 +1,4 @@
github "SwiftyJSON/SwiftyJSON" ~> 4.0 github "SwiftyJSON/SwiftyJSON" ~> 4.0
github "PromiseKit/Foundation" ~> 3.0 github "PromiseKit/Foundation" ~> 3.0
github "nhurden/MediaKeyTap" "fix-tis-tsm-error" github "nhurden/MediaKeyTap"
github "krzyzanowskim/CryptoSwift"

View File

@ -1,4 +1,5 @@
github "PromiseKit/Foundation" "3.3.1" github "PromiseKit/Foundation" "3.3.2"
github "SwiftyJSON/SwiftyJSON" "4.2.0" github "SwiftyJSON/SwiftyJSON" "4.2.0"
github "mxcl/PromiseKit" "6.8.3" github "krzyzanowskim/CryptoSwift" "1.0.0"
github "nhurden/MediaKeyTap" "355d346c56243e6d56487fa46fcad945251e16ae" github "mxcl/PromiseKit" "6.8.4"
github "nhurden/MediaKeyTap" "2.2.1"

View File

@ -19,6 +19,7 @@
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; }; E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; };
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40F41F2221EDE27004B6CB8 /* Preferences.swift */; }; E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40F41F2221EDE27004B6CB8 /* Preferences.swift */; };
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; }; E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; };
E419E2872249B96600216A8C /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E419E2862249B96600216A8C /* Song.swift */; };
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; };
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; }; E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
@ -41,7 +42,7 @@
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; }; E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; }; E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; };
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; }; E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; };
E450AD7E222620A10091BED3 /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* AlbumItem.swift */; }; E450AD7E222620A10091BED3 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* Album.swift */; };
E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; }; E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; };
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */; }; E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */; };
@ -50,11 +51,17 @@
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */; }; E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */; };
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; }; E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; };
E450AD9D2229B9050091BED3 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9C2229B9050091BED3 /* String.swift */; };
E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */; }; E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */; };
E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; }; E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; };
E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E45962C62241A78500FC1A1E /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* Command.swift */; }; E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; };
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; };
E45E4FDB22515D87004B537F /* Brewfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD822515D87004B537F /* Brewfile */; };
E45E4FDC22515D87004B537F /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD922515D87004B537F /* Cartfile */; };
E45E4FDF225168DA004B537F /* CryptoSwift.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */; };
E45E4FE0225168DA004B537F /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; };
E45E4FE122516953004B537F /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; };
E45E4FE222516953004B537F /* CryptoSwift.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; }; E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; };
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */; }; E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */; };
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; }; E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; };
@ -64,20 +71,20 @@
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FDC2220A6D100F747E6 /* Time.swift */; }; E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FDC2220A6D100F747E6 /* Time.swift */; };
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; }; E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; };
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; }; E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; }; E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */; }; E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */; };
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* AlbumArtService.swift */; }; E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* AlbumArtService.swift */; };
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; }; E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
E4C8B53E22349002009A20F3 /* Idle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* Idle.swift */; }; E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.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 */; };
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* NotificationsController.swift */; }; E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* NotificationsController.swift */; };
E4E8CC9A22075D370024217A /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* Song.swift */; }; E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* MPDSong.swift */; };
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* Pair.swift */; }; E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* MPDPair.swift */; };
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* Album.swift */; }; E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* MPDAlbum.swift */; };
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; }; E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; };
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* SongItem.swift */; }; E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* QueueItem.swift */; };
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; }; E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
@ -108,6 +115,7 @@
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */, E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */,
E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */, E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */,
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */, E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */,
E45E4FE222516953004B537F /* CryptoSwift.framework in Embed Libraries */,
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */, E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */,
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */, E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */,
); );
@ -145,6 +153,7 @@
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumViewItem.xib; sourceTree = "<group>"; }; E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumViewItem.xib; sourceTree = "<group>"; };
E40F41F2221EDE27004B6CB8 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; }; E40F41F2221EDE27004B6CB8 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; }; E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; };
E419E2862249B96600216A8C /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; };
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = "<group>"; }; E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = "<group>"; };
E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; }; E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = "<group>"; }; E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = "<group>"; };
@ -201,7 +210,7 @@
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; }; E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; }; E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; };
E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; }; E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
E450AD7D222620A10091BED3 /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; }; E450AD7D222620A10091BED3 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
E450AD8522262AE60091BED3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/Mac/SwiftyJSON.framework; sourceTree = "<group>"; }; E450AD8522262AE60091BED3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/Mac/SwiftyJSON.framework; sourceTree = "<group>"; };
E450AD8C22262C590091BED3 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = "<group>"; }; E450AD8C22262C590091BED3 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = "<group>"; };
E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = "<group>"; }; E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = "<group>"; };
@ -209,11 +218,15 @@
E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtQueue.swift; sourceTree = "<group>"; }; E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtQueue.swift; sourceTree = "<group>"; };
E450AD96222633920091BED3 /* Alamofire.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Alamofire.framework.dSYM; path = Carthage/Build/Mac/Alamofire.framework.dSYM; sourceTree = "<group>"; }; E450AD96222633920091BED3 /* Alamofire.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Alamofire.framework.dSYM; path = Carthage/Build/Mac/Alamofire.framework.dSYM; sourceTree = "<group>"; };
E450AD97222633920091BED3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = "<group>"; }; E450AD97222633920091BED3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = "<group>"; };
E450AD9C2229B9050091BED3 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; }; E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; };
E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PMKFoundation.framework.dSYM; path = Carthage/Build/Mac/PMKFoundation.framework.dSYM; sourceTree = "<group>"; }; E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PMKFoundation.framework.dSYM; path = Carthage/Build/Mac/PMKFoundation.framework.dSYM; sourceTree = "<group>"; };
E450ADA02229E7C90091BED3 /* PMKFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PMKFoundation.framework; path = Carthage/Build/Mac/PMKFoundation.framework; sourceTree = "<group>"; }; E450ADA02229E7C90091BED3 /* PMKFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PMKFoundation.framework; path = Carthage/Build/Mac/PMKFoundation.framework; sourceTree = "<group>"; };
E45962C52241A78500FC1A1E /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = "<group>"; }; E45962C52241A78500FC1A1E /* MPDCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDCommand.swift; sourceTree = "<group>"; };
E45E4FD722515D87004B537F /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; };
E45E4FD822515D87004B537F /* Brewfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Brewfile; sourceTree = SOURCE_ROOT; };
E45E4FD922515D87004B537F /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = SOURCE_ROOT; };
E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = CryptoSwift.framework.dSYM; path = Carthage/Build/Mac/CryptoSwift.framework.dSYM; sourceTree = "<group>"; };
E45E4FDE225168DA004B537F /* CryptoSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoSwift.framework; path = Carthage/Build/Mac/CryptoSwift.framework; sourceTree = "<group>"; };
E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; }; E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MediaKeyTap.framework.dSYM; path = Carthage/Build/Mac/MediaKeyTap.framework.dSYM; sourceTree = "<group>"; }; E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MediaKeyTap.framework.dSYM; path = Carthage/Build/Mac/MediaKeyTap.framework.dSYM; sourceTree = "<group>"; };
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; }; E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = "<group>"; };
@ -223,20 +236,20 @@
E47E2FDC2220A6D100F747E6 /* Time.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; }; E47E2FDC2220A6D100F747E6 /* Time.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = "<group>"; };
E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = "<group>"; }; E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.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>"; };
E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; }; E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtPrefsController.swift; sourceTree = "<group>"; }; E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtPrefsController.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 /* AlbumArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtService.swift; sourceTree = "<group>"; }; E4A83BF3222207D50098FED6 /* AlbumArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtService.swift; sourceTree = "<group>"; };
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 /* Idle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Idle.swift; sourceTree = "<group>"; }; E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.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>"; };
E4E8CC932206097F0024217A /* NotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; }; E4E8CC932206097F0024217A /* NotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; };
E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; }; E4E8CC9922075D370024217A /* MPDSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDSong.swift; sourceTree = "<group>"; };
E4EB2378220F10B8008C70C0 /* Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = "<group>"; }; E4EB2378220F10B8008C70C0 /* MPDPair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDPair.swift; sourceTree = "<group>"; };
E4EB237A220F7CF1008C70C0 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; }; E4EB237A220F7CF1008C70C0 /* MPDAlbum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDAlbum.swift; sourceTree = "<group>"; };
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; }; E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; };
E4F6B462221E125900ACF42A /* SongItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongItem.swift; sourceTree = "<group>"; }; E4F6B462221E125900ACF42A /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = "<group>"; };
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; }; E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -245,9 +258,11 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E45E4FE122516953004B537F /* CryptoSwift.framework in Frameworks */,
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */, E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */,
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */, E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */,
E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */, E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */,
E45E4FE0225168DA004B537F /* CryptoSwift.framework in Frameworks */,
E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */, E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */,
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */, E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */,
); );
@ -273,11 +288,14 @@
E407860F2110CE6E006887B1 = { E407860F2110CE6E006887B1 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41B22BE21FB6B3300D544F6 /* Frameworks */,
E407861A2110CE6E006887B1 /* Persephone */, E407861A2110CE6E006887B1 /* Persephone */,
E407862D2110CE70006887B1 /* PersephoneTests */, E407862D2110CE70006887B1 /* PersephoneTests */,
E40786382110CE70006887B1 /* PersephoneUITests */, E40786382110CE70006887B1 /* PersephoneUITests */,
E40786192110CE6E006887B1 /* Products */, E40786192110CE6E006887B1 /* Products */,
E41B22BE21FB6B3300D544F6 /* Frameworks */, E45E4FD822515D87004B537F /* Brewfile */,
E45E4FD922515D87004B537F /* Cartfile */,
E45E4FD722515D87004B537F /* CHANGELOG.md */,
E42A8F3922176D6400A13ED9 /* LICENSE.md */, E42A8F3922176D6400A13ED9 /* LICENSE.md */,
E42A8F3A22176D6400A13ED9 /* README.md */, E42A8F3A22176D6400A13ED9 /* README.md */,
); );
@ -296,23 +314,23 @@
E407861A2110CE6E006887B1 /* Persephone */ = { E407861A2110CE6E006887B1 /* Persephone */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */, E407861B2110CE6E006887B1 /* AppDelegate.swift */,
E450AD8922262B420091BED3 /* Operations */, E407861F2110CE70006887B1 /* Assets.xcassets */,
E4A83BF2222207BE0098FED6 /* Services */, E4D1B597220BA3A20026F233 /* Controllers */,
E4A83BEC2221F5DD0098FED6 /* Preferences */, E4F6B45E221E117600ACF42A /* DataSources */,
E408D3B7220DE8CC0006D9BE /* Extensions */,
E41B22C721FB966C00D544F6 /* include */,
E40786242110CE70006887B1 /* Info.plist */,
E47E2FE32220AA0700F747E6 /* Layouts */, E47E2FE32220AA0700F747E6 /* Layouts */,
E4F6B461221E124700ACF42A /* Models */, E4F6B461221E124700ACF42A /* Models */,
E4F6B45E221E117600ACF42A /* DataSources */,
E407861F2110CE70006887B1 /* Assets.xcassets */,
E408D3B7220DE8CC0006D9BE /* Extensions */,
E4D1B598220BA3C90026F233 /* Resources */,
E4D1B597220BA3A20026F233 /* Controllers */,
E408D3C3220E138B0006D9BE /* Views */,
E41B22C721FB966C00D544F6 /* include */,
E407861B2110CE6E006887B1 /* AppDelegate.swift */,
E40786242110CE70006887B1 /* Info.plist */,
E40786252110CE70006887B1 /* Persephone.entitlements */,
E4A642DB220912FA00067D21 /* MPDClient */, E4A642DB220912FA00067D21 /* MPDClient */,
E450AD8922262B420091BED3 /* Operations */,
E40786252110CE70006887B1 /* Persephone.entitlements */,
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */,
E4A83BEC2221F5DD0098FED6 /* Preferences */,
E4D1B598220BA3C90026F233 /* Resources */,
E4A83BF2222207BE0098FED6 /* Services */,
E408D3C3220E138B0006D9BE /* Views */,
); );
path = Persephone; path = Persephone;
sourceTree = "<group>"; sourceTree = "<group>";
@ -340,11 +358,10 @@
children = ( children = (
E4928E0A2218D62A001D4BEA /* CGColor.swift */, E4928E0A2218D62A001D4BEA /* CGColor.swift */,
E408D3B5220DD8970006D9BE /* Notification.swift */, E408D3B5220DD8970006D9BE /* Notification.swift */,
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */,
E40FE71A221B904300A4223F /* NSEvent.swift */, E40FE71A221B904300A4223F /* NSEvent.swift */,
E435E3E1221CD4E200184CFC /* NSFont.swift */, E435E3E1221CD4E200184CFC /* NSFont.swift */,
E435E3E3221CD75D00184CFC /* NSImage.swift */, E435E3E3221CD75D00184CFC /* NSImage.swift */,
E450AD9C2229B9050091BED3 /* String.swift */, E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -352,15 +369,15 @@
E408D3BC220E03D20006D9BE /* Extensions */ = { E408D3BC220E03D20006D9BE /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41E530A223C033700173814 /* MPDClient+Album.swift */,
E41E5308223C020400173814 /* MPDClient+Command.swift */,
E41E52FC223BF87300173814 /* MPDClient+Connection.swift */, E41E52FC223BF87300173814 /* MPDClient+Connection.swift */,
E42410B52241B956005ED6DF /* MPDClient+Database.swift */, E42410B52241B956005ED6DF /* MPDClient+Database.swift */,
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
E41E5304223BFB0700173814 /* MPDClient+Error.swift */, E41E5304223BFB0700173814 /* MPDClient+Error.swift */,
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
E41E5306223C019100173814 /* MPDClient+Status.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */,
E41E5308223C020400173814 /* MPDClient+Command.swift */, E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
E41E530A223C033700173814 /* MPDClient+Album.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */, E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
); );
path = Extensions; path = Extensions;
@ -378,6 +395,8 @@
E41B22BE21FB6B3300D544F6 /* Frameworks */ = { E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E45E4FDE225168DA004B537F /* CryptoSwift.framework */,
E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */,
E450ADA02229E7C90091BED3 /* PMKFoundation.framework */, E450ADA02229E7C90091BED3 /* PMKFoundation.framework */,
E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */, E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */,
E450AD97222633920091BED3 /* Alamofire.framework */, E450AD97222633920091BED3 /* Alamofire.framework */,
@ -448,8 +467,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */, E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */,
E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */,
E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */, E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */,
E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -473,10 +492,10 @@
E4A642DB220912FA00067D21 /* MPDClient */ = { E4A642DB220912FA00067D21 /* MPDClient */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41B22C521FB932700D544F6 /* MPDClient.swift */,
E408D3BC220E03D20006D9BE /* Extensions */, E408D3BC220E03D20006D9BE /* Extensions */,
E4D1B595220BA27C0026F233 /* Protocols */,
E4D1B594220BA2490026F233 /* Models */, E4D1B594220BA2490026F233 /* Models */,
E41B22C521FB932700D544F6 /* MPDClient.swift */,
E4D1B595220BA27C0026F233 /* Protocols */,
); );
path = MPDClient; path = MPDClient;
sourceTree = "<group>"; sourceTree = "<group>";
@ -492,8 +511,8 @@
E4A83BED2221F5E60098FED6 /* Controllers */ = { E4A83BED2221F5E60098FED6 /* Controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */,
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */, E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */,
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */,
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */, E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */,
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */, E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */,
); );
@ -503,8 +522,8 @@
E4A83BF2222207BE0098FED6 /* Services */ = { E4A83BF2222207BE0098FED6 /* Services */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41E530C223EF4BA00173814 /* Extensions */,
E4A83BF3222207D50098FED6 /* AlbumArtService.swift */, E4A83BF3222207D50098FED6 /* AlbumArtService.swift */,
E41E530C223EF4BA00173814 /* Extensions */,
); );
path = Services; path = Services;
sourceTree = "<group>"; sourceTree = "<group>";
@ -512,12 +531,12 @@
E4D1B594220BA2490026F233 /* Models */ = { E4D1B594220BA2490026F233 /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4E8CC9922075D370024217A /* Song.swift */, E4EB237A220F7CF1008C70C0 /* MPDAlbum.swift */,
E4A642D922090CBE00067D21 /* Status.swift */, E45962C52241A78500FC1A1E /* MPDCommand.swift */,
E4EB2378220F10B8008C70C0 /* Pair.swift */, E4C8B53D22349002009A20F3 /* MPDIdle.swift */,
E4EB237A220F7CF1008C70C0 /* Album.swift */, E4EB2378220F10B8008C70C0 /* MPDPair.swift */,
E4C8B53D22349002009A20F3 /* Idle.swift */, E4E8CC9922075D370024217A /* MPDSong.swift */,
E45962C52241A78500FC1A1E /* Command.swift */, E4A642D922090CBE00067D21 /* MPDStatus.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -533,12 +552,12 @@
E4D1B597220BA3A20026F233 /* Controllers */ = { E4D1B597220BA3A20026F233 /* Controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */, E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
E4E8CC932206097F0024217A /* NotificationsController.swift */, E4E8CC932206097F0024217A /* NotificationsController.swift */,
E4E8CC912204F4B80024217A /* QueueViewController.swift */, E4E8CC912204F4B80024217A /* QueueViewController.swift */,
E465049921E94DF500A70F4C /* WindowController.swift */, E465049921E94DF500A70F4C /* WindowController.swift */,
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
); );
path = Controllers; path = Controllers;
sourceTree = "<group>"; sourceTree = "<group>";
@ -555,8 +574,8 @@
E4F6B45E221E117600ACF42A /* DataSources */ = { E4F6B45E221E117600ACF42A /* DataSources */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */,
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */, E4F6B466221E233200ACF42A /* AlbumDataSource.swift */,
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */,
); );
path = DataSources; path = DataSources;
sourceTree = "<group>"; sourceTree = "<group>";
@ -564,10 +583,11 @@
E4F6B461221E124700ACF42A /* Models */ = { E4F6B461221E124700ACF42A /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E47E2FDC2220A6D100F747E6 /* Time.swift */, E450AD7D222620A10091BED3 /* Album.swift */,
E40F41F2221EDE27004B6CB8 /* Preferences.swift */, E40F41F2221EDE27004B6CB8 /* Preferences.swift */,
E4F6B462221E125900ACF42A /* SongItem.swift */, E4F6B462221E125900ACF42A /* QueueItem.swift */,
E450AD7D222620A10091BED3 /* AlbumItem.swift */, E419E2862249B96600216A8C /* Song.swift */,
E47E2FDC2220A6D100F747E6 /* Time.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -643,7 +663,7 @@
TargetAttributes = { TargetAttributes = {
E40786172110CE6E006887B1 = { E40786172110CE6E006887B1 = {
CreatedOnToolsVersion = 9.4.1; CreatedOnToolsVersion = 9.4.1;
LastSwiftMigration = 1010; LastSwiftMigration = 1020;
SystemCapabilities = { SystemCapabilities = {
com.apple.Sandbox = { com.apple.Sandbox = {
enabled = 0; enabled = 0;
@ -652,12 +672,12 @@
}; };
E40786292110CE70006887B1 = { E40786292110CE70006887B1 = {
CreatedOnToolsVersion = 9.4.1; CreatedOnToolsVersion = 9.4.1;
LastSwiftMigration = 1010; LastSwiftMigration = 1020;
TestTargetID = E40786172110CE6E006887B1; TestTargetID = E40786172110CE6E006887B1;
}; };
E40786342110CE70006887B1 = { E40786342110CE70006887B1 = {
CreatedOnToolsVersion = 9.4.1; CreatedOnToolsVersion = 9.4.1;
LastSwiftMigration = 1010; LastSwiftMigration = 1020;
TestTargetID = E40786172110CE6E006887B1; TestTargetID = E40786172110CE6E006887B1;
}; };
}; };
@ -687,10 +707,14 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E45E4FDB22515D87004B537F /* Brewfile in Resources */,
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */, E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */,
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */, E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */,
E45E4FDC22515D87004B537F /* Cartfile in Resources */,
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */, E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */,
E40786202110CE70006887B1 /* Assets.xcassets in Resources */, E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
E45E4FDF225168DA004B537F /* CryptoSwift.framework.dSYM in Resources */,
E42A8F3C22176D6400A13ED9 /* README.md in Resources */, E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */, E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */,
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */, E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */,
@ -746,36 +770,37 @@
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */, E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */, E40FE71B221B904300A4223F /* NSEvent.swift in Sources */,
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */, E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
E4C8B53E22349002009A20F3 /* Idle.swift in Sources */, E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */,
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */, E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */,
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */, E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */, E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
E41E5310223EF6CE00173814 /* AlbumArtService+Remote.swift in Sources */, E41E5310223EF6CE00173814 /* AlbumArtService+Remote.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 */,
E4A642DA22090CBE00067D21 /* Status.swift in Sources */, E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */,
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */, E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */, E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */,
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */, E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */,
E450AD7E222620A10091BED3 /* AlbumItem.swift in Sources */, E450AD7E222620A10091BED3 /* Album.swift in Sources */,
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */, E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */,
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
E45962C62241A78500FC1A1E /* Command.swift in Sources */, E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */,
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */, E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */,
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */, E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */,
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */, E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */,
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */, E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */, E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */, E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */,
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */, E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */,
E419E2872249B96600216A8C /* Song.swift in Sources */,
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */, E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */, E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */,
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */, E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */,
E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */, E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */,
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */, E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */,
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */, E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */,
E4E8CC9A22075D370024217A /* Song.swift in Sources */, E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */, E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */, E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */, E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */,
@ -785,8 +810,7 @@
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
E41E5312223EF74A00173814 /* AlbumArtService+Filesystem.swift in Sources */, E41E5312223EF74A00173814 /* AlbumArtService+Filesystem.swift in Sources */,
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */, E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */, E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
E450AD9D2229B9050091BED3 /* String.swift in Sources */,
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */, E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */, E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */,
E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */, E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */,
@ -977,7 +1001,7 @@
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 = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include;
USER_HEADER_SEARCH_PATHS = libmpdclient/output; USER_HEADER_SEARCH_PATHS = libmpdclient/output;
}; };
@ -1008,7 +1032,7 @@
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 = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include;
USER_HEADER_SEARCH_PATHS = libmpdclient/output; USER_HEADER_SEARCH_PATHS = libmpdclient/output;
}; };
@ -1036,7 +1060,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneTests; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Persephone.app/Contents/MacOS/Persephone"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Persephone.app/Contents/MacOS/Persephone";
USER_HEADER_SEARCH_PATHS = libmpdclient/output; USER_HEADER_SEARCH_PATHS = libmpdclient/output;
@ -1065,7 +1089,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneTests; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneTests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Persephone.app/Contents/MacOS/Persephone"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Persephone.app/Contents/MacOS/Persephone";
USER_HEADER_SEARCH_PATHS = libmpdclient/output; USER_HEADER_SEARCH_PATHS = libmpdclient/output;
@ -1089,7 +1113,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneUITests; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Persephone; TEST_TARGET_NAME = Persephone;
}; };
name = Debug; name = Debug;
@ -1111,7 +1135,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneUITests; PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.PersephoneUITests;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.2; SWIFT_VERSION = 5.0;
TEST_TARGET_NAME = Persephone; TEST_TARGET_NAME = Persephone;
}; };
name = Release; name = Release;

View File

@ -30,6 +30,7 @@
}, },
"properties" : { "properties" : {
"template-rendering-intent" : "original", "template-rendering-intent" : "original",
"preserves-vector-representation" : true "preserves-vector-representation" : true,
"auto-scaling" : "auto"
} }
} }

View File

@ -40,11 +40,16 @@ class AlbumViewController: NSViewController,
albumCollectionView.dataSource = dataSource albumCollectionView.dataSource = dataSource
preferences.addObserver(self, forKeyPath: "mpdLibraryDir") preferences.addObserver(self, forKeyPath: "mpdLibraryDir")
preferences.addObserver(self, forKeyPath: "fetchMissingArtworkFromInternet")
} }
override func viewWillLayout() { override func viewWillLayout() {
super.viewWillLayout() super.viewWillLayout()
if let layout = albumCollectionView.collectionViewLayout as? AlbumViewLayout {
layout.saveScrollPosition()
}
albumCollectionView.collectionViewLayout?.invalidateLayout() albumCollectionView.collectionViewLayout?.invalidateLayout()
} }
@ -66,16 +71,19 @@ class AlbumViewController: NSViewController,
switch keyPath { switch keyPath {
case "mpdLibraryDir": case "mpdLibraryDir":
albumCollectionView.reloadData() albumCollectionView.reloadData()
case "fetchMissingArtworkFromInternet":
dataSource.resetCoverArt()
albumCollectionView.reloadData()
default: default:
break break
} }
} }
@objc func updateAlbums(_ notification: Notification) { @objc func updateAlbums(_ notification: Notification) {
guard let albums = notification.userInfo?[Notification.albumsKey] as? [MPDClient.Album] guard let albums = notification.userInfo?[Notification.albumsKey] as? [MPDClient.MPDAlbum]
else { return } else { return }
dataSource.albums = albums.map { AlbumItem(album: $0, coverArt: nil) } dataSource.albums = albums.map { Album(mpdAlbum: $0) }
albumCollectionView.reloadData() albumCollectionView.reloadData()
} }

View File

@ -10,7 +10,7 @@ import Cocoa
class AlbumViewItem: NSCollectionViewItem { class AlbumViewItem: NSCollectionViewItem {
var observer: NSKeyValueObservation? var observer: NSKeyValueObservation?
var album: AlbumItem? var album: Album?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -27,7 +27,7 @@ class AlbumViewItem: NSCollectionViewItem {
} }
} }
func setAlbum(_ album: AlbumItem) { func setAlbum(_ album: Album) {
self.album = album self.album = album
albumTitle.stringValue = album.title albumTitle.stringValue = album.title
albumArtist.stringValue = album.artist albumArtist.stringValue = album.artist
@ -53,7 +53,7 @@ class AlbumViewItem: NSCollectionViewItem {
@IBAction func playAlbum(_ sender: Any) { @IBAction func playAlbum(_ sender: Any) {
guard let album = album else { return } guard let album = album else { return }
AppDelegate.mpdClient.playAlbum(album.album) AppDelegate.mpdClient.playAlbum(album.mpdAlbum)
} }
@IBOutlet var albumCoverView: NSImageView! @IBOutlet var albumCoverView: NSImageView!

View File

@ -19,7 +19,7 @@ class NotificationsController: MPDClientDelegate {
sendNotification(name: Notification.willDisconnect) sendNotification(name: Notification.willDisconnect)
} }
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) { func didUpdateState(mpdClient: MPDClient, state: MPDClient.MPDStatus.State) {
sendNotification( sendNotification(
name: Notification.stateChanged, name: Notification.stateChanged,
userInfo: [Notification.stateKey: state] userInfo: [Notification.stateKey: state]
@ -44,7 +44,7 @@ class NotificationsController: MPDClientDelegate {
sendNotification(name: Notification.databaseUpdateFinished) sendNotification(name: Notification.databaseUpdateFinished)
} }
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) { func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.MPDSong]) {
sendNotification( sendNotification(
name: Notification.queueChanged, name: Notification.queueChanged,
userInfo: [Notification.queueKey: queue] userInfo: [Notification.queueKey: queue]
@ -58,7 +58,7 @@ class NotificationsController: MPDClientDelegate {
) )
} }
func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album]) { func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.MPDAlbum]) {
sendNotification( sendNotification(
name: Notification.loadedAlbums, name: Notification.loadedAlbums,
userInfo: [Notification.albumsKey: albums] userInfo: [Notification.albumsKey: albums]

View File

@ -7,12 +7,14 @@
// //
import Cocoa import Cocoa
import PromiseKit
class QueueViewController: NSViewController, class QueueViewController: NSViewController,
NSOutlineViewDelegate { NSOutlineViewDelegate {
var dataSource = QueueDataSource() var dataSource = QueueDataSource()
@IBOutlet var queueView: NSOutlineView! @IBOutlet var queueView: NSOutlineView!
@IBOutlet var queueAlbumArtImage: NSImageView!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -41,14 +43,14 @@ class QueueViewController: NSViewController,
} }
@objc func stateChanged(_ notification: Notification) { @objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.MPDStatus.State
else { return } else { return }
dataSource.setQueueIcon(state) dataSource.setQueueIcon(state)
} }
@objc func queueChanged(_ notification: Notification) { @objc func queueChanged(_ notification: Notification) {
guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song] guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.MPDSong]
else { return } else { return }
dataSource.updateQueue(queue) dataSource.updateQueue(queue)
@ -60,8 +62,26 @@ class QueueViewController: NSViewController,
else { return } else { return }
dataSource.setQueuePos(queuePos) dataSource.setQueuePos(queuePos)
queueView.reloadData() queueView.reloadData()
updateAlbumArt()
}
func updateAlbumArt() {
if let playingQueueItem = dataSource.queue.first(where: { $0.isPlaying }) {
let albumArtService = AlbumArtService(song: playingQueueItem.song)
albumArtService.fetchBigAlbumArt()
.done() {
if let image = $0 {
self.queueAlbumArtImage.image = image
} else {
self.queueAlbumArtImage.image = NSImage.defaultCoverArt
}
}
.cauterize()
} else {
queueAlbumArtImage.image = NSImage.defaultCoverArt
}
} }
func outlineView( func outlineView(
@ -76,12 +96,12 @@ class QueueViewController: NSViewController,
} }
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
if let songItem = item as? SongItem { if let queueItem = item as? QueueItem {
switch tableColumn?.identifier.rawValue { switch tableColumn?.identifier.rawValue {
case "songTitleColumn": case "songTitleColumn":
return cellForSongTitle(outlineView, with: songItem) return cellForSongTitle(outlineView, with: queueItem)
case "songArtistColumn": case "songArtistColumn":
return cellForSongArtist(outlineView, with: songItem) return cellForSongArtist(outlineView, with: queueItem)
default: default:
return nil return nil
} }
@ -92,14 +112,14 @@ class QueueViewController: NSViewController,
} }
} }
func cellForSongTitle(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView { func cellForSongTitle(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
let cellView = outlineView.makeView( let cellView = outlineView.makeView(
withIdentifier: .queueSongTitle, withIdentifier: .queueSongTitle,
owner: self owner: self
) as! NSTableCellView ) as! NSTableCellView
cellView.textField?.stringValue = songItem.song.getTag(.title) cellView.textField?.stringValue = queueItem.song.title
if songItem.isPlaying { if queueItem.isPlaying {
cellView.textField?.font = .systemFontBold cellView.textField?.font = .systemFontBold
cellView.imageView?.image = dataSource.queueIcon cellView.imageView?.image = dataSource.queueIcon
} else { } else {
@ -110,14 +130,14 @@ class QueueViewController: NSViewController,
return cellView return cellView
} }
func cellForSongArtist(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView { func cellForSongArtist(_ outlineView: NSOutlineView, with queueItem: QueueItem) -> NSView {
let cellView = outlineView.makeView( let cellView = outlineView.makeView(
withIdentifier: .queueSongArtist, withIdentifier: .queueSongArtist,
owner: self owner: self
) as! NSTableCellView ) as! NSTableCellView
cellView.textField?.stringValue = songItem.song.getTag(.artist) cellView.textField?.stringValue = queueItem.song.artist
if songItem.isPlaying { if queueItem.isPlaying {
cellView.textField?.font = .systemFontBold cellView.textField?.font = .systemFontBold
} else { } else {
cellView.textField?.font = .systemFontRegular cellView.textField?.font = .systemFontRegular

View File

@ -13,7 +13,7 @@ class WindowController: NSWindowController {
case prevTrack, playPause, stop, nextTrack case prevTrack, playPause, stop, nextTrack
} }
var state: MPDClient.Status.State? var state: MPDClient.MPDStatus.State?
var totalTime: UInt? var totalTime: UInt?
var elapsedTimeMs: UInt? var elapsedTimeMs: UInt?
var trackTimer: Timer? var trackTimer: Timer?
@ -64,7 +64,7 @@ class WindowController: NSWindowController {
} }
@objc func stateChanged(_ notification: Notification) { @objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.MPDStatus.State
else { return } else { return }
self.state = state self.state = state

View File

@ -7,14 +7,23 @@
// //
import Cocoa import Cocoa
import PromiseKit
class AlbumDataSource: NSObject, NSCollectionViewDataSource { class AlbumDataSource: NSObject, NSCollectionViewDataSource {
var albums: [AlbumItem] = [] var albums: [Album] = []
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return albums.count return albums.count
} }
func resetCoverArt() {
albums = albums.map {
var album = $0
album.coverArtFetched = false
return album
}
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: .albumViewItem, for: indexPath) let item = collectionView.makeItem(withIdentifier: .albumViewItem, for: indexPath)
guard let albumViewItem = item as? AlbumViewItem else { return item } guard let albumViewItem = item as? AlbumViewItem else { return item }
@ -22,15 +31,24 @@ class AlbumDataSource: NSObject, NSCollectionViewDataSource {
albumViewItem.view.wantsLayer = true albumViewItem.view.wantsLayer = true
albumViewItem.setAlbum(albums[indexPath.item]) albumViewItem.setAlbum(albums[indexPath.item])
if albums[indexPath.item].coverArt == nil { if albums[indexPath.item].coverArt == nil &&
AlbumArtService(album: albums[indexPath.item]).fetchAlbumArt { image in !albums[indexPath.item].coverArtFetched {
AppDelegate.mpdClient.getAlbumFirstSong(for: albums[indexPath.item].mpdAlbum) {
guard let song = $0 else { return }
AlbumArtService(song: Song(mpdSong: song))
.fetchAlbumArt()
.done { image in
self.albums[indexPath.item].coverArt = image self.albums[indexPath.item].coverArt = image
self.albums[indexPath.item].coverArtFetched = true
DispatchQueue.main.async { DispatchQueue.main.async {
collectionView.reloadItems(at: [indexPath]) collectionView.reloadItems(at: [indexPath])
} }
} }
} }
}
return albumViewItem return albumViewItem
} }

View File

@ -9,16 +9,21 @@
import Cocoa import Cocoa
class QueueDataSource: NSObject, NSOutlineViewDataSource { class QueueDataSource: NSObject, NSOutlineViewDataSource {
var queue: [SongItem] = [] var queue: [QueueItem] = []
var queuePos: Int = -1 var queuePos: Int = -1
var queueIcon: NSImage? = nil var queueIcon: NSImage? = nil
func updateQueue(_ queue: [MPDClient.Song]) { func updateQueue(_ queue: [MPDClient.MPDSong]) {
queuePos = -1 queuePos = -1
self.queue = queue.enumerated().map { index, song in self.queue = queue.enumerated().map { index, mpdSong in
SongItem(song: song, queuePos: index, isPlaying: index == queuePos) let song = Song(mpdSong: mpdSong)
return QueueItem(
song: song,
queuePos: index,
isPlaying: index == queuePos
)
} }
} }
@ -35,7 +40,7 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
} }
} }
func setQueueIcon(_ state: MPDClient.Status.State) { func setQueueIcon(_ state: MPDClient.MPDStatus.State) {
switch state { switch state {
case .playing: case .playing:
queueIcon = .playIcon queueIcon = .playIcon

View File

@ -1,24 +0,0 @@
//
// String.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/01.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
extension String {
func sha1() -> String {
let data = self.data(using: String.Encoding.utf8)!
var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH))
data.withUnsafeBytes {
_ = CC_SHA1($0, CC_LONG(data.count), &digest)
}
let hexBytes = digest.map { String(format: "%02hhx", $0) }
return hexBytes.joined()
}
}

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.10.3a</string> <string>0.11.1-alpha</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>

View File

@ -36,10 +36,6 @@ class AlbumViewLayout: NSCollectionViewFlowLayout {
var divider: CGFloat = 1 var divider: CGFloat = 1
var itemWidth: CGFloat = 0 var itemWidth: CGFloat = 0
if let scrollView = collectionView.enclosingScrollView {
scrollPosition = scrollView.documentVisibleRect.minY / collectionView.bounds.height
}
repeat { repeat {
let totalPaddingWidth = sectionInset.left + sectionInset.right let totalPaddingWidth = sectionInset.left + sectionInset.right
let totalGutterWidth = (divider - 1) * (minimumInteritemSpacing) let totalGutterWidth = (divider - 1) * (minimumInteritemSpacing)
@ -52,6 +48,15 @@ class AlbumViewLayout: NSCollectionViewFlowLayout {
itemSize = NSSize(width: itemWidth, height: itemHeight) itemSize = NSSize(width: itemWidth, height: itemHeight)
} }
func saveScrollPosition() {
guard let collectionView = collectionView
else { return }
if let scrollView = collectionView.enclosingScrollView {
scrollPosition = scrollView.documentVisibleRect.minY / collectionView.bounds.height
}
}
func setScrollPosition() { func setScrollPosition() {
guard let collectionView = collectionView guard let collectionView = collectionView
else { return } else { return }

View File

@ -14,28 +14,28 @@ extension MPDClient {
enqueueCommand(command: .fetchAllAlbums) enqueueCommand(command: .fetchAllAlbums)
} }
func playAlbum(_ album: Album) { func playAlbum(_ album: MPDAlbum) {
enqueueCommand(command: .playAlbum, userData: ["album": album]) enqueueCommand(command: .playAlbum, userData: ["album": album])
} }
func getAlbumURI(for album: Album, callback: @escaping (String?) -> Void) { func getAlbumFirstSong(for album: MPDAlbum, callback: @escaping (MPDSong?) -> Void) {
enqueueCommand( enqueueCommand(
command: .getAlbumURI, command: .getAlbumFirstSong,
priority: .low, priority: .low,
userData: ["album": album, "callback": callback] userData: ["album": album, "callback": callback]
) )
} }
func sendPlayAlbum(_ album: Album) { func sendPlayAlbum(_ album: MPDAlbum) {
var songs: [Song] = [] var songs: [MPDSong] = []
mpd_run_clear(self.connection) mpd_run_clear(self.connection)
mpd_search_db_songs(self.connection, true) 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, album.title)
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist) mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
mpd_search_commit(self.connection) mpd_search_commit(self.connection)
while let mpdSong = mpd_recv_song(self.connection) { while let song = mpd_recv_song(self.connection) {
songs.append(Song(mpdSong)) songs.append(MPDSong(song))
} }
for song in songs { for song in songs {
mpd_run_add(self.connection, song.uri) mpd_run_add(self.connection, song.uri)
@ -44,36 +44,36 @@ extension MPDClient {
} }
func allAlbums() { func allAlbums() {
var albums: [Album] = [] var albums: [MPDAlbum] = []
var artist: String = "" var artist: String = ""
mpd_search_db_tags(self.connection, MPD_TAG_ALBUM) mpd_search_db_tags(self.connection, MPD_TAG_ALBUM)
mpd_search_add_group_tag(self.connection, MPD_TAG_ALBUM_ARTIST) mpd_search_add_group_tag(self.connection, MPD_TAG_ALBUM_ARTIST)
mpd_search_commit(self.connection) mpd_search_commit(self.connection)
while let mpdPair = mpd_recv_pair(self.connection) { while let pair = mpd_recv_pair(self.connection) {
let pair = Pair(mpdPair) let pair = MPDPair(pair)
switch pair.name { switch pair.name {
case "AlbumArtist": case "AlbumArtist":
artist = pair.value artist = pair.value
case "Album": case "Album":
albums.append(Album(title: pair.value, artist: artist)) albums.append(MPDAlbum(title: pair.value, artist: artist))
default: default:
break break
} }
mpd_return_pair(self.connection, pair.mpdPair) mpd_return_pair(self.connection, pair.pair)
} }
self.delegate?.didLoadAlbums(mpdClient: self, albums: albums) self.delegate?.didLoadAlbums(mpdClient: self, albums: albums)
} }
func albumURI(for album: Album, callback: (String?) -> Void) { func albumFirstSong(for album: MPDAlbum, callback: (MPDSong?) -> Void) {
var songURI: String?
guard isConnected else { return } guard isConnected else { return }
var firstSong: MPDSong?
mpd_search_db_songs(self.connection, true) 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, album.title)
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist) mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
@ -81,19 +81,14 @@ extension MPDClient {
mpd_search_commit(self.connection) mpd_search_commit(self.connection)
while let mpdSong = mpd_recv_song(self.connection) { while let song = mpd_recv_song(self.connection) {
let song = Song(mpdSong) let song = MPDSong(song)
if songURI == nil { if firstSong == nil {
songURI = song.uriString firstSong = song
} }
} }
callback( callback(firstSong)
songURI?
.split(separator: "/")
.dropLast()
.joined(separator: "/")
)
} }
} }

View File

@ -10,7 +10,7 @@ import Foundation
extension MPDClient { extension MPDClient {
func sendCommand( func sendCommand(
command: Command, command: MPDCommand,
userData: Dictionary<String, Any> = [:] userData: Dictionary<String, Any> = [:]
) { ) {
switch command { switch command {
@ -49,18 +49,19 @@ extension MPDClient {
case .fetchAllAlbums: case .fetchAllAlbums:
allAlbums() allAlbums()
case .playAlbum: case .playAlbum:
guard let album = userData["album"] as? Album else { return } guard let album = userData["album"] as? MPDAlbum else { return }
sendPlayAlbum(album) sendPlayAlbum(album)
case .getAlbumURI: case .getAlbumFirstSong:
guard let album = userData["album"] as? Album, guard let album = userData["album"] as? MPDAlbum,
let callback = userData["callback"] as? (String?) -> Void let callback = userData["callback"] as? (MPDSong?) -> Void
else { return } else { return }
albumURI(for: album, callback: callback)
albumFirstSong(for: album, callback: callback)
} }
} }
func enqueueCommand( func enqueueCommand(
command: Command, command: MPDCommand,
priority: BlockOperation.QueuePriority = .normal, priority: BlockOperation.QueuePriority = .normal,
userData: Dictionary<String, Any> = [:] userData: Dictionary<String, Any> = [:]
) { ) {

View File

@ -22,7 +22,7 @@ extension MPDClient {
else { return } else { return }
self.connection = connection self.connection = connection
self.status = Status(status) self.status = MPDStatus(status)
self.fetchQueue() self.fetchQueue()
self.fetchAllAlbums() self.fetchAllAlbums()

View File

@ -30,7 +30,7 @@ extension MPDClient {
func handleIdleResult(_ result: mpd_idle) { func handleIdleResult(_ result: mpd_idle) {
isIdle = false isIdle = false
let mpdIdle = Idle(rawValue: result.rawValue) let mpdIdle = MPDIdle(rawValue: result.rawValue)
if mpdIdle.contains(.database) { if mpdIdle.contains(.database) {
self.fetchAllAlbums() self.fetchAllAlbums()

View File

@ -27,7 +27,7 @@ extension MPDClient {
mpd_send_list_queue_meta(connection) mpd_send_list_queue_meta(connection)
while let mpdSong = mpd_recv_song(connection) { while let mpdSong = mpd_recv_song(connection) {
let song = Song(mpdSong) let song = MPDSong(mpdSong)
self.queue.append(song) self.queue.append(song)
} }
} }

View File

@ -16,6 +16,6 @@ extension MPDClient {
func sendRunStatus() { func sendRunStatus() {
guard let status = mpd_run_status(connection) else { return } guard let status = mpd_run_status(connection) else { return }
self.status = Status(status) self.status = MPDStatus(status)
} }
} }

View File

@ -15,8 +15,8 @@ class MPDClient {
var connection: OpaquePointer? var connection: OpaquePointer?
var isConnected: Bool = false var isConnected: Bool = false
var isIdle: Bool = false var isIdle: Bool = false
var status: Status? var status: MPDStatus?
var queue: [Song] = [] var queue: [MPDSong] = []
let commandQueue = OperationQueue() let commandQueue = OperationQueue()

View File

@ -1,27 +0,0 @@
//
// Idle.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/09.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
extension MPDClient {
struct Idle: OptionSet {
let rawValue: UInt32
static let database = Idle(rawValue: 0x1)
static let storedPlaylist = Idle(rawValue: 0x2)
static let queue = Idle(rawValue: 0x4)
static let player = Idle(rawValue: 0x8)
static let mixer = Idle(rawValue: 0x10)
static let output = Idle(rawValue: 0x20)
static let options = Idle(rawValue: 0x40)
static let update = Idle(rawValue: 0x80)
static let sticker = Idle(rawValue: 0x100)
static let subscription = Idle(rawValue: 0x200)
static let message = Idle(rawValue: 0x400)
}
}

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
extension MPDClient { extension MPDClient {
struct Album { struct MPDAlbum {
let title: String let title: String
let artist: String let artist: String
} }

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
extension MPDClient { extension MPDClient {
enum Command { enum MPDCommand {
// Transport commands // Transport commands
case prevTrack case prevTrack
case nextTrack case nextTrack
@ -30,6 +30,6 @@ extension MPDClient {
// Album commands // Album commands
case fetchAllAlbums case fetchAllAlbums
case playAlbum case playAlbum
case getAlbumURI case getAlbumFirstSong
} }
} }

View File

@ -0,0 +1,27 @@
//
// Idle.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/09.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
extension MPDClient {
struct MPDIdle: OptionSet {
let rawValue: UInt32
static let database = MPDIdle(rawValue: 0x1)
static let storedPlaylist = MPDIdle(rawValue: 0x2)
static let queue = MPDIdle(rawValue: 0x4)
static let player = MPDIdle(rawValue: 0x8)
static let mixer = MPDIdle(rawValue: 0x10)
static let output = MPDIdle(rawValue: 0x20)
static let options = MPDIdle(rawValue: 0x40)
static let update = MPDIdle(rawValue: 0x80)
static let sticker = MPDIdle(rawValue: 0x100)
static let subscription = MPDIdle(rawValue: 0x200)
static let message = MPDIdle(rawValue: 0x400)
}
}

View File

@ -0,0 +1,28 @@
//
// Pair.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/09.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import mpdclient
extension MPDClient {
class MPDPair {
let pair: UnsafeMutablePointer<mpd_pair>
init(_ pair: UnsafeMutablePointer<mpd_pair>) {
self.pair = pair
}
var name: String {
get { return String(cString: pair.pointee.name) }
}
var value: String {
get { return String(cString: pair.pointee.value) }
}
}
}

View File

@ -10,8 +10,8 @@ import Foundation
import mpdclient import mpdclient
extension MPDClient { extension MPDClient {
class Song { class MPDSong {
let mpdSong: OpaquePointer let song: OpaquePointer
enum TagType: Int { enum TagType: Int {
case unknown = -1 case unknown = -1
@ -33,26 +33,41 @@ extension MPDClient {
case tagCount case tagCount
} }
init(_ mpdSong: OpaquePointer) { init(_ song: OpaquePointer) {
self.mpdSong = mpdSong self.song = song
} }
deinit { deinit {
mpd_song_free(mpdSong) mpd_song_free(song)
} }
var uri: UnsafePointer<Int8> { var uri: UnsafePointer<Int8> {
return mpd_song_get_uri(mpdSong) return mpd_song_get_uri(song)
} }
var uriString: String { var uriString: String {
return String(cString: uri) return String(cString: uri)
} }
var album: MPDAlbum {
return MPDAlbum(
title: getTag(.album),
artist: artist
)
}
var artist: String {
if getTag(.albumArtist) != "" {
return getTag(.albumArtist)
} else {
return getTag(.artist)
}
}
func getTag(_ tagType: TagType) -> String { func getTag(_ tagType: TagType) -> String {
let mpdTagType = mpd_tag_type(rawValue: Int32(tagType.rawValue)) let mpdTagType = mpd_tag_type(rawValue: Int32(tagType.rawValue))
guard let tag = mpd_song_get_tag(mpdSong, mpdTagType, 0) guard let tag = mpd_song_get_tag(song, mpdTagType, 0)
else { return "" } else { return "" }
return String(cString: tag) return String(cString: tag)

View File

@ -10,8 +10,8 @@ import Foundation
import mpdclient import mpdclient
extension MPDClient { extension MPDClient {
class Status { class MPDStatus {
private let mpdStatus: OpaquePointer private let status: OpaquePointer
enum State: UInt { enum State: UInt {
case unknown = 0 case unknown = 0
@ -20,38 +20,38 @@ extension MPDClient {
case paused = 3 case paused = 3
} }
init(_ mpdStatus: OpaquePointer) { init(_ status: OpaquePointer) {
self.mpdStatus = mpdStatus self.status = status
} }
deinit { deinit {
mpd_status_free(mpdStatus) mpd_status_free(status)
} }
var state: State { var state: State {
let mpdState = mpd_status_get_state(mpdStatus) let mpdState = mpd_status_get_state(status)
return State(rawValue: UInt(mpdState.rawValue))! return State(rawValue: UInt(mpdState.rawValue))!
} }
var totalTime: UInt { var totalTime: UInt {
let mpdTotalTime = mpd_status_get_total_time(mpdStatus) let mpdTotalTime = mpd_status_get_total_time(status)
return UInt(mpdTotalTime) return UInt(mpdTotalTime)
} }
var elapsedTimeMs: UInt { var elapsedTimeMs: UInt {
let mpdElapsedTimeMs = mpd_status_get_elapsed_ms(mpdStatus) let mpdElapsedTimeMs = mpd_status_get_elapsed_ms(status)
return UInt(mpdElapsedTimeMs) return UInt(mpdElapsedTimeMs)
} }
var song: Int { var song: Int {
return Int(mpd_status_get_song_pos(mpdStatus)) return Int(mpd_status_get_song_pos(status))
} }
var updating: Bool { var updating: Bool {
let updating = mpd_status_get_update_id(mpdStatus) let updating = mpd_status_get_update_id(status)
return updating > 0 return updating > 0
} }

View File

@ -1,26 +0,0 @@
//
// Pair.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/09.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import mpdclient
class Pair {
let mpdPair: UnsafeMutablePointer<mpd_pair>
init(_ mpdPair: UnsafeMutablePointer<mpd_pair>) {
self.mpdPair = mpdPair
}
var name: String {
get { return String(cString: mpdPair.pointee.name) }
}
var value: String {
get { return String(cString: mpdPair.pointee.value) }
}
}

View File

@ -12,14 +12,14 @@ protocol MPDClientDelegate {
func didConnect(mpdClient: MPDClient) func didConnect(mpdClient: MPDClient)
func willDisconnect(mpdClient: MPDClient) func willDisconnect(mpdClient: MPDClient)
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) func didUpdateState(mpdClient: MPDClient, state: MPDClient.MPDStatus.State)
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt) func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt)
func willStartDatabaseUpdate(mpdClient: MPDClient) func willStartDatabaseUpdate(mpdClient: MPDClient)
func didFinishDatabaseUpdate(mpdClient: MPDClient) func didFinishDatabaseUpdate(mpdClient: MPDClient)
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.MPDSong])
func didUpdateQueuePos(mpdClient: MPDClient, song: Int) func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album]) func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.MPDAlbum])
} }

View File

@ -7,17 +7,23 @@
// //
import Cocoa import Cocoa
import CryptoSwift
struct AlbumItem { struct Album {
var album: MPDClient.Album var mpdAlbum: MPDClient.MPDAlbum
var coverArt: NSImage? var coverArt: NSImage?
var coverArtFetched: Bool = false
init(mpdAlbum: MPDClient.MPDAlbum) {
self.mpdAlbum = mpdAlbum
}
var title: String { var title: String {
return album.title return mpdAlbum.title
} }
var artist: String { var artist: String {
return album.artist return mpdAlbum.artist
} }
var hash: String { var hash: String {

View File

@ -8,8 +8,8 @@
import Foundation import Foundation
struct SongItem { struct QueueItem {
var song: MPDClient.Song var song: Song
var queuePos: Int var queuePos: Int
var isPlaying: Bool var isPlaying: Bool
} }

View File

@ -0,0 +1,25 @@
//
// SongItem.swift
// Persephone
//
// Created by Daniel Barber on 2019/3/25.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct Song {
var mpdSong: MPDClient.MPDSong
var title: String {
return mpdSong.getTag(.title)
}
var artist: String {
return mpdSong.getTag(.artist)
}
var album: Album {
return Album(mpdAlbum: mpdSong.album)
}
}

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="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS"> <document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14490.70" 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="14460.31"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/> <capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/>
<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>
@ -315,11 +315,11 @@
<objects> <objects>
<viewController title="Album Art" id="3C9-vU-zjZ" customClass="AlbumArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Album Art" id="3C9-vU-zjZ" customClass="AlbumArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="PyK-v2-kus"> <view key="view" id="PyK-v2-kus">
<rect key="frame" x="0.0" y="0.0" width="524" height="100"/> <rect key="frame" x="0.0" y="0.0" width="524" height="168"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zZn-Rm-e1f"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zZn-Rm-e1f">
<rect key="frame" x="53" y="62" width="104" height="17"/> <rect key="frame" x="53" y="130" width="104" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Music Directory:" id="sPn-V6-CfK"> <textFieldCell key="cell" lineBreakMode="clipping" title="Music Directory:" id="sPn-V6-CfK">
<font key="font" usesAppearanceFont="YES"/> <font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -327,7 +327,7 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gDk-ca-eOa"> <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gDk-ca-eOa">
<rect key="frame" x="162" y="58" width="288" height="22"/> <rect key="frame" x="162" y="126" width="288" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="~/Music" drawsBackground="YES" id="7WZ-b7-GUs"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="~/Music" drawsBackground="YES" id="7WZ-b7-GUs">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -338,7 +338,7 @@
</connections> </connections>
</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="27" width="265" height="18"/> <rect key="frame" x="160" y="95" 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" 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"/>
@ -347,15 +347,51 @@
<action selector="updateFetchMissingArtworkFromInternet:" target="3C9-vU-zjZ" id="I7x-9V-xJr"/> <action selector="updateFetchMissingArtworkFromInternet:" target="3C9-vU-zjZ" id="I7x-9V-xJr"/>
</connections> </connections>
</button> </button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="z1g-nP-ksw">
<rect key="frame" x="160" y="64" width="264" height="18"/>
<constraints>
<constraint firstAttribute="width" constant="260" id="gK0-aW-CJy"/>
</constraints>
<buttonCell key="cell" type="check" title="Save fetched artwork to music directory" bezelStyle="regularSquare" imagePosition="left" enabled="NO" inset="2" id="ZeZ-O4-vjS">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xgS-Kg-8KR">
<rect key="frame" x="162" y="26" width="144" height="22"/>
<constraints>
<constraint firstAttribute="width" constant="144" id="DSX-th-Wn1"/>
</constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" enabled="NO" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="folder.jpg" drawsBackground="YES" id="nKF-YI-xBL">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SmH-w6-5QI">
<rect key="frame" x="37" y="30" width="119" height="17"/>
<textFieldCell key="cell" lineBreakMode="clipping" enabled="NO" alignment="right" title="Cover art filename:" id="b4u-u7-iWD">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="zZn-Rm-e1f" firstAttribute="leading" secondItem="PyK-v2-kus" secondAttribute="leading" constant="55" id="F9T-mO-lMa"/> <constraint firstItem="pRL-MG-1Be" firstAttribute="centerX" secondItem="z1g-nP-ksw" secondAttribute="centerX" id="0Ev-Ia-XBO"/>
<constraint firstItem="gDk-ca-eOa" firstAttribute="top" secondItem="PyK-v2-kus" secondAttribute="top" constant="20" symbolic="YES" id="NSz-Xf-KZS"/> <constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="pRL-MG-1Be" secondAttribute="leading" id="1jd-wZ-a4Q"/>
<constraint firstAttribute="trailing" secondItem="gDk-ca-eOa" secondAttribute="trailing" constant="74" id="QMb-TP-IdQ"/> <constraint firstItem="pRL-MG-1Be" firstAttribute="leading" secondItem="z1g-nP-ksw" secondAttribute="leading" id="3tZ-Ub-RaT"/>
<constraint firstItem="pRL-MG-1Be" firstAttribute="top" secondItem="gDk-ca-eOa" secondAttribute="bottom" constant="15" id="bD6-hA-Wz5"/> <constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="zZn-Rm-e1f" secondAttribute="trailing" constant="7" id="C0m-yx-gXh"/>
<constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="zZn-Rm-e1f" secondAttribute="trailing" constant="7" id="oZ5-45-Pe5"/> <constraint firstItem="z1g-nP-ksw" firstAttribute="leading" secondItem="xgS-Kg-8KR" secondAttribute="leading" id="Dkv-ai-X2G"/>
<constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="pRL-MG-1Be" secondAttribute="leading" id="sBG-Yb-ii6"/> <constraint firstItem="xgS-Kg-8KR" firstAttribute="leading" secondItem="SmH-w6-5QI" secondAttribute="trailing" constant="8" symbolic="YES" id="KQk-nr-H8y"/>
<constraint firstItem="zZn-Rm-e1f" firstAttribute="top" secondItem="PyK-v2-kus" secondAttribute="top" constant="21" id="wHW-jd-TaG"/> <constraint firstItem="zZn-Rm-e1f" firstAttribute="leading" secondItem="PyK-v2-kus" secondAttribute="leading" constant="55" id="OzK-MR-zuB"/>
<constraint firstAttribute="bottom" secondItem="SmH-w6-5QI" secondAttribute="bottom" constant="30" id="aBY-Ny-jPe"/>
<constraint firstItem="pRL-MG-1Be" firstAttribute="top" secondItem="gDk-ca-eOa" secondAttribute="bottom" constant="15" id="dKy-uC-r43"/>
<constraint firstItem="xgS-Kg-8KR" firstAttribute="top" secondItem="z1g-nP-ksw" secondAttribute="bottom" constant="18" id="lfR-Im-bd4"/>
<constraint firstAttribute="trailing" secondItem="gDk-ca-eOa" secondAttribute="trailing" constant="74" id="n8X-T2-tXA"/>
<constraint firstAttribute="bottom" secondItem="xgS-Kg-8KR" secondAttribute="bottom" constant="26" id="oXZ-qo-HwX"/>
<constraint firstItem="SmH-w6-5QI" firstAttribute="top" secondItem="zZn-Rm-e1f" secondAttribute="bottom" constant="83" id="qhC-mD-Bvw"/>
<constraint firstItem="z1g-nP-ksw" firstAttribute="top" secondItem="pRL-MG-1Be" secondAttribute="bottom" constant="17" id="sTP-hk-zfU"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>
@ -365,7 +401,7 @@
</viewController> </viewController>
<customObject id="KzD-E3-lpA" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="KzD-E3-lpA" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="1626" y="339"/> <point key="canvasLocation" x="1626" y="373"/>
</scene> </scene>
<!--General--> <!--General-->
<scene sceneID="xTC-Y5-Agk"> <scene sceneID="xTC-Y5-Agk">
@ -389,7 +425,7 @@
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="IbX-oV-soD"> <textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="IbX-oV-soD">
<rect key="frame" x="162" y="26" width="80" height="22"/> <rect key="frame" x="162" y="26" width="80" height="22"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="80" id="WAb-PB-Z1Y"/> <constraint firstAttribute="width" constant="80" id="vW2-G6-2vi"/>
</constraints> </constraints>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="6600" drawsBackground="YES" id="i9j-nB-bqq"> <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="6600" drawsBackground="YES" id="i9j-nB-bqq">
<numberFormatter key="formatter" formatterBehavior="custom10_4" numberStyle="decimal" usesGroupingSeparator="NO" minimumIntegerDigits="1" maximumIntegerDigits="2000000000" maximumFractionDigits="3" id="UiQ-gi-Hbp"> <numberFormatter key="formatter" formatterBehavior="custom10_4" numberStyle="decimal" usesGroupingSeparator="NO" minimumIntegerDigits="1" maximumIntegerDigits="2000000000" maximumFractionDigits="3" id="UiQ-gi-Hbp">
@ -406,17 +442,17 @@
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY">
<rect key="frame" x="76" y="62" width="80" height="17"/> <rect key="frame" x="76" y="62" width="80" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="76" id="4VR-n5-bGr"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU"> <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU">
<rect key="frame" x="77" y="30" width="77" height="17"/> <rect key="frame" x="78" y="30" width="76" height="17"/>
<constraints>
<constraint firstAttribute="width" constant="72" id="Of6-Ls-knP"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir"> <textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -425,16 +461,15 @@
</textField> </textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="wPm-sJ-e9E" firstAttribute="top" secondItem="Uwt-Lw-ILP" secondAttribute="top" constant="20" symbolic="YES" id="1u9-dh-XrA"/> <constraint firstItem="kvB-99-zwY" firstAttribute="centerX" secondItem="AU9-wN-kbU" secondAttribute="centerX" id="6bm-yZ-rJu"/>
<constraint firstItem="wPm-sJ-e9E" firstAttribute="leading" secondItem="kvB-99-zwY" secondAttribute="trailing" constant="8" symbolic="YES" id="655-3c-mJH"/> <constraint firstItem="AU9-wN-kbU" firstAttribute="top" secondItem="kvB-99-zwY" secondAttribute="bottom" constant="15" id="6qh-zn-2xt"/>
<constraint firstItem="AU9-wN-kbU" firstAttribute="top" secondItem="kvB-99-zwY" secondAttribute="bottom" constant="15" id="FPR-mZ-SUo"/> <constraint firstAttribute="trailing" secondItem="wPm-sJ-e9E" secondAttribute="trailing" constant="74" id="B0v-nc-2aA"/>
<constraint firstItem="IbX-oV-soD" firstAttribute="top" secondItem="wPm-sJ-e9E" secondAttribute="bottom" constant="10" symbolic="YES" id="G7a-VX-OLJ"/> <constraint firstItem="kvB-99-zwY" firstAttribute="leading" secondItem="Uwt-Lw-ILP" secondAttribute="leading" constant="78" id="Hvc-Wt-Uha"/>
<constraint firstItem="kvB-99-zwY" firstAttribute="top" secondItem="Uwt-Lw-ILP" secondAttribute="top" constant="21" id="ITg-Ag-wpl"/> <constraint firstItem="wPm-sJ-e9E" firstAttribute="leading" secondItem="kvB-99-zwY" secondAttribute="trailing" constant="8" symbolic="YES" id="NAc-1j-JEH"/>
<constraint firstItem="kvB-99-zwY" firstAttribute="leading" secondItem="Uwt-Lw-ILP" secondAttribute="leading" constant="78" id="Kw0-2i-oST"/> <constraint firstAttribute="bottom" secondItem="IbX-oV-soD" secondAttribute="bottom" constant="26" id="QHJ-ID-D3L"/>
<constraint firstItem="IbX-oV-soD" firstAttribute="leading" secondItem="AU9-wN-kbU" secondAttribute="trailing" constant="10" id="Y6y-25-qRM"/> <constraint firstAttribute="bottom" secondItem="AU9-wN-kbU" secondAttribute="bottom" constant="30" id="j5r-By-kiE"/>
<constraint firstAttribute="trailing" secondItem="wPm-sJ-e9E" secondAttribute="trailing" constant="74" id="cX3-Sz-RXR"/> <constraint firstItem="wPm-sJ-e9E" firstAttribute="leading" secondItem="IbX-oV-soD" secondAttribute="leading" id="lz4-7S-QQb"/>
<constraint firstItem="kvB-99-zwY" firstAttribute="centerX" secondItem="AU9-wN-kbU" secondAttribute="centerX" id="fdg-fL-UzL"/> <constraint firstItem="IbX-oV-soD" firstAttribute="top" secondItem="wPm-sJ-e9E" secondAttribute="bottom" constant="10" id="sZA-01-JAS"/>
<constraint firstItem="wPm-sJ-e9E" firstAttribute="leading" secondItem="IbX-oV-soD" secondAttribute="leading" id="qBw-Ri-Z4l"/>
</constraints> </constraints>
</view> </view>
<connections> <connections>
@ -450,18 +485,19 @@
<scene sceneID="QcX-dC-cTZ"> <scene sceneID="QcX-dC-cTZ">
<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">
<view key="view" id="2su-YT-hba"> <splitView key="view" wantsLayer="YES" dividerStyle="thin" id="84I-w3-Mxl">
<rect key="frame" x="0.0" y="0.0" width="350" height="300"/> <rect key="frame" x="0.0" y="0.0" width="328" height="548"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews> <subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="S3o-nF-NN7"> <scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="19" horizontalPageScroll="10" verticalLineScroll="19" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="S3o-nF-NN7">
<rect key="frame" x="0.0" y="0.0" width="350" height="300"/> <rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="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="350" height="300"/> <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="16" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll"> <outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" selectionHighlightStyle="sourceList" multipleSelection="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="0Co-uF-CCB" id="jEJ-jg-fll">
<rect key="frame" x="0.0" y="0.0" width="350" height="300"/> <rect key="frame" x="0.0" y="0.0" width="328" height="219"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/> <size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -484,9 +520,11 @@
<rect key="frame" x="1" y="1" width="200" height="17"/> <rect key="frame" x="1" y="1" width="200" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xgd-Cz-np3"> <textField verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="xgd-Cz-np3">
<rect key="frame" x="0.0" y="1" width="447" height="14"/> <rect key="frame" x="0.0" y="1" width="329" height="14"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> <constraints>
<constraint firstAttribute="width" constant="443" id="mkA-ng-q8a"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="QUEUE" id="Mqf-uh-ibl"> <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="QUEUE" id="Mqf-uh-ibl">
<font key="font" metaFont="smallSystemBold"/> <font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlAccentColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlAccentColor" catalog="System" colorSpace="catalog"/>
@ -494,6 +532,10 @@
</textFieldCell> </textFieldCell>
</textField> </textField>
</subviews> </subviews>
<constraints>
<constraint firstItem="xgd-Cz-np3" firstAttribute="leading" secondItem="GOd-cg-juD" secondAttribute="leading" constant="2" id="iOU-E2-K47"/>
<constraint firstItem="xgd-Cz-np3" firstAttribute="centerY" secondItem="GOd-cg-juD" secondAttribute="centerY" id="uxd-zs-s33"/>
</constraints>
</tableCellView> </tableCellView>
<tableCellView identifier="songTitleCell" id="5rR-Gz-AcP"> <tableCellView identifier="songTitleCell" id="5rR-Gz-AcP">
<rect key="frame" x="1" y="20" width="200" height="17"/> <rect key="frame" x="1" y="20" width="200" height="17"/>
@ -502,7 +544,7 @@
<imageView translatesAutoresizingMaskIntoConstraints="NO" id="o8i-cz-hIP"> <imageView translatesAutoresizingMaskIntoConstraints="NO" id="o8i-cz-hIP">
<rect key="frame" x="3" y="0.0" width="17" height="17"/> <rect key="frame" x="3" y="0.0" width="17" height="17"/>
<constraints> <constraints>
<constraint firstAttribute="width" constant="17" id="UFf-Fg-9Qg"/> <constraint firstAttribute="width" constant="17" id="v6R-Dd-a1y"/>
</constraints> </constraints>
<imageCell key="cell" refusesFirstResponder="YES" imageScaling="proportionallyDown" id="ckK-gW-Vhx"/> <imageCell key="cell" refusesFirstResponder="YES" imageScaling="proportionallyDown" id="ckK-gW-Vhx"/>
</imageView> </imageView>
@ -516,12 +558,12 @@
</textField> </textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstAttribute="trailing" secondItem="i0h-bn-auJ" secondAttribute="trailing" constant="2" id="0bp-mw-2mz"/> <constraint firstItem="o8i-cz-hIP" firstAttribute="bottom" secondItem="i0h-bn-auJ" secondAttribute="bottom" id="3PU-pE-aEs"/>
<constraint firstItem="i0h-bn-auJ" firstAttribute="leading" secondItem="o8i-cz-hIP" secondAttribute="trailing" constant="7" id="GUh-cd-QQx"/> <constraint firstItem="o8i-cz-hIP" firstAttribute="leading" secondItem="5rR-Gz-AcP" secondAttribute="leading" constant="3" id="6gY-cP-87e"/>
<constraint firstItem="o8i-cz-hIP" firstAttribute="centerY" secondItem="5rR-Gz-AcP" secondAttribute="centerY" id="QT8-Zj-AGK"/> <constraint firstItem="o8i-cz-hIP" firstAttribute="top" secondItem="i0h-bn-auJ" secondAttribute="top" id="Aju-r7-8gb"/>
<constraint firstItem="o8i-cz-hIP" firstAttribute="leading" secondItem="5rR-Gz-AcP" secondAttribute="leading" constant="3" id="VCd-sT-dmS"/> <constraint firstItem="o8i-cz-hIP" firstAttribute="centerY" secondItem="5rR-Gz-AcP" secondAttribute="centerY" id="POX-ni-OdT"/>
<constraint firstItem="o8i-cz-hIP" firstAttribute="bottom" secondItem="i0h-bn-auJ" secondAttribute="bottom" id="ud8-g2-OYn"/> <constraint firstItem="i0h-bn-auJ" firstAttribute="leading" secondItem="o8i-cz-hIP" secondAttribute="trailing" constant="7" id="QIl-PH-2Ox"/>
<constraint firstItem="o8i-cz-hIP" firstAttribute="top" secondItem="i0h-bn-auJ" secondAttribute="top" id="yN0-Pp-adE"/> <constraint firstAttribute="trailing" secondItem="i0h-bn-auJ" secondAttribute="trailing" constant="2" id="S2f-3u-olm"/>
</constraints> </constraints>
<connections> <connections>
<outlet property="imageView" destination="o8i-cz-hIP" id="4In-Lr-QcL"/> <outlet property="imageView" destination="o8i-cz-hIP" id="4In-Lr-QcL"/>
@ -530,7 +572,7 @@
</tableCellView> </tableCellView>
</prototypeCellViews> </prototypeCellViews>
</tableColumn> </tableColumn>
<tableColumn identifier="songArtistColumn" width="144" 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="smallSystem"/>
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
@ -544,11 +586,11 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/> <tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
<prototypeCellViews> <prototypeCellViews>
<tableCellView identifier="songArtistCell" id="JSk-Vc-Y7e"> <tableCellView identifier="songArtistCell" id="JSk-Vc-Y7e">
<rect key="frame" x="204" y="1" width="144" height="17"/> <rect key="frame" x="204" y="1" width="122" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw"> <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="tBe-Q9-3Rw">
<rect key="frame" x="0.0" y="0.0" width="149" height="17"/> <rect key="frame" x="0.0" y="0.0" width="127" height="17"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Ceb-ec-ydU"> <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="Ceb-ec-ydU">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@ -557,9 +599,9 @@
</textField> </textField>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="tBe-Q9-3Rw" firstAttribute="centerY" secondItem="JSk-Vc-Y7e" secondAttribute="centerY" id="Tkg-cb-Bg6"/> <constraint firstAttribute="trailing" secondItem="tBe-Q9-3Rw" secondAttribute="trailing" constant="-3" id="E6a-sY-dne"/>
<constraint firstAttribute="trailing" secondItem="tBe-Q9-3Rw" secondAttribute="trailing" constant="-3" id="VhZ-ua-QQX"/> <constraint firstItem="tBe-Q9-3Rw" firstAttribute="leading" secondItem="JSk-Vc-Y7e" secondAttribute="leading" constant="2" id="Gm4-l1-WRz"/>
<constraint firstItem="tBe-Q9-3Rw" firstAttribute="leading" secondItem="JSk-Vc-Y7e" secondAttribute="leading" constant="2" id="cTy-tR-Grg"/> <constraint firstItem="tBe-Q9-3Rw" firstAttribute="centerY" secondItem="JSk-Vc-Y7e" secondAttribute="centerY" id="KhJ-nn-rh5"/>
</constraints> </constraints>
<connections> <connections>
<outlet property="textField" destination="tBe-Q9-3Rw" id="2e6-zi-tKj"/> <outlet property="textField" destination="tBe-Q9-3Rw" id="2e6-zi-tKj"/>
@ -577,11 +619,11 @@
<nil key="backgroundColor"/> <nil key="backgroundColor"/>
</clipView> </clipView>
<constraints> <constraints>
<constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="tgW-46-U0V"/> <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="Kqx-7n-cws"/>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="ynf-58-b0B"/> <constraint firstAttribute="width" relation="lessThanOrEqual" constant="500" id="zuT-k9-w8d"/>
</constraints> </constraints>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="7mx-v9-DSr"> <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="7mx-v9-DSr">
<rect key="frame" x="0.0" y="284" width="350" height="16"/> <rect key="frame" x="0.0" y="237" width="328" height="16"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="p5z-C0-FUJ"> <scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="p5z-C0-FUJ">
@ -589,21 +631,40 @@
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
</scroller> </scroller>
</scrollView> </scrollView>
<customView id="iUb-eV-Qws">
<rect key="frame" x="0.0" y="220" width="328" height="328"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY">
<rect key="frame" x="0.0" y="0.0" width="328" height="328"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="blankAlbum" id="IoN-3N-TCb"/>
</imageView>
</subviews> </subviews>
<constraints> <constraints>
<constraint firstItem="S3o-nF-NN7" firstAttribute="leading" secondItem="2su-YT-hba" secondAttribute="leading" id="B7i-Su-Rgr"/> <constraint firstItem="Dw3-M5-tWY" firstAttribute="leading" secondItem="iUb-eV-Qws" secondAttribute="leading" id="ERf-Kd-Q7L"/>
<constraint firstItem="S3o-nF-NN7" firstAttribute="top" secondItem="2su-YT-hba" secondAttribute="top" id="EZN-ac-0xk"/> <constraint firstAttribute="bottom" secondItem="Dw3-M5-tWY" secondAttribute="bottom" id="GGz-uh-bzq"/>
<constraint firstAttribute="bottom" secondItem="S3o-nF-NN7" secondAttribute="bottom" id="OTS-yj-4am"/> <constraint firstAttribute="trailing" secondItem="Dw3-M5-tWY" secondAttribute="trailing" id="c91-AQ-BT2"/>
<constraint firstAttribute="trailing" secondItem="S3o-nF-NN7" secondAttribute="trailing" id="Yis-ZS-y2w"/> <constraint firstAttribute="width" secondItem="iUb-eV-Qws" secondAttribute="height" multiplier="1:1" id="f4L-V4-5x2"/>
<constraint firstItem="Dw3-M5-tWY" firstAttribute="top" secondItem="iUb-eV-Qws" secondAttribute="top" id="ue4-Gb-CaX"/>
</constraints> </constraints>
</view> <shadow key="shadow">
<color key="color" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</shadow>
</customView>
</subviews>
<holdingPriorities>
<real value="250"/>
<real value="250"/>
</holdingPriorities>
</splitView>
<connections> <connections>
<outlet property="queueAlbumArtImage" destination="Dw3-M5-tWY" id="3hQ-Gu-XqM"/>
<outlet property="queueView" destination="jEJ-jg-fll" id="cwo-E8-deo"/> <outlet property="queueView" destination="jEJ-jg-fll" id="cwo-E8-deo"/>
</connections> </connections>
</viewController> </viewController>
<customObject id="du4-e9-TfX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="du4-e9-TfX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="834" y="679"/> <point key="canvasLocation" x="820" y="749"/>
</scene> </scene>
<!--Album View Controller--> <!--Album View Controller-->
<scene sceneID="7Ua-Hj-zWt"> <scene sceneID="7Ua-Hj-zWt">
@ -654,11 +715,12 @@
</viewController> </viewController>
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="744" y="1077"/> <point key="canvasLocation" x="531" y="1358"/>
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="NSPreferencesGeneral" width="32" height="32"/> <image name="NSPreferencesGeneral" width="32" height="32"/>
<image name="blankAlbum" width="128" height="128"/>
<image name="coverArtPreferencesIcon" width="32" height="32"/> <image name="coverArtPreferencesIcon" width="32" height="32"/>
<image name="nextTrackButton" width="17" height="17"/> <image name="nextTrackButton" width="17" height="17"/>
<image name="playButton" width="17" height="17"/> <image name="playButton" width="17" height="17"/>

View File

@ -11,43 +11,60 @@ import PromiseKit
class AlbumArtService { class AlbumArtService {
var preferences = Preferences() var preferences = Preferences()
let album: AlbumItem let song: Song
let album: Album
let cachedArtworkSize = 180 let cachedArtworkSize = 180
let cachedArtworkQuality: CGFloat = 0.5 let cachedArtworkQuality: CGFloat = 0.5
let bigArtworkSize = 600
var session = URLSession(configuration: .default) var session = URLSession(configuration: .default)
let cacheQueue = DispatchQueue(label: "albumArtCacheQueue") let artworkQueue = DispatchQueue(label: "albumArtQueue", qos: .utility)
init(album: AlbumItem) { init(song: Song) {
self.album = album self.song = song
self.album = song.album
} }
func fetchAlbumArt(callback: @escaping (_ image: NSImage?) -> Void) { func fetchBigAlbumArt() -> Promise<NSImage?> {
cacheQueue.async { return firstly {
firstly { self.getArtworkFromFilesystem()
}.then { (image: NSImage?) -> Promise<NSImage?> in
image.map(Promise.value) ?? self.getRemoteArtwork()
}.recover { (_) -> Guarantee<NSImage?> in
return .value(nil)
}
}
func fetchAlbumArt() -> Guarantee<NSImage?> {
return firstly {
self.getCachedArtwork() self.getCachedArtwork()
}.then { artwork -> Promise<NSImage?> in }.then { (artwork: NSImage?) -> Promise<NSImage?> in
artwork.map(Promise.value) ?? self.cacheIfNecessary(self.getArtworkFromFilesystem()) artwork.map(Promise.value) ?? self.getArtworkFromFilesystem()
}.then { artwork -> Promise<NSImage?> in }.then { (artwork: NSImage?) -> Promise<NSImage?> in
artwork.map(Promise.value) ?? self.cacheIfNecessary(self.getArtworkFromMusicBrainz().map(Optional.some)) artwork.map(Promise.value) ?? self.getRemoteArtwork()
}.tap { result in }.compactMap(on: artworkQueue) {
switch result { return self.sizeAndCacheImage($0).map(Optional.some)
case .fulfilled(nil), .rejected(MusicBrainzError.noArtworkAvailable): }.recover { _ in
self.cacheArtwork(data: Data()) return .value(nil)
default:
break
}
}.recover { error in
.value(nil)
}.done(callback)
} }
} }
func cacheIfNecessary(_ promise: Promise<NSImage?>) -> Promise<NSImage?> { func sizeAndCacheImage(_ image: NSImage?) -> NSImage? {
return promise.get { image in switch image {
if let data = image?.jpegData(compressionQuality: self.cachedArtworkQuality) { case nil:
self.cacheArtwork(data: data) 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

@ -14,22 +14,38 @@ extension AlbumArtService {
func getCachedArtwork() -> Promise<NSImage?> { func getCachedArtwork() -> Promise<NSImage?> {
return Promise { seal in return Promise { seal in
let cacheFilePath = AlbumArtService.cacheDir.appendingPathComponent(album.hash).path artworkQueue.async {
if self.isArtworkCached() {
let cacheFilePath = AlbumArtService.cacheDir.appendingPathComponent(self.album.hash).path
let data = FileManager.default.contents(atPath: cacheFilePath) let data = FileManager.default.contents(atPath: cacheFilePath)
let image = data.flatMap(NSImage.init(data:)) let image = NSImage(data: data ?? Data()) ?? NSImage.defaultCoverArt
seal.fulfill(image) seal.fulfill(image)
} else {
seal.fulfill(nil)
}
}
} }
} }
func cacheArtwork(data: Data?) { func cacheArtwork(data: Data?) {
artworkQueue.async {
guard let bundleIdentifier = Bundle.main.bundleIdentifier, guard let bundleIdentifier = Bundle.main.bundleIdentifier,
let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(bundleIdentifier) .appendingPathComponent(bundleIdentifier)
else { return } else { return }
let cacheFilePath = cacheDir.appendingPathComponent(album.hash).path let cacheFilePath = cacheDir.appendingPathComponent(self.album.hash).path
if !self.isArtworkCached() {
FileManager.default.createFile(atPath: cacheFilePath, contents: data, attributes: nil) FileManager.default.createFile(atPath: cacheFilePath, contents: data, attributes: nil)
} }
}
}
func isArtworkCached() -> Bool {
let cacheFilePath = AlbumArtService.cacheDir.appendingPathComponent(album.hash).path
return FileManager.default.fileExists(atPath: cacheFilePath)
}
} }

View File

@ -10,29 +10,61 @@ import Cocoa
import PromiseKit import PromiseKit
extension AlbumArtService { extension AlbumArtService {
func getArtworkFromFilesystem() -> Promise<NSImage?> { var coverArtFilenames: [String] {
let coverArtFilenames = [ return [
"folder.jpg", "folder.jpg",
"cover.jpg", "cover.jpg",
"\(album.artist) - \(album.title).jpg" "\(album.artist) - \(album.title).jpg"
] ]
}
return getAlbumURI().map { albumURI in var musicDir: String {
return self.preferences.expandedMpdLibraryDir
}
func getArtworkFromFilesystem() -> Promise<NSImage?> {
return Promise { seal in
artworkQueue.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 = self.preferences.expandedMpdLibraryDir let musicDir = self.preferences.expandedMpdLibraryDir
return coverArtFilenames return self.coverArtFilenames
.lazy .lazy
.map { "\(musicDir)/\(albumURI)/\($0)" } .map { "\(musicDir)/\(self.songPath)/\($0)" }
.compactMap(self.tryImage) .first {
.first FileManager.default.fileExists(atPath: $0)
} }
} }
func getAlbumURI() -> Promise<String> { var songPath: String {
return Promise { seal in return song
AppDelegate.mpdClient.getAlbumURI(for: album.album, callback: seal.fulfill) .mpdSong
} .uriString
.compactMap { $0 } .split(separator: "/")
.dropLast()
.joined(separator: "/")
} }
func tryImage(_ filePath: String) -> NSImage? { func tryImage(_ filePath: String) -> NSImage? {
@ -40,10 +72,6 @@ extension AlbumArtService {
let image = NSImage(data: data) let image = NSImage(data: data)
else { return nil } else { return nil }
let imageThumb = image.toFitBox( return image
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
)
return imageThumb
} }
} }

View File

@ -12,26 +12,34 @@ import PromiseKit
import PMKFoundation import PMKFoundation
extension AlbumArtService { extension AlbumArtService {
enum MusicBrainzError: Error { enum RemoteArtworkError: Error {
case noArtworkAvailable case noArtworkAvailable
case notConfigured
} }
func getRemoteArtwork() -> Promise<NSImage> { func getRemoteArtwork() -> Promise<NSImage?> {
return Promise { seal in return Promise { seal in
if preferences.fetchMissingArtworkFromInternet {
artworkQueue.async {
let albumArtWorkItem = DispatchWorkItem { let albumArtWorkItem = DispatchWorkItem {
self.getArtworkFromMusicBrainz().pipe(to: seal.resolve) self.getArtworkFromMusicBrainz().map(Optional.some).pipe(to: seal.resolve)
} }
AlbumArtQueue.shared.addToQueue(workItem: albumArtWorkItem) AlbumArtQueue.shared.addToQueue(workItem: albumArtWorkItem)
} }
} else {
throw RemoteArtworkError.notConfigured
}
}
} }
func getArtworkFromMusicBrainz() -> Promise<NSImage> { func getArtworkFromMusicBrainz() -> Promise<NSImage> {
var search = URLComponents(string: "https://musicbrainz.org/ws/2/release/")! 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" search.query = "query=artist:\(album.artist) AND release:\(album.title) AND country:US&limit=1&fmt=json"
return URLSession.shared.dataTask(.promise, with: search.url!).validate() return firstly {
.compactMap { URLSession.shared.dataTask(.promise, with: search.url!).validate()
}.compactMap {
JSON($0.data) JSON($0.data)
}.compactMap { }.compactMap {
$0["releases"][0]["id"].string $0["releases"][0]["id"].string
@ -45,7 +53,7 @@ extension AlbumArtService {
) )
}.recover { error -> Promise<NSImage> in }.recover { error -> Promise<NSImage> in
if case PMKHTTPError.badStatusCode(404, _, _) = error { if case PMKHTTPError.badStatusCode(404, _, _) = error {
throw MusicBrainzError.noArtworkAvailable throw RemoteArtworkError.noArtworkAvailable
} else { } else {
throw error throw error
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 KiB

After

Width:  |  Height:  |  Size: 795 KiB