Compare commits
34 Commits
bd00dcf576
...
c406737ec1
| Author | SHA1 | Date | |
|---|---|---|---|
| c406737ec1 | |||
| 55c543411e | |||
| 4b43d661c6 | |||
| 0db3cbe137 | |||
| 560a3bf14f | |||
| 9eacd2b25a | |||
| 92ca7ea93f | |||
| b43afb405f | |||
| 79ad585530 | |||
| 8983106cda | |||
| 0147ffbfda | |||
| ce5b0be2e1 | |||
| 5672ded50a | |||
| 11be238788 | |||
| 9714aabb10 | |||
| 9123a25bc7 | |||
| e8b58b7686 | |||
| 280ec0cdc4 | |||
| 487e0cc2c2 | |||
| 9517abf319 | |||
| fe748e2c61 | |||
| 537a66d6aa | |||
| 88aa765e83 | |||
| b1f3e6d399 | |||
| 592cd73ec7 | |||
| 5e97bfc42b | |||
| 02a68ba539 | |||
| 38431702d2 | |||
| 4ff0ff6e9b | |||
| b91cb50f4e | |||
| 51bc2c9adf | |||
| 8afe0a15fa | |||
| 3b7bdc7983 | |||
| 480c2786ad |
27
CHANGELOG.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 0.10.2a - 2019-03-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Will fetch album art from the filesystem or MusicBrainz
|
||||||
|
- Menu option to update the database
|
||||||
|
- Album art preferences icon
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Refactored command queue to be more reliable
|
||||||
|
- Resizing the windows no longer makes the album view jump around
|
||||||
|
- Fixed a crash when another client clears the queue
|
||||||
|
|
||||||
|
## 0.8.0a - 2019-02-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Connects to MPD
|
||||||
|
- Lists all albums
|
||||||
|
- Will play an album
|
||||||
|
- Queue is working
|
||||||
|
- Transport controls are working
|
||||||
|
- Seek bar is working
|
||||||
|
- Media keys work
|
||||||
2
Cartfile
@ -1 +1,3 @@
|
|||||||
|
github "SwiftyJSON/SwiftyJSON" ~> 4.0
|
||||||
|
github "PromiseKit/Foundation" ~> 3.0
|
||||||
github "nhurden/MediaKeyTap" "fix-tis-tsm-error"
|
github "nhurden/MediaKeyTap" "fix-tis-tsm-error"
|
||||||
|
|||||||
@ -1 +1,4 @@
|
|||||||
|
github "PromiseKit/Foundation" "3.3.1"
|
||||||
|
github "SwiftyJSON/SwiftyJSON" "4.2.0"
|
||||||
|
github "mxcl/PromiseKit" "6.8.3"
|
||||||
github "nhurden/MediaKeyTap" "355d346c56243e6d56487fa46fcad945251e16ae"
|
github "nhurden/MediaKeyTap" "355d346c56243e6d56487fa46fcad945251e16ae"
|
||||||
|
|||||||
@ -16,29 +16,60 @@
|
|||||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; };
|
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; };
|
||||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; };
|
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; };
|
||||||
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
|
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
|
||||||
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumItem.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 */; };
|
||||||
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 */; };
|
||||||
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */; };
|
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FC223BF87300173814 /* MPDClient+Connection.swift */; };
|
||||||
|
E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */; };
|
||||||
|
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5300223BF99300173814 /* MPDClient+Queue.swift */; };
|
||||||
|
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5302223BF9C300173814 /* MPDClient+Idle.swift */; };
|
||||||
|
E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5304223BFB0700173814 /* MPDClient+Error.swift */; };
|
||||||
|
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; };
|
||||||
|
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; };
|
||||||
|
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; };
|
||||||
|
E41E530E223EF4CF00173814 /* AlbumArtService+Caching.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */; };
|
||||||
|
E41E5310223EF6CE00173814 /* AlbumArtService+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */; };
|
||||||
|
E41E5312223EF74A00173814 /* AlbumArtService+Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */; };
|
||||||
|
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
|
||||||
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; };
|
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; };
|
||||||
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
|
||||||
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
|
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
|
||||||
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 */; };
|
||||||
|
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, ); }; };
|
||||||
|
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */; };
|
||||||
|
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */; };
|
||||||
|
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; };
|
||||||
|
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 */; };
|
||||||
|
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 */; };
|
||||||
|
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, ); }; };
|
||||||
|
E45962C62241A78500FC1A1E /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* Command.swift */; };
|
||||||
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 */; };
|
||||||
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD222205D2500F747E6 /* MainWindow.swift */; };
|
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD222205D2500F747E6 /* MainWindow.swift */; };
|
||||||
E47E2FD5222071FD00F747E6 /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD4222071FD00F747E6 /* AlbumItem.swift */; };
|
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */; };
|
||||||
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD62220720300F747E6 /* AlbumItemView.swift */; };
|
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD62220720300F747E6 /* AlbumItemView.swift */; };
|
||||||
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 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; };
|
||||||
|
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */; };
|
||||||
|
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
|
||||||
|
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* AlbumArtService.swift */; };
|
||||||
|
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
|
||||||
|
E4C8B53E22349002009A20F3 /* Idle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* Idle.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 */; };
|
||||||
@ -75,7 +106,10 @@
|
|||||||
dstSubfolderSpec = 10;
|
dstSubfolderSpec = 10;
|
||||||
files = (
|
files = (
|
||||||
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */,
|
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */,
|
||||||
|
E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */,
|
||||||
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */,
|
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */,
|
||||||
|
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */,
|
||||||
|
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */,
|
||||||
);
|
);
|
||||||
name = "Embed Libraries";
|
name = "Embed Libraries";
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -108,7 +142,7 @@
|
|||||||
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = "<group>"; };
|
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = "<group>"; };
|
||||||
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; };
|
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; };
|
||||||
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; };
|
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; };
|
||||||
E408D3C9220E341D0006D9BE /* AlbumItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumItem.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>"; };
|
||||||
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>"; };
|
||||||
@ -149,22 +183,52 @@
|
|||||||
E41B22E921FB966C00D544F6 /* capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capabilities.h; sourceTree = "<group>"; };
|
E41B22E921FB966C00D544F6 /* capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capabilities.h; sourceTree = "<group>"; };
|
||||||
E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; };
|
E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; };
|
||||||
E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; };
|
E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; };
|
||||||
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
|
E41E52FC223BF87300173814 /* MPDClient+Connection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Connection.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Transport.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E5300223BF99300173814 /* MPDClient+Queue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Queue.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Idle.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E5304223BFB0700173814 /* MPDClient+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Error.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumArtService+Caching.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumArtService+Remote.swift"; sourceTree = "<group>"; };
|
||||||
|
E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumArtService+Filesystem.swift"; sourceTree = "<group>"; };
|
||||||
|
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
|
||||||
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = "<group>"; };
|
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = "<group>"; };
|
||||||
|
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
|
||||||
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
|
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
|
||||||
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
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>"; };
|
||||||
|
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>"; };
|
||||||
|
E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = "<group>"; };
|
||||||
|
E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = SwiftyJSON.framework.dSYM; path = Carthage/Build/Mac/SwiftyJSON.framework.dSYM; 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>"; };
|
||||||
|
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>"; };
|
||||||
|
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>"; };
|
||||||
|
E45962C52241A78500FC1A1E /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; 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>"; };
|
||||||
E47E2FD222205D2500F747E6 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
|
E47E2FD222205D2500F747E6 /* MainWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindow.swift; sourceTree = "<group>"; };
|
||||||
E47E2FD4222071FD00F747E6 /* AlbumItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; };
|
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewItem.swift; sourceTree = "<group>"; };
|
||||||
E47E2FD62220720300F747E6 /* AlbumItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItemView.swift; sourceTree = "<group>"; };
|
E47E2FD62220720300F747E6 /* AlbumItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItemView.swift; sourceTree = "<group>"; };
|
||||||
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 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.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>"; };
|
||||||
|
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>"; };
|
||||||
|
E4C8B53D22349002009A20F3 /* Idle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Idle.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>"; };
|
||||||
@ -183,6 +247,9 @@
|
|||||||
files = (
|
files = (
|
||||||
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 */,
|
||||||
|
E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */,
|
||||||
|
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -229,6 +296,10 @@
|
|||||||
E407861A2110CE6E006887B1 /* Persephone */ = {
|
E407861A2110CE6E006887B1 /* Persephone */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */,
|
||||||
|
E450AD8922262B420091BED3 /* Operations */,
|
||||||
|
E4A83BF2222207BE0098FED6 /* Services */,
|
||||||
|
E4A83BEC2221F5DD0098FED6 /* Preferences */,
|
||||||
E47E2FE32220AA0700F747E6 /* Layouts */,
|
E47E2FE32220AA0700F747E6 /* Layouts */,
|
||||||
E4F6B461221E124700ACF42A /* Models */,
|
E4F6B461221E124700ACF42A /* Models */,
|
||||||
E4F6B45E221E117600ACF42A /* DataSources */,
|
E4F6B45E221E117600ACF42A /* DataSources */,
|
||||||
@ -273,6 +344,7 @@
|
|||||||
E40FE71A221B904300A4223F /* NSEvent.swift */,
|
E40FE71A221B904300A4223F /* NSEvent.swift */,
|
||||||
E435E3E1221CD4E200184CFC /* NSFont.swift */,
|
E435E3E1221CD4E200184CFC /* NSFont.swift */,
|
||||||
E435E3E3221CD75D00184CFC /* NSImage.swift */,
|
E435E3E3221CD75D00184CFC /* NSImage.swift */,
|
||||||
|
E450AD9C2229B9050091BED3 /* String.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -280,6 +352,15 @@
|
|||||||
E408D3BC220E03D20006D9BE /* Extensions */ = {
|
E408D3BC220E03D20006D9BE /* Extensions */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E41E52FC223BF87300173814 /* MPDClient+Connection.swift */,
|
||||||
|
E42410B52241B956005ED6DF /* MPDClient+Database.swift */,
|
||||||
|
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
|
||||||
|
E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
|
||||||
|
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
|
||||||
|
E41E5304223BFB0700173814 /* MPDClient+Error.swift */,
|
||||||
|
E41E5306223C019100173814 /* MPDClient+Status.swift */,
|
||||||
|
E41E5308223C020400173814 /* MPDClient+Command.swift */,
|
||||||
|
E41E530A223C033700173814 /* MPDClient+Album.swift */,
|
||||||
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
|
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
@ -297,8 +378,16 @@
|
|||||||
E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
|
E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */,
|
E450ADA02229E7C90091BED3 /* PMKFoundation.framework */,
|
||||||
|
E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */,
|
||||||
|
E450AD97222633920091BED3 /* Alamofire.framework */,
|
||||||
|
E450AD96222633920091BED3 /* Alamofire.framework.dSYM */,
|
||||||
|
E450AD8C22262C590091BED3 /* PromiseKit.framework */,
|
||||||
|
E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */,
|
||||||
|
E450AD8522262AE60091BED3 /* SwiftyJSON.framework */,
|
||||||
|
E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */,
|
||||||
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */,
|
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */,
|
||||||
|
E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */,
|
||||||
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */,
|
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
@ -355,6 +444,24 @@
|
|||||||
path = mpd;
|
path = mpd;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E41E530C223EF4BA00173814 /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */,
|
||||||
|
E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */,
|
||||||
|
E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */,
|
||||||
|
);
|
||||||
|
path = Extensions;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E450AD8922262B420091BED3 /* Operations */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */,
|
||||||
|
);
|
||||||
|
path = Operations;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E47E2FE32220AA0700F747E6 /* Layouts */ = {
|
E47E2FE32220AA0700F747E6 /* Layouts */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -366,14 +473,42 @@
|
|||||||
E4A642DB220912FA00067D21 /* MPDClient */ = {
|
E4A642DB220912FA00067D21 /* MPDClient */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E408D3BC220E03D20006D9BE /* Extensions */,
|
|
||||||
E41B22C521FB932700D544F6 /* MPDClient.swift */,
|
E41B22C521FB932700D544F6 /* MPDClient.swift */,
|
||||||
|
E408D3BC220E03D20006D9BE /* Extensions */,
|
||||||
E4D1B595220BA27C0026F233 /* Protocols */,
|
E4D1B595220BA27C0026F233 /* Protocols */,
|
||||||
E4D1B594220BA2490026F233 /* Models */,
|
E4D1B594220BA2490026F233 /* Models */,
|
||||||
);
|
);
|
||||||
path = MPDClient;
|
path = MPDClient;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E4A83BEC2221F5DD0098FED6 /* Preferences */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E4A83BED2221F5E60098FED6 /* Controllers */,
|
||||||
|
);
|
||||||
|
path = Preferences;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E4A83BED2221F5E60098FED6 /* Controllers */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */,
|
||||||
|
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */,
|
||||||
|
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */,
|
||||||
|
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */,
|
||||||
|
);
|
||||||
|
path = Controllers;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E4A83BF2222207BE0098FED6 /* Services */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E41E530C223EF4BA00173814 /* Extensions */,
|
||||||
|
E4A83BF3222207D50098FED6 /* AlbumArtService.swift */,
|
||||||
|
);
|
||||||
|
path = Services;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E4D1B594220BA2490026F233 /* Models */ = {
|
E4D1B594220BA2490026F233 /* Models */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -381,6 +516,8 @@
|
|||||||
E4A642D922090CBE00067D21 /* Status.swift */,
|
E4A642D922090CBE00067D21 /* Status.swift */,
|
||||||
E4EB2378220F10B8008C70C0 /* Pair.swift */,
|
E4EB2378220F10B8008C70C0 /* Pair.swift */,
|
||||||
E4EB237A220F7CF1008C70C0 /* Album.swift */,
|
E4EB237A220F7CF1008C70C0 /* Album.swift */,
|
||||||
|
E4C8B53D22349002009A20F3 /* Idle.swift */,
|
||||||
|
E45962C52241A78500FC1A1E /* Command.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -396,9 +533,8 @@
|
|||||||
E4D1B597220BA3A20026F233 /* Controllers */ = {
|
E4D1B597220BA3A20026F233 /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E47E2FD4222071FD00F747E6 /* AlbumItem.swift */,
|
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
|
||||||
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
|
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
|
||||||
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */,
|
|
||||||
E4E8CC932206097F0024217A /* NotificationsController.swift */,
|
E4E8CC932206097F0024217A /* NotificationsController.swift */,
|
||||||
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
|
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
|
||||||
E465049921E94DF500A70F4C /* WindowController.swift */,
|
E465049921E94DF500A70F4C /* WindowController.swift */,
|
||||||
@ -411,7 +547,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E40786212110CE70006887B1 /* Main.storyboard */,
|
E40786212110CE70006887B1 /* Main.storyboard */,
|
||||||
E408D3C9220E341D0006D9BE /* AlbumItem.xib */,
|
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */,
|
||||||
);
|
);
|
||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -431,6 +567,7 @@
|
|||||||
E47E2FDC2220A6D100F747E6 /* Time.swift */,
|
E47E2FDC2220A6D100F747E6 /* Time.swift */,
|
||||||
E40F41F2221EDE27004B6CB8 /* Preferences.swift */,
|
E40F41F2221EDE27004B6CB8 /* Preferences.swift */,
|
||||||
E4F6B462221E125900ACF42A /* SongItem.swift */,
|
E4F6B462221E125900ACF42A /* SongItem.swift */,
|
||||||
|
E450AD7D222620A10091BED3 /* AlbumItem.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -550,11 +687,15 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */,
|
||||||
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
|
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
|
||||||
|
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */,
|
||||||
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
|
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
|
||||||
E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
|
E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
|
||||||
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */,
|
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */,
|
||||||
|
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */,
|
||||||
E40786232110CE70006887B1 /* Main.storyboard in Resources */,
|
E40786232110CE70006887B1 /* Main.storyboard in Resources */,
|
||||||
|
E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */,
|
||||||
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */,
|
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -600,34 +741,55 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */,
|
||||||
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
|
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
|
||||||
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 */,
|
||||||
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */,
|
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */,
|
||||||
|
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
|
||||||
|
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
|
||||||
|
E41E5310223EF6CE00173814 /* AlbumArtService+Remote.swift in Sources */,
|
||||||
|
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
|
||||||
|
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
|
||||||
E4A642DA22090CBE00067D21 /* Status.swift in Sources */,
|
E4A642DA22090CBE00067D21 /* Status.swift in Sources */,
|
||||||
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
|
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
|
||||||
|
E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */,
|
||||||
|
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */,
|
||||||
|
E450AD7E222620A10091BED3 /* AlbumItem.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 */,
|
||||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
||||||
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
|
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
|
||||||
E4F6B463221E125900ACF42A /* SongItem.swift in Sources */,
|
E4F6B463221E125900ACF42A /* SongItem.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 */,
|
||||||
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
|
E407861C2110CE6E006887B1 /* AppDelegate.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 */,
|
||||||
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 /* Song.swift in Sources */,
|
||||||
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */,
|
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
|
||||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
||||||
E47E2FD5222071FD00F747E6 /* AlbumItem.swift in Sources */,
|
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */,
|
||||||
|
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
|
||||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
||||||
|
E41E530E223EF4CF00173814 /* AlbumArtService+Caching.swift in Sources */,
|
||||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
||||||
|
E41E5312223EF74A00173814 /* AlbumArtService+Filesystem.swift in Sources */,
|
||||||
|
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
|
||||||
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */,
|
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */,
|
||||||
|
E450AD9D2229B9050091BED3 /* String.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 */,
|
||||||
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
|
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -731,6 +893,7 @@
|
|||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = Persephone/PersephoneBridgingHeader.h;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
@ -784,6 +947,7 @@
|
|||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
SWIFT_COMPILATION_MODE = wholemodule;
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = Persephone/PersephoneBridgingHeader.h;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Workspace
|
<Workspace
|
||||||
version = "1.0">
|
version = "1.0">
|
||||||
<FileRef
|
|
||||||
location = "group:../README.md">
|
|
||||||
</FileRef>
|
|
||||||
<FileRef
|
<FileRef
|
||||||
location = "self:">
|
location = "self:">
|
||||||
</FileRef>
|
</FileRef>
|
||||||
|
|||||||
@ -26,6 +26,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
|
|||||||
|
|
||||||
mediaKeyTap = MediaKeyTap(delegate: self)
|
mediaKeyTap = MediaKeyTap(delegate: self)
|
||||||
mediaKeyTap?.start()
|
mediaKeyTap?.start()
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(enableUpdateDatabaseMenuItem),
|
||||||
|
name: Notification.databaseUpdateFinished,
|
||||||
|
object: AppDelegate.mpdClient
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillTerminate(_ aNotification: Notification) {
|
func applicationWillTerminate(_ aNotification: Notification) {
|
||||||
@ -68,4 +75,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
|
|||||||
func disconnect() {
|
func disconnect() {
|
||||||
AppDelegate.mpdClient.disconnect()
|
AppDelegate.mpdClient.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func updateDatabase(_ sender: NSMenuItem) {
|
||||||
|
sender.isEnabled = false
|
||||||
|
AppDelegate.mpdClient.updateDatabase()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func enableUpdateDatabaseMenuItem() {
|
||||||
|
updateDatabaseMenuItem?.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
|
||||||
}
|
}
|
||||||
|
|||||||
22
Persephone/Assets.xcassets/coverArtPreferencesIcon.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "coverArtPreferencesIcon.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "coverArtPreferencesIcon@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Persephone/Assets.xcassets/coverArtPreferencesIcon.imageset/coverArtPreferencesIcon.png
vendored
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Persephone/Assets.xcassets/coverArtPreferencesIcon.imageset/coverArtPreferencesIcon@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
@ -11,6 +11,8 @@ import Cocoa
|
|||||||
class AlbumViewController: NSViewController,
|
class AlbumViewController: NSViewController,
|
||||||
NSCollectionViewDelegate,
|
NSCollectionViewDelegate,
|
||||||
NSCollectionViewDelegateFlowLayout {
|
NSCollectionViewDelegateFlowLayout {
|
||||||
|
var preferences = Preferences()
|
||||||
|
|
||||||
let paddingWidth: CGFloat = 40
|
let paddingWidth: CGFloat = 40
|
||||||
let gutterWidth: CGFloat = 20
|
let gutterWidth: CGFloat = 20
|
||||||
|
|
||||||
@ -36,6 +38,8 @@ class AlbumViewController: NSViewController,
|
|||||||
)
|
)
|
||||||
|
|
||||||
albumCollectionView.dataSource = dataSource
|
albumCollectionView.dataSource = dataSource
|
||||||
|
|
||||||
|
preferences.addObserver(self, forKeyPath: "mpdLibraryDir")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillLayout() {
|
override func viewWillLayout() {
|
||||||
@ -53,11 +57,25 @@ class AlbumViewController: NSViewController,
|
|||||||
layout.setScrollPosition()
|
layout.setScrollPosition()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func observeValue(
|
||||||
|
forKeyPath keyPath: String?,
|
||||||
|
of object: Any?,
|
||||||
|
change: [NSKeyValueChangeKey : Any]?,
|
||||||
|
context: UnsafeMutableRawPointer?
|
||||||
|
) {
|
||||||
|
switch keyPath {
|
||||||
|
case "mpdLibraryDir":
|
||||||
|
albumCollectionView.reloadData()
|
||||||
|
default:
|
||||||
|
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.Album]
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
dataSource.albums = albums
|
dataSource.albums = albums.map { AlbumItem(album: $0, coverArt: nil) }
|
||||||
albumCollectionView.reloadData()
|
albumCollectionView.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// AlbumItem.swift
|
// AlbumViewItem.swift
|
||||||
// Persephone
|
// Persephone
|
||||||
//
|
//
|
||||||
// Created by Daniel Barber on 2019/2/08.
|
// Created by Daniel Barber on 2019/2/08.
|
||||||
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class AlbumItem: NSCollectionViewItem {
|
class AlbumViewItem: NSCollectionViewItem {
|
||||||
var observer: NSKeyValueObservation?
|
var observer: NSKeyValueObservation?
|
||||||
var album: MPDClient.Album?
|
var album: AlbumItem?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
@ -27,10 +27,16 @@ class AlbumItem: NSCollectionViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setAlbum(_ album: MPDClient.Album) {
|
func setAlbum(_ album: AlbumItem) {
|
||||||
self.album = album
|
self.album = album
|
||||||
albumTitle.stringValue = album.title
|
albumTitle.stringValue = album.title
|
||||||
albumArtist.stringValue = album.artist
|
albumArtist.stringValue = album.artist
|
||||||
|
|
||||||
|
if let coverArt = album.coverArt {
|
||||||
|
albumCoverView.image = coverArt
|
||||||
|
} else {
|
||||||
|
albumCoverView.image = .defaultCoverArt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setAppearance() {
|
func setAppearance() {
|
||||||
@ -47,7 +53,7 @@ class AlbumItem: 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)
|
AppDelegate.mpdClient.playAlbum(album.album)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBOutlet var albumCoverView: NSImageView!
|
@IBOutlet var albumCoverView: NSImageView!
|
||||||
@ -36,6 +36,14 @@ class NotificationsController: MPDClientDelegate {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func willStartDatabaseUpdate(mpdClient: MPDClient) {
|
||||||
|
sendNotification(name: Notification.databaseUpdateStarted)
|
||||||
|
}
|
||||||
|
|
||||||
|
func didFinishDatabaseUpdate(mpdClient: MPDClient) {
|
||||||
|
sendNotification(name: Notification.databaseUpdateFinished)
|
||||||
|
}
|
||||||
|
|
||||||
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) {
|
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) {
|
||||||
sendNotification(
|
sendNotification(
|
||||||
name: Notification.queueChanged,
|
name: Notification.queueChanged,
|
||||||
|
|||||||
@ -36,7 +36,7 @@ class QueueViewController: NSViewController,
|
|||||||
let newQueuePos = queueView.selectedRow - 1
|
let newQueuePos = queueView.selectedRow - 1
|
||||||
|
|
||||||
if newQueuePos >= 0 {
|
if newQueuePos >= 0 {
|
||||||
AppDelegate.mpdClient.playTrack(queuePos: newQueuePos)
|
AppDelegate.mpdClient.playTrack(at: newQueuePos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,20 @@ class WindowController: NSWindowController {
|
|||||||
object: AppDelegate.mpdClient
|
object: AppDelegate.mpdClient
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(startDatabaseUpdatingIndicator),
|
||||||
|
name: Notification.databaseUpdateStarted,
|
||||||
|
object: AppDelegate.mpdClient
|
||||||
|
)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(
|
||||||
|
self,
|
||||||
|
selector: #selector(stopDatabaseUpdatingIndicator),
|
||||||
|
name: Notification.databaseUpdateFinished,
|
||||||
|
object: AppDelegate.mpdClient
|
||||||
|
)
|
||||||
|
|
||||||
trackProgress.font = .timerFont
|
trackProgress.font = .timerFont
|
||||||
trackRemaining.font = .timerFont
|
trackRemaining.font = .timerFont
|
||||||
}
|
}
|
||||||
@ -132,6 +146,14 @@ class WindowController: NSWindowController {
|
|||||||
setTimeRemaining()
|
setTimeRemaining()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc func startDatabaseUpdatingIndicator() {
|
||||||
|
databaseUpdatingIndicator.startAnimation(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func stopDatabaseUpdatingIndicator() {
|
||||||
|
databaseUpdatingIndicator.stopAnimation(self)
|
||||||
|
}
|
||||||
|
|
||||||
func setTimeElapsed() {
|
func setTimeElapsed() {
|
||||||
guard let elapsedTimeMs = elapsedTimeMs else { return }
|
guard let elapsedTimeMs = elapsedTimeMs else { return }
|
||||||
|
|
||||||
@ -194,4 +216,5 @@ class WindowController: NSWindowController {
|
|||||||
@IBOutlet var trackProgress: NSTextField!
|
@IBOutlet var trackProgress: NSTextField!
|
||||||
@IBOutlet var trackProgressBar: NSSlider!
|
@IBOutlet var trackProgressBar: NSSlider!
|
||||||
@IBOutlet var trackRemaining: NSTextField!
|
@IBOutlet var trackRemaining: NSTextField!
|
||||||
|
@IBOutlet var databaseUpdatingIndicator: NSProgressIndicator!
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,19 +9,29 @@
|
|||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class AlbumDataSource: NSObject, NSCollectionViewDataSource {
|
class AlbumDataSource: NSObject, NSCollectionViewDataSource {
|
||||||
var albums: [MPDClient.Album] = []
|
var albums: [AlbumItem] = []
|
||||||
|
|
||||||
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
return albums.count
|
return albums.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
|
||||||
let item = collectionView.makeItem(withIdentifier: .albumItem, for: indexPath)
|
let item = collectionView.makeItem(withIdentifier: .albumViewItem, for: indexPath)
|
||||||
guard let albumItem = item as? AlbumItem else { return item }
|
guard let albumViewItem = item as? AlbumViewItem else { return item }
|
||||||
|
|
||||||
albumItem.view.wantsLayer = true
|
albumViewItem.view.wantsLayer = true
|
||||||
albumItem.setAlbum(albums[indexPath.item])
|
albumViewItem.setAlbum(albums[indexPath.item])
|
||||||
|
|
||||||
return albumItem
|
if albums[indexPath.item].coverArt == nil {
|
||||||
|
AlbumArtService(album: albums[indexPath.item]).fetchAlbumArt { image in
|
||||||
|
self.albums[indexPath.item].coverArt = image
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
collectionView.reloadItems(at: [indexPath])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return albumViewItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,6 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
|
|||||||
|
|
||||||
var queueIcon: NSImage? = nil
|
var queueIcon: NSImage? = nil
|
||||||
|
|
||||||
let playIcon = NSImage(named: "playButton")
|
|
||||||
let pauseIcon = NSImage(named: "pauseButton")
|
|
||||||
|
|
||||||
func updateQueue(_ queue: [MPDClient.Song]) {
|
func updateQueue(_ queue: [MPDClient.Song]) {
|
||||||
queuePos = -1
|
queuePos = -1
|
||||||
|
|
||||||
@ -41,9 +38,9 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
|
|||||||
func setQueueIcon(_ state: MPDClient.Status.State) {
|
func setQueueIcon(_ state: MPDClient.Status.State) {
|
||||||
switch state {
|
switch state {
|
||||||
case .playing:
|
case .playing:
|
||||||
queueIcon = playIcon
|
queueIcon = .playIcon
|
||||||
case .paused:
|
case .paused:
|
||||||
queueIcon = pauseIcon
|
queueIcon = .pauseIcon
|
||||||
default:
|
default:
|
||||||
queueIcon = nil
|
queueIcon = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,4 +11,26 @@ import Cocoa
|
|||||||
extension NSImage {
|
extension NSImage {
|
||||||
static let playIcon = NSImage(named: "playButton")
|
static let playIcon = NSImage(named: "playButton")
|
||||||
static let pauseIcon = NSImage(named: "pauseButton")
|
static let pauseIcon = NSImage(named: "pauseButton")
|
||||||
|
|
||||||
|
static let defaultCoverArt = NSImage(named: "blankAlbum")
|
||||||
|
|
||||||
|
func toFitBox(size: NSSize) -> NSImage {
|
||||||
|
let newImage = NSImage(size: size)
|
||||||
|
newImage.lockFocus()
|
||||||
|
self.draw(in: newImage.alignmentRect)
|
||||||
|
newImage.unlockFocus()
|
||||||
|
return newImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func jpegData(compressionQuality: CGFloat) -> Data? {
|
||||||
|
guard let image = cgImage(forProposedRect: nil, context: nil, hints: nil)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
let bitmapImageRep = NSBitmapImageRep(cgImage: image)
|
||||||
|
|
||||||
|
return bitmapImageRep.representation(
|
||||||
|
using: .jpeg,
|
||||||
|
properties: [.compressionFactor: compressionQuality]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,5 +16,5 @@ extension NSUserInterfaceItemIdentifier {
|
|||||||
static let queueSongArtist = NSUserInterfaceItemIdentifier("songArtistCell")
|
static let queueSongArtist = NSUserInterfaceItemIdentifier("songArtistCell")
|
||||||
static let queueSongTitle = NSUserInterfaceItemIdentifier("songTitleCell")
|
static let queueSongTitle = NSUserInterfaceItemIdentifier("songTitleCell")
|
||||||
|
|
||||||
static let albumItem = NSUserInterfaceItemIdentifier("AlbumItem")
|
static let albumViewItem = NSUserInterfaceItemIdentifier("AlbumViewItem")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ extension Notification {
|
|||||||
|
|
||||||
static let stateChanged = Name("MPDClientStateChanged")
|
static let stateChanged = Name("MPDClientStateChanged")
|
||||||
static let timeChanged = Name("MPDClientTimeChanged")
|
static let timeChanged = Name("MPDClientTimeChanged")
|
||||||
|
static let databaseUpdateStarted = Name("MPDClientDatabaseUpdateStarted")
|
||||||
|
static let databaseUpdateFinished = Name("MPDClientDatabaseUpdateFinished")
|
||||||
static let queueChanged = Name("MPDClientQueueChanged")
|
static let queueChanged = Name("MPDClientQueueChanged")
|
||||||
static let queuePosChanged = Name("MPDClientQueuePosChanged")
|
static let queuePosChanged = Name("MPDClientQueuePosChanged")
|
||||||
static let loadedAlbums = Name("MPDClientLoadedAlbums")
|
static let loadedAlbums = Name("MPDClientLoadedAlbums")
|
||||||
|
|||||||
24
Persephone/Extensions/String.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.8.0</string>
|
<string>0.10.2a</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>1</string>
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>LSApplicationCategoryType</key>
|
||||||
|
|||||||
99
Persephone/MPDClient/Extensions/MPDClient+Album.swift
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
//
|
||||||
|
// MPDAlbum.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func fetchAllAlbums() {
|
||||||
|
enqueueCommand(command: .fetchAllAlbums)
|
||||||
|
}
|
||||||
|
|
||||||
|
func playAlbum(_ album: Album) {
|
||||||
|
enqueueCommand(command: .playAlbum, userData: ["album": album])
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAlbumURI(for album: Album, callback: @escaping (String?) -> Void) {
|
||||||
|
enqueueCommand(
|
||||||
|
command: .getAlbumURI,
|
||||||
|
priority: .low,
|
||||||
|
userData: ["album": album, "callback": callback]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPlayAlbum(_ album: Album) {
|
||||||
|
var songs: [Song] = []
|
||||||
|
|
||||||
|
mpd_run_clear(self.connection)
|
||||||
|
mpd_search_db_songs(self.connection, true)
|
||||||
|
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
|
||||||
|
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
|
||||||
|
mpd_search_commit(self.connection)
|
||||||
|
while let mpdSong = mpd_recv_song(self.connection) {
|
||||||
|
songs.append(Song(mpdSong))
|
||||||
|
}
|
||||||
|
for song in songs {
|
||||||
|
mpd_run_add(self.connection, song.uri)
|
||||||
|
}
|
||||||
|
mpd_run_play_pos(self.connection, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allAlbums() {
|
||||||
|
var albums: [Album] = []
|
||||||
|
var artist: String = ""
|
||||||
|
|
||||||
|
mpd_search_db_tags(self.connection, MPD_TAG_ALBUM)
|
||||||
|
mpd_search_add_group_tag(self.connection, MPD_TAG_ALBUM_ARTIST)
|
||||||
|
mpd_search_commit(self.connection)
|
||||||
|
|
||||||
|
while let mpdPair = mpd_recv_pair(self.connection) {
|
||||||
|
let pair = Pair(mpdPair)
|
||||||
|
|
||||||
|
switch pair.name {
|
||||||
|
case "AlbumArtist":
|
||||||
|
artist = pair.value
|
||||||
|
case "Album":
|
||||||
|
albums.append(Album(title: pair.value, artist: artist))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mpd_return_pair(self.connection, pair.mpdPair)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.delegate?.didLoadAlbums(mpdClient: self, albums: albums)
|
||||||
|
}
|
||||||
|
|
||||||
|
func albumURI(for album: Album, callback: (String?) -> Void) {
|
||||||
|
var songURI: String?
|
||||||
|
|
||||||
|
guard isConnected else { return }
|
||||||
|
|
||||||
|
mpd_search_db_songs(self.connection, true)
|
||||||
|
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
|
||||||
|
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
|
||||||
|
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_TRACK, "1")
|
||||||
|
|
||||||
|
mpd_search_commit(self.connection)
|
||||||
|
|
||||||
|
while let mpdSong = mpd_recv_song(self.connection) {
|
||||||
|
let song = Song(mpdSong)
|
||||||
|
|
||||||
|
if songURI == nil {
|
||||||
|
songURI = song.uriString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(
|
||||||
|
songURI?
|
||||||
|
.split(separator: "/")
|
||||||
|
.dropLast()
|
||||||
|
.joined(separator: "/")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
79
Persephone/MPDClient/Extensions/MPDClient+Command.swift
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
//
|
||||||
|
// CommandQueue.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func sendCommand(
|
||||||
|
command: Command,
|
||||||
|
userData: Dictionary<String, Any> = [:]
|
||||||
|
) {
|
||||||
|
switch command {
|
||||||
|
|
||||||
|
// Transport commands
|
||||||
|
case .prevTrack:
|
||||||
|
sendPreviousTrack()
|
||||||
|
case .nextTrack:
|
||||||
|
sendNextTrack()
|
||||||
|
case .stop:
|
||||||
|
sendStop()
|
||||||
|
case .playPause:
|
||||||
|
sendPlay()
|
||||||
|
case .seekCurrentSong:
|
||||||
|
guard let timeInSeconds = userData["timeInSeconds"] as? Float
|
||||||
|
else { return }
|
||||||
|
sendSeekCurrentSong(timeInSeconds: timeInSeconds)
|
||||||
|
|
||||||
|
// Database commands
|
||||||
|
case .updateDatabase:
|
||||||
|
sendUpdateDatabase()
|
||||||
|
|
||||||
|
// Status commands
|
||||||
|
case .fetchStatus:
|
||||||
|
sendRunStatus()
|
||||||
|
|
||||||
|
// Queue commands
|
||||||
|
case .fetchQueue:
|
||||||
|
sendFetchQueue()
|
||||||
|
case .playTrack:
|
||||||
|
guard let queuePos = userData["queuePos"] as? Int
|
||||||
|
else { return }
|
||||||
|
sendPlayTrack(at: queuePos)
|
||||||
|
|
||||||
|
// Album commands
|
||||||
|
case .fetchAllAlbums:
|
||||||
|
allAlbums()
|
||||||
|
case .playAlbum:
|
||||||
|
guard let album = userData["album"] as? Album else { return }
|
||||||
|
sendPlayAlbum(album)
|
||||||
|
case .getAlbumURI:
|
||||||
|
guard let album = userData["album"] as? Album,
|
||||||
|
let callback = userData["callback"] as? (String?) -> Void
|
||||||
|
else { return }
|
||||||
|
albumURI(for: album, callback: callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func enqueueCommand(
|
||||||
|
command: Command,
|
||||||
|
priority: BlockOperation.QueuePriority = .normal,
|
||||||
|
userData: Dictionary<String, Any> = [:]
|
||||||
|
) {
|
||||||
|
guard isConnected else { return }
|
||||||
|
|
||||||
|
noIdle()
|
||||||
|
|
||||||
|
let commandOperation = BlockOperation() { [unowned self] in
|
||||||
|
self.sendCommand(command: command, userData: userData)
|
||||||
|
|
||||||
|
self.idle()
|
||||||
|
}
|
||||||
|
commandOperation.queuePriority = priority
|
||||||
|
commandQueue.addOperation(commandOperation)
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Persephone/MPDClient/Extensions/MPDClient+Connection.swift
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
//
|
||||||
|
// Connection.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func connect(host: String, port: Int) {
|
||||||
|
commandQueue.addOperation { [unowned self] in
|
||||||
|
guard let connection = mpd_connection_new(host, UInt32(port), 10000),
|
||||||
|
mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.isConnected = true
|
||||||
|
|
||||||
|
guard let status = mpd_run_status(connection)
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
self.connection = connection
|
||||||
|
self.status = Status(status)
|
||||||
|
|
||||||
|
self.fetchQueue()
|
||||||
|
self.fetchAllAlbums()
|
||||||
|
self.idle()
|
||||||
|
|
||||||
|
self.delegate?.didConnect(mpdClient: self)
|
||||||
|
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
|
||||||
|
self.delegate?.didUpdateTime(mpdClient: self, total: self.status!.totalTime, elapsedMs: self.status!.elapsedTimeMs)
|
||||||
|
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
|
||||||
|
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnect() {
|
||||||
|
guard isConnected else { return }
|
||||||
|
|
||||||
|
noIdle()
|
||||||
|
commandQueue.addOperation { [unowned self] in
|
||||||
|
self.delegate?.willDisconnect(mpdClient: self)
|
||||||
|
|
||||||
|
mpd_connection_free(self.connection)
|
||||||
|
self.isConnected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Persephone/MPDClient/Extensions/MPDClient+Database.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// MPDClient+Database.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/19.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func updateDatabase() {
|
||||||
|
enqueueCommand(command: .updateDatabase)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendUpdateDatabase() {
|
||||||
|
mpd_run_update(connection, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Persephone/MPDClient/Extensions/MPDClient+Error.swift
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
//
|
||||||
|
// Error.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func getLastErrorMessage() -> String? {
|
||||||
|
if mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let errorMessage = mpd_connection_get_error_message(connection) {
|
||||||
|
return String(cString: errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
64
Persephone/MPDClient/Extensions/MPDClient+Idle.swift
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// Idle.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func noIdle() {
|
||||||
|
if isIdle {
|
||||||
|
mpd_send_noidle(connection)
|
||||||
|
isIdle = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func idle() {
|
||||||
|
if !self.isIdle && self.commandQueue.operationCount == 1 {
|
||||||
|
mpd_send_idle(self.connection)
|
||||||
|
self.isIdle = true
|
||||||
|
|
||||||
|
let result = mpd_recv_idle(self.connection, true)
|
||||||
|
self.handleIdleResult(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleIdleResult(_ result: mpd_idle) {
|
||||||
|
isIdle = false
|
||||||
|
|
||||||
|
let mpdIdle = Idle(rawValue: result.rawValue)
|
||||||
|
|
||||||
|
if mpdIdle.contains(.database) {
|
||||||
|
self.fetchAllAlbums()
|
||||||
|
}
|
||||||
|
if mpdIdle.contains(.queue) {
|
||||||
|
self.fetchQueue()
|
||||||
|
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
|
||||||
|
}
|
||||||
|
if mpdIdle.contains(.player) {
|
||||||
|
self.fetchStatus()
|
||||||
|
|
||||||
|
if let status = self.status {
|
||||||
|
self.delegate?.didUpdateState(mpdClient: self, state: status.state)
|
||||||
|
self.delegate?.didUpdateTime(mpdClient: self, total: status.totalTime, elapsedMs: status.elapsedTimeMs)
|
||||||
|
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mpdIdle.contains(.update) {
|
||||||
|
self.fetchStatus()
|
||||||
|
|
||||||
|
if self.status?.updating ?? false {
|
||||||
|
self.delegate?.willStartDatabaseUpdate(mpdClient: self)
|
||||||
|
} else {
|
||||||
|
self.delegate?.didFinishDatabaseUpdate(mpdClient: self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !mpdIdle.isEmpty {
|
||||||
|
self.idle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Persephone/MPDClient/Extensions/MPDClient+Queue.swift
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Queue.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func fetchQueue() {
|
||||||
|
sendCommand(command: .fetchQueue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func playTrack(at queuePos: Int) {
|
||||||
|
enqueueCommand(command: .playTrack, userData: ["queuePos": queuePos])
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPlayTrack(at queuePos: Int) {
|
||||||
|
mpd_run_play_pos(self.connection, UInt32(queuePos))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendFetchQueue() {
|
||||||
|
self.queue = []
|
||||||
|
mpd_send_list_queue_meta(connection)
|
||||||
|
|
||||||
|
while let mpdSong = mpd_recv_song(connection) {
|
||||||
|
let song = Song(mpdSong)
|
||||||
|
self.queue.append(song)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Persephone/MPDClient/Extensions/MPDClient+Status.swift
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// Status.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func fetchStatus() {
|
||||||
|
sendCommand(command: .fetchStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendRunStatus() {
|
||||||
|
guard let status = mpd_run_status(connection) else { return }
|
||||||
|
self.status = Status(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
67
Persephone/MPDClient/Extensions/MPDClient+Transport.swift
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Transport.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/15.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import mpdclient
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
func playPause() {
|
||||||
|
enqueueCommand(command: .playPause)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stop() {
|
||||||
|
enqueueCommand(command: .stop)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prevTrack() {
|
||||||
|
enqueueCommand(command: .prevTrack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextTrack() {
|
||||||
|
enqueueCommand(command: .nextTrack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func seekCurrentSong(timeInSeconds: Float) {
|
||||||
|
enqueueCommand(
|
||||||
|
command: .seekCurrentSong,
|
||||||
|
userData: ["timeInSeconds": timeInSeconds]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendNextTrack() {
|
||||||
|
guard let state = status?.state,
|
||||||
|
state.isOneOf([.playing, .paused])
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
mpd_run_next(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPreviousTrack() {
|
||||||
|
guard let state = status?.state,
|
||||||
|
state.isOneOf([.playing, .paused])
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
mpd_run_previous(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendStop() {
|
||||||
|
mpd_run_stop(connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendPlay() {
|
||||||
|
if status?.state == .stopped {
|
||||||
|
mpd_run_play(connection)
|
||||||
|
} else {
|
||||||
|
mpd_run_toggle_pause(connection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendSeekCurrentSong(timeInSeconds: Float) {
|
||||||
|
mpd_run_seek_current(self.connection, timeInSeconds, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,285 +12,16 @@ import mpdclient
|
|||||||
class MPDClient {
|
class MPDClient {
|
||||||
var delegate: MPDClientDelegate?
|
var delegate: MPDClientDelegate?
|
||||||
|
|
||||||
private var connection: OpaquePointer?
|
var connection: OpaquePointer?
|
||||||
private var isConnected: Bool = false
|
var isConnected: Bool = false
|
||||||
private var status: Status?
|
var isIdle: Bool = false
|
||||||
private var queue: [Song] = []
|
var status: Status?
|
||||||
|
var queue: [Song] = []
|
||||||
|
|
||||||
private let commandQueue = DispatchQueue(label: "commandQueue")
|
let commandQueue = OperationQueue()
|
||||||
|
|
||||||
enum Command {
|
|
||||||
case prevTrack, nextTrack, playPause, stop,
|
|
||||||
fetchStatus, fetchQueue, fetchAllAlbums
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(withDelegate delegate: MPDClientDelegate?) {
|
init(withDelegate delegate: MPDClientDelegate?) {
|
||||||
|
commandQueue.maxConcurrentOperationCount = 1
|
||||||
self.delegate = delegate
|
self.delegate = delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
func connect(host: String, port: Int) {
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
guard let connection = mpd_connection_new(host, UInt32(port), 10000),
|
|
||||||
mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
self.isConnected = true
|
|
||||||
|
|
||||||
guard let status = mpd_run_status(connection)
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
self.connection = connection
|
|
||||||
self.status = Status(status)
|
|
||||||
|
|
||||||
self.fetchQueue()
|
|
||||||
self.fetchAllAlbums()
|
|
||||||
self.idle()
|
|
||||||
|
|
||||||
self.delegate?.didConnect(mpdClient: self)
|
|
||||||
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
|
|
||||||
self.delegate?.didUpdateTime(mpdClient: self, total: self.status!.totalTime, elapsedMs: self.status!.elapsedTimeMs)
|
|
||||||
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
|
|
||||||
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func disconnect() {
|
|
||||||
guard isConnected else { return }
|
|
||||||
|
|
||||||
noIdle()
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
self.delegate?.willDisconnect(mpdClient: self)
|
|
||||||
|
|
||||||
mpd_connection_free(self.connection)
|
|
||||||
self.isConnected = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchStatus() {
|
|
||||||
sendCommand(command: .fetchStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchQueue() {
|
|
||||||
sendCommand(command: .fetchQueue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchAllAlbums() {
|
|
||||||
sendCommand(command: .fetchAllAlbums)
|
|
||||||
}
|
|
||||||
|
|
||||||
func playPause() {
|
|
||||||
queueCommand(command: .playPause)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() {
|
|
||||||
queueCommand(command: .stop)
|
|
||||||
}
|
|
||||||
|
|
||||||
func prevTrack() {
|
|
||||||
queueCommand(command: .prevTrack)
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextTrack() {
|
|
||||||
queueCommand(command: .nextTrack)
|
|
||||||
}
|
|
||||||
|
|
||||||
func playTrack(queuePos: Int) {
|
|
||||||
guard isConnected else { return }
|
|
||||||
|
|
||||||
noIdle()
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
mpd_run_play_pos(self.connection, UInt32(queuePos))
|
|
||||||
}
|
|
||||||
idle()
|
|
||||||
}
|
|
||||||
|
|
||||||
func seekCurrentSong(timeInSeconds: Float) {
|
|
||||||
noIdle()
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
mpd_run_seek_current(self.connection, timeInSeconds, false)
|
|
||||||
}
|
|
||||||
idle()
|
|
||||||
}
|
|
||||||
|
|
||||||
func playAlbum(_ album: Album) {
|
|
||||||
noIdle()
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
var songs: [Song] = []
|
|
||||||
|
|
||||||
mpd_run_clear(self.connection)
|
|
||||||
mpd_search_db_songs(self.connection, true)
|
|
||||||
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
|
|
||||||
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
|
|
||||||
mpd_search_commit(self.connection)
|
|
||||||
while let mpdSong = mpd_recv_song(self.connection) {
|
|
||||||
songs.append(Song(mpdSong))
|
|
||||||
}
|
|
||||||
for song in songs {
|
|
||||||
mpd_run_add(self.connection, song.uri)
|
|
||||||
}
|
|
||||||
mpd_run_play_pos(self.connection, 0)
|
|
||||||
}
|
|
||||||
idle()
|
|
||||||
}
|
|
||||||
|
|
||||||
func queueCommand(command: Command) {
|
|
||||||
guard isConnected else { return }
|
|
||||||
|
|
||||||
noIdle()
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
self.sendCommand(command: command)
|
|
||||||
}
|
|
||||||
idle()
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendCommand(command: Command) {
|
|
||||||
switch command {
|
|
||||||
|
|
||||||
// Transport commands
|
|
||||||
case .prevTrack:
|
|
||||||
sendPreviousTrack()
|
|
||||||
case .nextTrack:
|
|
||||||
sendNextTrack()
|
|
||||||
case .stop:
|
|
||||||
sendStop()
|
|
||||||
case .playPause:
|
|
||||||
sendPlay()
|
|
||||||
case .fetchStatus:
|
|
||||||
sendRunStatus()
|
|
||||||
case .fetchQueue:
|
|
||||||
sendFetchQueue()
|
|
||||||
case .fetchAllAlbums:
|
|
||||||
allAlbums()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendNextTrack() {
|
|
||||||
guard let state = status?.state,
|
|
||||||
state.isOneOf([.playing, .paused])
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
mpd_run_next(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendPreviousTrack() {
|
|
||||||
guard let state = status?.state,
|
|
||||||
state.isOneOf([.playing, .paused])
|
|
||||||
else { return }
|
|
||||||
|
|
||||||
mpd_run_previous(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendStop() {
|
|
||||||
mpd_run_stop(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendPlay() {
|
|
||||||
if status?.state == .stopped {
|
|
||||||
mpd_run_play(connection)
|
|
||||||
} else {
|
|
||||||
mpd_run_toggle_pause(connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendRunStatus() {
|
|
||||||
guard let status = mpd_run_status(connection) else { return }
|
|
||||||
self.status = Status(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendFetchQueue() {
|
|
||||||
self.queue = []
|
|
||||||
mpd_send_list_queue_meta(connection)
|
|
||||||
|
|
||||||
while let mpdSong = mpd_recv_song(connection) {
|
|
||||||
let song = Song(mpdSong)
|
|
||||||
self.queue.append(song)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func allAlbums() {
|
|
||||||
var albums: [Album] = []
|
|
||||||
var artist: String = ""
|
|
||||||
|
|
||||||
mpd_search_db_tags(connection, MPD_TAG_ALBUM)
|
|
||||||
mpd_search_add_group_tag(connection, MPD_TAG_ALBUM_ARTIST)
|
|
||||||
mpd_search_commit(connection)
|
|
||||||
while let mpdPair = mpd_recv_pair(connection) {
|
|
||||||
let pair = Pair(mpdPair)
|
|
||||||
|
|
||||||
switch pair.name {
|
|
||||||
case "AlbumArtist":
|
|
||||||
artist = pair.value
|
|
||||||
case "Album":
|
|
||||||
albums.append(Album(title: pair.value, artist: artist))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
mpd_return_pair(connection, pair.mpdPair)
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate?.didLoadAlbums(mpdClient: self, albums: albums)
|
|
||||||
}
|
|
||||||
|
|
||||||
func noIdle() {
|
|
||||||
mpd_send_noidle(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
func idle() {
|
|
||||||
commandQueue.async { [unowned self] in
|
|
||||||
mpd_send_idle(self.connection)
|
|
||||||
|
|
||||||
let result = mpd_recv_idle(self.connection, true)
|
|
||||||
self.handleIdleResult(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleIdleResult(_ result: mpd_idle) {
|
|
||||||
let mpdIdle = Idle(rawValue: result.rawValue)
|
|
||||||
|
|
||||||
if mpdIdle.contains(.queue) {
|
|
||||||
self.fetchQueue()
|
|
||||||
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
|
|
||||||
}
|
|
||||||
if mpdIdle.contains(.player) {
|
|
||||||
self.fetchStatus()
|
|
||||||
|
|
||||||
if let status = self.status {
|
|
||||||
self.delegate?.didUpdateState(mpdClient: self, state: status.state)
|
|
||||||
self.delegate?.didUpdateTime(mpdClient: self, total: status.totalTime, elapsedMs: status.elapsedTimeMs)
|
|
||||||
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !mpdIdle.isEmpty {
|
|
||||||
self.idle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLastErrorMessage() -> String? {
|
|
||||||
if mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let errorMessage = mpd_connection_get_error_message(connection) {
|
|
||||||
return String(cString: errorMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
35
Persephone/MPDClient/Models/Command.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// Command.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/19.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension MPDClient {
|
||||||
|
enum Command {
|
||||||
|
// Transport commands
|
||||||
|
case prevTrack
|
||||||
|
case nextTrack
|
||||||
|
case playPause
|
||||||
|
case stop
|
||||||
|
case seekCurrentSong
|
||||||
|
|
||||||
|
// Database commands
|
||||||
|
case updateDatabase
|
||||||
|
|
||||||
|
// Status commands
|
||||||
|
case fetchStatus
|
||||||
|
|
||||||
|
// Queue commands
|
||||||
|
case fetchQueue
|
||||||
|
case playTrack
|
||||||
|
|
||||||
|
// Album commands
|
||||||
|
case fetchAllAlbums
|
||||||
|
case playAlbum
|
||||||
|
case getAlbumURI
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Persephone/MPDClient/Models/Idle.swift
Normal 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 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -45,6 +45,10 @@ extension MPDClient {
|
|||||||
return mpd_song_get_uri(mpdSong)
|
return mpd_song_get_uri(mpdSong)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var uriString: String {
|
||||||
|
return String(cString: uri)
|
||||||
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
|
|||||||
@ -49,5 +49,11 @@ extension MPDClient {
|
|||||||
var song: Int {
|
var song: Int {
|
||||||
return Int(mpd_status_get_song_pos(mpdStatus))
|
return Int(mpd_status_get_song_pos(mpdStatus))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var updating: Bool {
|
||||||
|
let updating = mpd_status_get_update_id(mpdStatus)
|
||||||
|
|
||||||
|
return updating > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,9 @@ protocol MPDClientDelegate {
|
|||||||
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
|
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
|
||||||
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt)
|
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt)
|
||||||
|
|
||||||
|
func willStartDatabaseUpdate(mpdClient: MPDClient)
|
||||||
|
func didFinishDatabaseUpdate(mpdClient: MPDClient)
|
||||||
|
|
||||||
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
|
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
|
||||||
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
|
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
|
||||||
|
|
||||||
|
|||||||
26
Persephone/Models/AlbumItem.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// AlbumItem.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/26.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
struct AlbumItem {
|
||||||
|
var album: MPDClient.Album
|
||||||
|
var coverArt: NSImage?
|
||||||
|
|
||||||
|
var title: String {
|
||||||
|
return album.title
|
||||||
|
}
|
||||||
|
|
||||||
|
var artist: String {
|
||||||
|
return album.artist
|
||||||
|
}
|
||||||
|
|
||||||
|
var hash: String {
|
||||||
|
return "\(title) - \(artist)".sha1()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,6 +9,10 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Preferences {
|
struct Preferences {
|
||||||
|
let mpdHostDefault = "127.0.0.1"
|
||||||
|
let mpdPortDefault = 6600
|
||||||
|
let mpdLibraryDirDefault = "~/Music"
|
||||||
|
|
||||||
let preferences = UserDefaults.standard
|
let preferences = UserDefaults.standard
|
||||||
|
|
||||||
var mpdHost: String? {
|
var mpdHost: String? {
|
||||||
@ -20,6 +24,10 @@ struct Preferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mpdHostOrDefault: String {
|
||||||
|
return mpdHost ?? mpdHostDefault
|
||||||
|
}
|
||||||
|
|
||||||
var mpdPort: Int? {
|
var mpdPort: Int? {
|
||||||
get {
|
get {
|
||||||
return preferences.value(forKey: "mpdPort") as? Int
|
return preferences.value(forKey: "mpdPort") as? Int
|
||||||
@ -33,12 +41,34 @@ struct Preferences {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mpdHostOrDefault: String {
|
var mpdPortOrDefault: Int {
|
||||||
return mpdHost ?? "127.0.0.1"
|
return mpdPort ?? mpdPortDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
var mpdPortOrDefault: Int {
|
var mpdLibraryDir: String? {
|
||||||
return mpdPort ?? 6600
|
get {
|
||||||
|
return preferences.string(forKey: "mpdLibraryDir")
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
preferences.set(newValue, forKey: "mpdLibraryDir")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var mpdLibraryDirOrDefault: String {
|
||||||
|
return mpdLibraryDir ?? mpdLibraryDirDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
var expandedMpdLibraryDir: String {
|
||||||
|
return NSString(string: mpdLibraryDirOrDefault).expandingTildeInPath
|
||||||
|
}
|
||||||
|
|
||||||
|
var fetchMissingArtworkFromInternet: Bool {
|
||||||
|
get {
|
||||||
|
return preferences.bool(forKey: "fetchMissingArtworkFromInternet")
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
preferences.set(newValue, forKey: "fetchMissingArtworkFromInternet")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addObserver(_ observer: NSObject, forKeyPath keyPath: String) {
|
func addObserver(_ observer: NSObject, forKeyPath keyPath: String) {
|
||||||
|
|||||||
23
Persephone/Operations/AlbumArtQueue.swift
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// AlbumArtOperationQueue.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/26.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class AlbumArtQueue {
|
||||||
|
static let shared = AlbumArtQueue()
|
||||||
|
|
||||||
|
let queue = DispatchQueue(label: "AlbumArtQueue")
|
||||||
|
var lastDispatchedTime = DispatchTime(uptimeNanoseconds: 0) - 1
|
||||||
|
|
||||||
|
func addToQueue(workItem: DispatchWorkItem) {
|
||||||
|
let dispatchTime = max(lastDispatchedTime + 1, DispatchTime(uptimeNanoseconds: 0))
|
||||||
|
lastDispatchedTime = dispatchTime
|
||||||
|
|
||||||
|
queue.asyncAfter(deadline: dispatchTime, execute: workItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Persephone/PersephoneBridgingHeader.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// PersephoneBridgingHeader.h
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/01.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef PersephoneBridgingHeader_h
|
||||||
|
#define PersephoneBridgingHeader_h
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* PersephoneBridgingHeader_h */
|
||||||
|
|
||||||
|
#import <CommonCrypto/CommonCrypto.h>
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// AlbumArtPrefsController.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/23.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class AlbumArtPrefsController: NSViewController {
|
||||||
|
var preferences = Preferences()
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
if let mpdLibraryDir = preferences.mpdLibraryDir {
|
||||||
|
mpdLibraryDirField.stringValue = mpdLibraryDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if preferences.fetchMissingArtworkFromInternet {
|
||||||
|
fetchMissingArtworkFromInternet.state = .on
|
||||||
|
} else {
|
||||||
|
fetchMissingArtworkFromInternet.state = .off
|
||||||
|
}
|
||||||
|
|
||||||
|
preferredContentSize = NSMakeSize(view.frame.size.width, view.frame.size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear() {
|
||||||
|
super.viewDidAppear()
|
||||||
|
|
||||||
|
guard let title = title
|
||||||
|
else { return }
|
||||||
|
self.parent?.view.window?.title = title
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func updateMpdLibraryDir(_ sender: NSTextField) {
|
||||||
|
preferences.mpdLibraryDir = sender.stringValue
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet var mpdLibraryDirField: NSTextField!
|
||||||
|
|
||||||
|
@IBAction func updateFetchMissingArtworkFromInternet(_ sender: NSButton) {
|
||||||
|
if sender.state == .on {
|
||||||
|
preferences.fetchMissingArtworkFromInternet = true
|
||||||
|
} else {
|
||||||
|
preferences.fetchMissingArtworkFromInternet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet var fetchMissingArtworkFromInternet: NSButton!
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
|
||||||
class PreferencesViewController: NSViewController {
|
class GeneralPrefsViewController: NSViewController {
|
||||||
var preferences = Preferences()
|
var preferences = Preferences()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
@ -21,6 +21,16 @@ class PreferencesViewController: NSViewController {
|
|||||||
if let mpdPort = preferences.mpdPort {
|
if let mpdPort = preferences.mpdPort {
|
||||||
mpdPortField.stringValue = "\(mpdPort)"
|
mpdPortField.stringValue = "\(mpdPort)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preferredContentSize = NSMakeSize(view.frame.size.width, view.frame.size.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func viewDidAppear() {
|
||||||
|
super.viewDidAppear()
|
||||||
|
|
||||||
|
guard let title = title
|
||||||
|
else { return }
|
||||||
|
self.parent?.view.window?.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func updateMpdHost(_ sender: NSTextField) {
|
@IBAction func updateMpdHost(_ sender: NSTextField) {
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// PreferencesController.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/23.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class PreferencesViewController: NSTabViewController {
|
||||||
|
private lazy var tabViewSizes: [String : NSSize] = [:]
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
if let viewController = self.tabViewItems.first?.viewController, let title = viewController.title {
|
||||||
|
tabViewSizes[title] = viewController.view.frame.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func transition(from fromViewController: NSViewController, to toViewController: NSViewController, options: NSViewController.TransitionOptions, completionHandler completion: (() -> Void)?) {
|
||||||
|
NSAnimationContext.runAnimationGroup({ context in
|
||||||
|
context.duration = 0.25
|
||||||
|
|
||||||
|
self.updateWindowFrameAnimated(viewController: toViewController)
|
||||||
|
|
||||||
|
super.transition(
|
||||||
|
from: fromViewController,
|
||||||
|
to: toViewController,
|
||||||
|
options: [.crossfade, .allowUserInteraction],
|
||||||
|
completionHandler: completion
|
||||||
|
)
|
||||||
|
}, completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateWindowFrameAnimated(viewController: NSViewController) {
|
||||||
|
guard let title = viewController.title,
|
||||||
|
let window = view.window
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let contentSize: NSSize
|
||||||
|
|
||||||
|
if tabViewSizes.keys.contains(title) {
|
||||||
|
contentSize = tabViewSizes[title]!
|
||||||
|
} else {
|
||||||
|
contentSize = viewController.view.frame.size
|
||||||
|
tabViewSizes[title] = contentSize
|
||||||
|
}
|
||||||
|
|
||||||
|
let newWindowSize = window.frameRect(forContentRect: NSRect(origin: NSPoint.zero, size: contentSize)).size
|
||||||
|
|
||||||
|
var frame = window.frame
|
||||||
|
frame.origin.y += frame.height
|
||||||
|
frame.origin.y -= newWindowSize.height
|
||||||
|
frame.size = newWindowSize
|
||||||
|
window.animator().setFrame(frame, display: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
//
|
||||||
|
// PreferencesWindowController.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/09.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class PreferencesWindowController: NSWindowController, NSWindowDelegate {
|
||||||
|
override func windowDidLoad() {
|
||||||
|
super.windowDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
func windowShouldClose(_ sender: NSWindow) -> Bool {
|
||||||
|
self.window?.orderOut(sender)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,7 @@
|
|||||||
<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>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target">
|
<customObject id="-2" userLabel="File's Owner" customClass="AlbumViewItem" customModule="Persephone" customModuleProvider="target">
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="albumArtist" destination="5Uu-j1-qyT" id="2Et-tX-InT"/>
|
<outlet property="albumArtist" destination="5Uu-j1-qyT" id="2Et-tX-InT"/>
|
||||||
<outlet property="albumCoverView" destination="Kfb-8f-ean" id="CXx-gB-gz8"/>
|
<outlet property="albumCoverView" destination="Kfb-8f-ean" id="CXx-gB-gz8"/>
|
||||||
@ -76,7 +76,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
<point key="canvasLocation" x="-22" y="125.5"/>
|
<point key="canvasLocation" x="-22" y="125.5"/>
|
||||||
</customView>
|
</customView>
|
||||||
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target"/>
|
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumViewItem" customModule="Persephone" customModuleProvider="target"/>
|
||||||
</objects>
|
</objects>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="blankAlbum" width="128" height="128"/>
|
<image name="blankAlbum" width="128" height="128"/>
|
||||||
@ -62,6 +62,20 @@
|
|||||||
</items>
|
</items>
|
||||||
</menu>
|
</menu>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
|
<menuItem title="Database" id="usv-UH-vkC">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<menu key="submenu" title="Database" autoenablesItems="NO" id="rFP-zL-1X4">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Update" keyEquivalent="u" id="EJg-93-1F6">
|
||||||
|
<attributedString key="attributedTitle"/>
|
||||||
|
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="updateDatabase:" target="Voe-Tx-rLC" id="FO1-Ge-TUL"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</menuItem>
|
||||||
<menuItem title="Window" id="aUF-d1-5bR">
|
<menuItem title="Window" id="aUF-d1-5bR">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||||
@ -105,7 +119,11 @@
|
|||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
|
||||||
</connections>
|
</connections>
|
||||||
</application>
|
</application>
|
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Persephone" customModuleProvider="target"/>
|
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Persephone" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="updateDatabaseMenuItem" destination="EJg-93-1F6" id="gMf-SQ-lyI"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
|
||||||
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
@ -185,9 +203,17 @@
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
</toolbarItem>
|
</toolbarItem>
|
||||||
|
<toolbarItem implicitItemIdentifier="6B3CE282-B6C1-48BF-8962-1A02892D8DF8" label="" paletteLabel="" tag="-1" sizingBehavior="auto" id="fw4-Lp-bWJ">
|
||||||
|
<nil key="toolTip"/>
|
||||||
|
<progressIndicator key="view" identifier="databaseUpdatingIndicator" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="LpV-iM-o6t">
|
||||||
|
<rect key="frame" x="0.0" y="14" width="16" height="16"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
</progressIndicator>
|
||||||
|
</toolbarItem>
|
||||||
</allowedToolbarItems>
|
</allowedToolbarItems>
|
||||||
<defaultToolbarItems>
|
<defaultToolbarItems>
|
||||||
<toolbarItem reference="p3r-ty-Pxf"/>
|
<toolbarItem reference="p3r-ty-Pxf"/>
|
||||||
|
<toolbarItem reference="fw4-Lp-bWJ"/>
|
||||||
<toolbarItem reference="9ol-aR-mzv"/>
|
<toolbarItem reference="9ol-aR-mzv"/>
|
||||||
<toolbarItem reference="n52-8S-6kR"/>
|
<toolbarItem reference="n52-8S-6kR"/>
|
||||||
<toolbarItem reference="s1h-EC-nvL"/>
|
<toolbarItem reference="s1h-EC-nvL"/>
|
||||||
@ -200,6 +226,7 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</window>
|
</window>
|
||||||
<connections>
|
<connections>
|
||||||
|
<outlet property="databaseUpdatingIndicator" destination="LpV-iM-o6t" id="y0T-eR-ygY"/>
|
||||||
<outlet property="trackProgress" destination="kx6-xm-TAN" id="XDv-Th-Agj"/>
|
<outlet property="trackProgress" destination="kx6-xm-TAN" id="XDv-Th-Agj"/>
|
||||||
<outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/>
|
<outlet property="trackProgressBar" destination="KMy-xf-rmN" id="a67-JU-cyQ"/>
|
||||||
<outlet property="trackRemaining" destination="9WZ-ij-lrb" id="0pH-d7-wvD"/>
|
<outlet property="trackRemaining" destination="9WZ-ij-lrb" id="0pH-d7-wvD"/>
|
||||||
@ -239,7 +266,7 @@
|
|||||||
<!--Window Controller-->
|
<!--Window Controller-->
|
||||||
<scene sceneID="Rpk-bo-5kf">
|
<scene sceneID="Rpk-bo-5kf">
|
||||||
<objects>
|
<objects>
|
||||||
<windowController id="xYu-7w-E5x" sceneMemberID="viewController">
|
<windowController id="xYu-7w-E5x" customClass="PreferencesWindowController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<window key="window" title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="3FN-my-6kU">
|
<window key="window" title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="3FN-my-6kU">
|
||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||||
<rect key="contentRect" x="245" y="301" width="416" height="100"/>
|
<rect key="contentRect" x="245" y="301" width="416" height="100"/>
|
||||||
@ -249,24 +276,107 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</window>
|
</window>
|
||||||
<connections>
|
<connections>
|
||||||
<segue destination="nYi-sw-ZNp" kind="relationship" relationship="window.shadowedContentViewController" id="607-3F-gJf"/>
|
<segue destination="zhe-qh-Mal" kind="relationship" relationship="window.shadowedContentViewController" id="iWi-v3-HxM"/>
|
||||||
</connections>
|
</connections>
|
||||||
</windowController>
|
</windowController>
|
||||||
<customObject id="0sd-8B-etN" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="0sd-8B-etN" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="915" y="-89"/>
|
<point key="canvasLocation" x="915" y="-155"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Preferences View Controller-->
|
<!--General-->
|
||||||
|
<scene sceneID="5er-B6-hoB">
|
||||||
|
<objects>
|
||||||
|
<tabViewController title="General" selectedTabViewItemIndex="0" tabStyle="toolbar" id="zhe-qh-Mal" customClass="PreferencesViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<tabViewItems>
|
||||||
|
<tabViewItem label="General" identifier="generalPreferencesTab" image="NSPreferencesGeneral" id="kn0-fa-vM3"/>
|
||||||
|
<tabViewItem label="Album Art" identifier="albumArtPreferencesTab" image="coverArtPreferencesIcon" id="4Lj-dz-bOK"/>
|
||||||
|
</tabViewItems>
|
||||||
|
<viewControllerTransitionOptions key="transitionOptions" allowUserInteraction="YES"/>
|
||||||
|
<tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="418" height="300"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<font key="font" metaFont="message"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="delegate" destination="zhe-qh-Mal" id="LUL-qN-JlP"/>
|
||||||
|
</connections>
|
||||||
|
</tabView>
|
||||||
|
<connections>
|
||||||
|
<outlet property="tabView" destination="6dC-M0-oC5" id="jFQ-3f-s5E"/>
|
||||||
|
<segue destination="nYi-sw-ZNp" kind="relationship" relationship="tabItems" id="Jr4-ql-vhk"/>
|
||||||
|
<segue destination="3C9-vU-zjZ" kind="relationship" relationship="tabItems" id="zOQ-NT-Tof"/>
|
||||||
|
</connections>
|
||||||
|
</tabViewController>
|
||||||
|
<customObject id="XtF-QO-9W0" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="916" y="236"/>
|
||||||
|
</scene>
|
||||||
|
<!--Album Art-->
|
||||||
|
<scene sceneID="pQx-0G-WVt">
|
||||||
|
<objects>
|
||||||
|
<viewController title="Album Art" id="3C9-vU-zjZ" customClass="AlbumArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
|
<view key="view" id="PyK-v2-kus">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="524" height="100"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zZn-Rm-e1f">
|
||||||
|
<rect key="frame" x="53" y="62" width="104" height="17"/>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" title="Music Directory:" id="sPn-V6-CfK">
|
||||||
|
<font key="font" usesAppearanceFont="YES"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gDk-ca-eOa">
|
||||||
|
<rect key="frame" x="162" y="58" 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">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="updateMpdLibraryDir:" target="3C9-vU-zjZ" id="3Ta-fH-5Zh"/>
|
||||||
|
</connections>
|
||||||
|
</textField>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pRL-MG-1Be">
|
||||||
|
<rect key="frame" x="160" y="27" width="265" height="18"/>
|
||||||
|
<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"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="updateFetchMissingArtworkFromInternet:" target="3C9-vU-zjZ" id="I7x-9V-xJr"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="zZn-Rm-e1f" firstAttribute="leading" secondItem="PyK-v2-kus" secondAttribute="leading" constant="55" id="F9T-mO-lMa"/>
|
||||||
|
<constraint firstItem="gDk-ca-eOa" firstAttribute="top" secondItem="PyK-v2-kus" secondAttribute="top" constant="20" symbolic="YES" id="NSz-Xf-KZS"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="gDk-ca-eOa" secondAttribute="trailing" constant="74" id="QMb-TP-IdQ"/>
|
||||||
|
<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="oZ5-45-Pe5"/>
|
||||||
|
<constraint firstItem="gDk-ca-eOa" firstAttribute="leading" secondItem="pRL-MG-1Be" secondAttribute="leading" id="sBG-Yb-ii6"/>
|
||||||
|
<constraint firstItem="zZn-Rm-e1f" firstAttribute="top" secondItem="PyK-v2-kus" secondAttribute="top" constant="21" id="wHW-jd-TaG"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="fetchMissingArtworkFromInternet" destination="pRL-MG-1Be" id="Xcp-sb-iZm"/>
|
||||||
|
<outlet property="mpdLibraryDirField" destination="gDk-ca-eOa" id="myi-BQ-0NS"/>
|
||||||
|
</connections>
|
||||||
|
</viewController>
|
||||||
|
<customObject id="KzD-E3-lpA" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="1626" y="339"/>
|
||||||
|
</scene>
|
||||||
|
<!--General-->
|
||||||
<scene sceneID="xTC-Y5-Agk">
|
<scene sceneID="xTC-Y5-Agk">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="nYi-sw-ZNp" customClass="PreferencesViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController title="General" id="nYi-sw-ZNp" customClass="GeneralPrefsViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="Uwt-Lw-ILP">
|
<view key="view" id="Uwt-Lw-ILP">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="420" height="100"/>
|
<rect key="frame" x="0.0" y="0.0" width="420" height="100"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wPm-sJ-e9E">
|
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="wPm-sJ-e9E">
|
||||||
<rect key="frame" x="162" y="58" width="184" height="22"/>
|
<rect key="frame" x="162" y="58" width="184" height="22"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" alignment="left" placeholderString="127.0.0.1" drawsBackground="YES" id="MSX-mn-2ma">
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" alignment="left" placeholderString="127.0.0.1" drawsBackground="YES" id="MSX-mn-2ma">
|
||||||
<font key="font" usesAppearanceFont="YES"/>
|
<font key="font" usesAppearanceFont="YES"/>
|
||||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||||
@ -276,9 +386,11 @@
|
|||||||
<action selector="updateMpdHost:" target="nYi-sw-ZNp" id="Y7x-N9-6ag"/>
|
<action selector="updateMpdHost:" target="nYi-sw-ZNp" id="Y7x-N9-6ag"/>
|
||||||
</connections>
|
</connections>
|
||||||
</textField>
|
</textField>
|
||||||
<textField verticalHuggingPriority="750" fixedFrame="YES" 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"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
<constraints>
|
||||||
|
<constraint firstAttribute="width" constant="80" id="WAb-PB-Z1Y"/>
|
||||||
|
</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">
|
||||||
<real key="minimum" value="0.0"/>
|
<real key="minimum" value="0.0"/>
|
||||||
@ -292,18 +404,19 @@
|
|||||||
<action selector="updateMpdPort:" target="nYi-sw-ZNp" id="406-EC-aO2"/>
|
<action selector="updateMpdPort:" target="nYi-sw-ZNp" id="406-EC-aO2"/>
|
||||||
</connections>
|
</connections>
|
||||||
</textField>
|
</textField>
|
||||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" 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"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<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" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU">
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU">
|
||||||
<rect key="frame" x="77" y="30" width="77" height="17"/>
|
<rect key="frame" x="77" y="30" width="77" height="17"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
|
||||||
<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"/>
|
||||||
@ -311,6 +424,18 @@
|
|||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
</subviews>
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="wPm-sJ-e9E" firstAttribute="top" secondItem="Uwt-Lw-ILP" secondAttribute="top" constant="20" symbolic="YES" id="1u9-dh-XrA"/>
|
||||||
|
<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="FPR-mZ-SUo"/>
|
||||||
|
<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="top" secondItem="Uwt-Lw-ILP" secondAttribute="top" constant="21" id="ITg-Ag-wpl"/>
|
||||||
|
<constraint firstItem="kvB-99-zwY" firstAttribute="leading" secondItem="Uwt-Lw-ILP" secondAttribute="leading" constant="78" id="Kw0-2i-oST"/>
|
||||||
|
<constraint firstItem="IbX-oV-soD" firstAttribute="leading" secondItem="AU9-wN-kbU" secondAttribute="trailing" constant="10" id="Y6y-25-qRM"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="wPm-sJ-e9E" secondAttribute="trailing" constant="74" id="cX3-Sz-RXR"/>
|
||||||
|
<constraint firstItem="kvB-99-zwY" firstAttribute="centerX" secondItem="AU9-wN-kbU" secondAttribute="centerX" id="fdg-fL-UzL"/>
|
||||||
|
<constraint firstItem="wPm-sJ-e9E" firstAttribute="leading" secondItem="IbX-oV-soD" secondAttribute="leading" id="qBw-Ri-Z4l"/>
|
||||||
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="mpdHostField" destination="wPm-sJ-e9E" id="PR7-oL-tVQ"/>
|
<outlet property="mpdHostField" destination="wPm-sJ-e9E" id="PR7-oL-tVQ"/>
|
||||||
@ -319,7 +444,7 @@
|
|||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="lzf-yO-5pP" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="lzf-yO-5pP" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="917" y="217"/>
|
<point key="canvasLocation" x="1574" y="69"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Queue View Controller-->
|
<!--Queue View Controller-->
|
||||||
<scene sceneID="QcX-dC-cTZ">
|
<scene sceneID="QcX-dC-cTZ">
|
||||||
@ -478,7 +603,7 @@
|
|||||||
</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="848" y="635"/>
|
<point key="canvasLocation" x="834" y="679"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--Album View Controller-->
|
<!--Album View Controller-->
|
||||||
<scene sceneID="7Ua-Hj-zWt">
|
<scene sceneID="7Ua-Hj-zWt">
|
||||||
@ -494,7 +619,7 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<collectionView id="lfq-AB-epE">
|
<collectionView identifier="albumCollectionView" id="lfq-AB-epE">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="450" height="158"/>
|
<rect key="frame" x="0.0" y="0.0" width="450" height="158"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||||
<collectionViewLayout key="collectionViewLayout" id="YE8-sD-l5P" customClass="AlbumViewLayout" customModule="Persephone" customModuleProvider="target"/>
|
<collectionViewLayout key="collectionViewLayout" id="YE8-sD-l5P" customClass="AlbumViewLayout" customModule="Persephone" customModuleProvider="target"/>
|
||||||
@ -533,6 +658,8 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
|
<image name="NSPreferencesGeneral" 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"/>
|
||||||
<image name="prevTrackButton" width="17" height="17"/>
|
<image name="prevTrackButton" width="17" height="17"/>
|
||||||
|
|||||||
54
Persephone/Services/AlbumArtService.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// AlbumArtService.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/23.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import PromiseKit
|
||||||
|
|
||||||
|
class AlbumArtService {
|
||||||
|
var preferences = Preferences()
|
||||||
|
let album: AlbumItem
|
||||||
|
|
||||||
|
let cachedArtworkSize = 180
|
||||||
|
let cachedArtworkQuality: CGFloat = 0.5
|
||||||
|
|
||||||
|
var session = URLSession(configuration: .default)
|
||||||
|
let cacheQueue = DispatchQueue(label: "albumArtCacheQueue")
|
||||||
|
|
||||||
|
init(album: AlbumItem) {
|
||||||
|
self.album = album
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAlbumArt(callback: @escaping (_ image: NSImage?) -> Void) {
|
||||||
|
cacheQueue.async {
|
||||||
|
firstly {
|
||||||
|
self.getCachedArtwork()
|
||||||
|
}.then { artwork -> Promise<NSImage?> in
|
||||||
|
artwork.map(Promise.value) ?? self.cacheIfNecessary(self.getArtworkFromFilesystem())
|
||||||
|
}.then { artwork -> Promise<NSImage?> in
|
||||||
|
artwork.map(Promise.value) ?? self.cacheIfNecessary(self.getArtworkFromMusicBrainz().map(Optional.some))
|
||||||
|
}.tap { result in
|
||||||
|
switch result {
|
||||||
|
case .fulfilled(nil), .rejected(MusicBrainzError.noArtworkAvailable):
|
||||||
|
self.cacheArtwork(data: Data())
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}.recover { error in
|
||||||
|
.value(nil)
|
||||||
|
}.done(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheIfNecessary(_ promise: Promise<NSImage?>) -> Promise<NSImage?> {
|
||||||
|
return promise.get { image in
|
||||||
|
if let data = image?.jpegData(compressionQuality: self.cachedArtworkQuality) {
|
||||||
|
self.cacheArtwork(data: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
Persephone/Services/Extensions/AlbumArtService+Caching.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
//
|
||||||
|
// AlbumArtService+Caching.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/17.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import PromiseKit
|
||||||
|
|
||||||
|
extension AlbumArtService {
|
||||||
|
static let cacheDir = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(Bundle.main.bundleIdentifier!)
|
||||||
|
|
||||||
|
func getCachedArtwork() -> Promise<NSImage?> {
|
||||||
|
return Promise { seal in
|
||||||
|
let cacheFilePath = AlbumArtService.cacheDir.appendingPathComponent(album.hash).path
|
||||||
|
let data = FileManager.default.contents(atPath: cacheFilePath)
|
||||||
|
let image = data.flatMap(NSImage.init(data:))
|
||||||
|
|
||||||
|
seal.fulfill(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheArtwork(data: Data?) {
|
||||||
|
guard let bundleIdentifier = Bundle.main.bundleIdentifier,
|
||||||
|
let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
|
||||||
|
.appendingPathComponent(bundleIdentifier)
|
||||||
|
else { return }
|
||||||
|
|
||||||
|
let cacheFilePath = cacheDir.appendingPathComponent(album.hash).path
|
||||||
|
|
||||||
|
FileManager.default.createFile(atPath: cacheFilePath, contents: data, attributes: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// AlbumArtService+Filesystem.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/17.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import PromiseKit
|
||||||
|
|
||||||
|
extension AlbumArtService {
|
||||||
|
func getArtworkFromFilesystem() -> Promise<NSImage?> {
|
||||||
|
let coverArtFilenames = [
|
||||||
|
"folder.jpg",
|
||||||
|
"cover.jpg",
|
||||||
|
"\(album.artist) - \(album.title).jpg"
|
||||||
|
]
|
||||||
|
|
||||||
|
return getAlbumURI().map { albumURI in
|
||||||
|
let musicDir = self.preferences.expandedMpdLibraryDir
|
||||||
|
|
||||||
|
return coverArtFilenames
|
||||||
|
.lazy
|
||||||
|
.map { "\(musicDir)/\($0)" }
|
||||||
|
.compactMap(self.tryImage)
|
||||||
|
.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAlbumURI() -> Promise<String> {
|
||||||
|
return Promise { seal in
|
||||||
|
AppDelegate.mpdClient.getAlbumURI(for: album.album, callback: seal.fulfill)
|
||||||
|
}
|
||||||
|
.compactMap { $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryImage(_ filePath: String) -> NSImage? {
|
||||||
|
guard let data = FileManager.default.contents(atPath: filePath),
|
||||||
|
let image = NSImage(data: data)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
let imageThumb = image.toFitBox(
|
||||||
|
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
|
||||||
|
)
|
||||||
|
|
||||||
|
return imageThumb
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Persephone/Services/Extensions/AlbumArtService+Remote.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// AlbumArtService+Remote.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/3/17.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
import SwiftyJSON
|
||||||
|
import PromiseKit
|
||||||
|
import PMKFoundation
|
||||||
|
|
||||||
|
extension AlbumArtService {
|
||||||
|
enum MusicBrainzError: Error {
|
||||||
|
case noArtworkAvailable
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRemoteArtwork() -> Promise<NSImage> {
|
||||||
|
return Promise { seal in
|
||||||
|
let albumArtWorkItem = DispatchWorkItem {
|
||||||
|
self.getArtworkFromMusicBrainz().pipe(to: seal.resolve)
|
||||||
|
}
|
||||||
|
|
||||||
|
AlbumArtQueue.shared.addToQueue(workItem: albumArtWorkItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArtworkFromMusicBrainz() -> Promise<NSImage> {
|
||||||
|
var search = URLComponents(string: "https://musicbrainz.org/ws/2/release/")!
|
||||||
|
search.query = "query=artist:\(album.artist) AND release:\(album.title) AND country:US&limit=1&fmt=json"
|
||||||
|
|
||||||
|
return URLSession.shared.dataTask(.promise, with: search.url!).validate()
|
||||||
|
.compactMap {
|
||||||
|
JSON($0.data)
|
||||||
|
}.compactMap {
|
||||||
|
$0["releases"][0]["id"].string
|
||||||
|
}.compactMap {
|
||||||
|
URLComponents(string: "https://coverartarchive.org/release/\($0)/front-500")?.url
|
||||||
|
}.then { (url: URL?) -> Promise<(data: Data, response: URLResponse)> in
|
||||||
|
return URLSession.shared.dataTask(.promise, with: url!).validate()
|
||||||
|
}.compactMap {
|
||||||
|
NSImage(data: $0.data)?.toFitBox(
|
||||||
|
size: NSSize(width: self.cachedArtworkSize, height: self.cachedArtworkSize)
|
||||||
|
)
|
||||||
|
}.recover { error -> Promise<NSImage> in
|
||||||
|
if case PMKHTTPError.badStatusCode(404, _, _) = error {
|
||||||
|
throw MusicBrainzError.noArtworkAvailable
|
||||||
|
} else {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 982 B After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 976 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 7.4 KiB |
BIN
Resources/export/coverArtPreferencesIcon.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Resources/export/coverArtPreferencesIcon@2x.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 784 KiB |
10
bin/release
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -o pipefail && \
|
||||||
|
|
||||||
|
xcodebuild \
|
||||||
|
-project Persephone.xcodeproj \
|
||||||
|
-scheme Persephone \
|
||||||
|
-destination platform\=macOS build \
|
||||||
|
-configuration Release \
|
||||||
|
| xcpretty
|
||||||