diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index dbc62f1..310d0d2 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -53,7 +53,6 @@ E44051A0227BB0AB0090CD6F /* UIState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E440519F227BB0AB0090CD6F /* UIState.swift */; }; E442CCCD2347E73C00004E0C /* Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E442CCCC2347E73C00004E0C /* Artist.swift */; }; E450AD7E222620A10091BED3 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* Album.swift */; }; - E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* CoverArtQueue.swift */; }; E451E36B22BD214D008BE9B2 /* DraggedSongType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */; }; E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */; }; E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */; }; @@ -81,6 +80,8 @@ E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; }; E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; + E4B079C723E5E0AD0044B6D3 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; }; + E4B079C823E5E0AD0044B6D3 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; }; E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; }; E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; }; @@ -90,8 +91,6 @@ E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B72226A6C770075461B /* TrackTimer.swift */; }; E4B11B75226CC4D30075461B /* QueueReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B74226CC4D30075461B /* QueueReducer.swift */; }; E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B78226D346B0075461B /* AlbumListReducer.swift */; }; - E4B11BA62274E44A0075461B /* libmpdclient.2.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; }; E4B11BA92274EDE30075461B /* Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BA82274EDE30075461B /* Loading.swift */; }; E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB52275374B0075461B /* UserNotificationsController.swift */; }; E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */; }; @@ -100,11 +99,14 @@ E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; }; E4B3DF6523D66A4400728F6B /* QueueSongCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */; }; E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */; }; + E4BB7F8F23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */; }; + E4BB7F9323E9150A00906E2F /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F9223E9150A00906E2F /* CoverArtService.swift */; }; E4BBD2F323357C0700702C16 /* ArtistListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BBD2F223357C0700702C16 /* ArtistListState.swift */; }; E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; }; E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; }; E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; }; E4DA820623D6236200C1EE58 /* NSSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4DA820523D6236200C1EE58 /* NSSize.swift */; }; + E4DCCFAE23E4DB5D009A8113 /* MPDClientWrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */; }; E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; }; E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; }; @@ -151,15 +153,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - E4B11BA52274E43B0075461B /* Embed Frameworks */ = { + E4B079C923E5E0AD0044B6D3 /* Embed Libraries */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - E4B11BA62274E44A0075461B /* libmpdclient.2.dylib in Embed Frameworks */, + E4B079C823E5E0AD0044B6D3 /* libmpdclient.2.dylib in Embed Libraries */, ); - name = "Embed Frameworks"; + name = "Embed Libraries"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ @@ -254,7 +256,6 @@ E440519F227BB0AB0090CD6F /* UIState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIState.swift; sourceTree = ""; }; E442CCCC2347E73C00004E0C /* Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Artist.swift; sourceTree = ""; }; E450AD7D222620A10091BED3 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = ""; }; - E450AD9422262DF10091BED3 /* CoverArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtQueue.swift; sourceTree = ""; }; E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = ""; }; E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSongType.swift; sourceTree = ""; }; E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraggedSong.swift; sourceTree = ""; }; @@ -294,11 +295,16 @@ E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = ""; }; E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongCoverView.swift; sourceTree = ""; }; E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerDelegate.swift; sourceTree = ""; }; + E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDAlbumArtImageDataProvider.swift; sourceTree = ""; }; + E4BB7F9223E9150A00906E2F /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = ""; }; E4BBD2F223357C0700702C16 /* ArtistListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistListState.swift; sourceTree = ""; }; E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = ""; }; E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = ""; }; E4DA820523D6236200C1EE58 /* NSSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSize.swift; sourceTree = ""; }; + E4DCCFAB23E4DB5D009A8113 /* Persephone-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Persephone-Bridging-Header.h"; sourceTree = ""; }; + E4DCCFAC23E4DB5D009A8113 /* MPDClientWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MPDClientWrapper.h; sourceTree = ""; }; + E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = MPDClientWrapper.c; sourceTree = ""; }; E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = ""; }; E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = ""; }; E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = ""; }; @@ -322,12 +328,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */, E49A5485233E5ADC00EED353 /* Differ in Frameworks */, E4677C48233E60E70041474F /* MediaKeyTap in Frameworks */, E43BECA0238835DC00CAF1EB /* Kingfisher in Frameworks */, E49A548E233E5B6000EED353 /* SwiftyJSON in Frameworks */, E4E96D13233E630800AFD36F /* PMKFoundation in Frameworks */, + E4B079C723E5E0AD0044B6D3 /* libmpdclient.2.dylib in Frameworks */, E49A5482233E580800EED353 /* PromiseKit in Frameworks */, E49A548B233E5B2D00EED353 /* CryptoSwift in Frameworks */, E49A5488233E5B0000EED353 /* ReSwift in Frameworks */, @@ -387,7 +393,6 @@ E40786242110CE70006887B1 /* Info.plist */, E4F6B461221E124700ACF42A /* Models */, E4A642DB220912FA00067D21 /* MPDClient */, - E450AD8922262B420091BED3 /* Operations */, E40786252110CE70006887B1 /* Persephone.entitlements */, E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */, E4A83BF2222207BE0098FED6 /* Services */, @@ -446,6 +451,9 @@ E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */, E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */, + E4DCCFAC23E4DB5D009A8113 /* MPDClientWrapper.h */, + E4DCCFAD23E4DB5D009A8113 /* MPDClientWrapper.c */, + E4DCCFAB23E4DB5D009A8113 /* Persephone-Bridging-Header.h */, ); path = Extensions; sourceTree = ""; @@ -574,6 +582,7 @@ E442CCC92347D6FD00004E0C /* Shared */ = { isa = PBXGroup; children = ( + E4BB7F8D23E5E7A300906E2F /* ImageDataProviders */, E4E13C2C2350D8CB00092A6E /* Layouts */, E408D3B7220DE8CC0006D9BE /* Extensions */, E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */, @@ -594,14 +603,6 @@ path = Browser; sourceTree = ""; }; - E450AD8922262B420091BED3 /* Operations */ = { - isa = PBXGroup; - children = ( - E450AD9422262DF10091BED3 /* CoverArtQueue.swift */, - ); - path = Operations; - sourceTree = ""; - }; E4A642DB220912FA00067D21 /* MPDClient */ = { isa = PBXGroup; children = ( @@ -628,6 +629,7 @@ isa = PBXGroup; children = ( E439109722640213002982E9 /* SongNotifierService.swift */, + E4BB7F9223E9150A00906E2F /* CoverArtService.swift */, ); path = Services; sourceTree = ""; @@ -675,6 +677,14 @@ path = Actions; sourceTree = ""; }; + E4BB7F8D23E5E7A300906E2F /* ImageDataProviders */ = { + isa = PBXGroup; + children = ( + E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */, + ); + path = ImageDataProviders; + sourceTree = ""; + }; E4D1B594220BA2490026F233 /* Models */ = { isa = PBXGroup; children = ( @@ -735,7 +745,7 @@ E40786162110CE6E006887B1 /* Resources */, E42A98F122430936004D8180 /* ShellScript */, E421AC9B221F7319008B2449 /* CopyFiles */, - E4B11BA52274E43B0075461B /* Embed Frameworks */, + E4B079C923E5E0AD0044B6D3 /* Embed Libraries */, ); buildRules = ( ); @@ -804,7 +814,7 @@ TargetAttributes = { E40786172110CE6E006887B1 = { CreatedOnToolsVersion = 9.4.1; - LastSwiftMigration = 1020; + LastSwiftMigration = 1130; SystemCapabilities = { com.apple.HardenedRuntime = { enabled = 1; @@ -933,13 +943,14 @@ E4B11B63226A4C510075461B /* AppReducer.swift in Sources */, E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */, E442CCCD2347E73C00004E0C /* Artist.swift in Sources */, + E4DCCFAE23E4DB5D009A8113 /* MPDClientWrapper.c in Sources */, E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */, E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */, + E4BB7F8F23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift in Sources */, E4235640228623D2001216D6 /* QueueSongInfoView.swift in Sources */, E451E36E22BD2501008BE9B2 /* DraggedSong.swift in Sources */, E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */, E4B11BC02275EE150075461B /* QueueActions.swift in Sources */, - E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */, E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */, E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */, E450AD7E222620A10091BED3 /* Album.swift in Sources */, @@ -978,6 +989,7 @@ E4F26F7923411B1500D45FF9 /* ArtistReducer.swift in Sources */, E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */, E44051942278765A0090CD6F /* App.swift in Sources */, + E4BB7F9323E9150A00906E2F /* CoverArtService.swift in Sources */, E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */, E47E2FE52220AA0700F747E6 /* FlexibleGridViewLayout.swift in Sources */, E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */, @@ -1173,6 +1185,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -1192,6 +1205,8 @@ PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; USER_HEADER_SEARCH_PATHS = libmpdclient/output; @@ -1202,6 +1217,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; @@ -1221,6 +1237,7 @@ PRODUCT_BUNDLE_IDENTIFIER = me.danbarber.Persephone; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = Persephone/include; USER_HEADER_SEARCH_PATHS = libmpdclient/output; diff --git a/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone.xcscheme b/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone.xcscheme index 66628de..5b48df2 100644 --- a/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone.xcscheme +++ b/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone.xcscheme @@ -27,6 +27,15 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + @@ -49,17 +58,6 @@ - - - - - - - - - + @@ -54,7 +54,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -98,6 +98,13 @@ + + + + + + + diff --git a/Persephone/Components/Browser/Album Detail/AlbumDetailView.swift b/Persephone/Components/Browser/Album Detail/AlbumDetailView.swift index 4e16196..d289901 100644 --- a/Persephone/Components/Browser/Album Detail/AlbumDetailView.swift +++ b/Persephone/Components/Browser/Album Detail/AlbumDetailView.swift @@ -128,15 +128,18 @@ class AlbumDetailView: NSViewController { self.dataSource.albumSongs[1].song else { return } - self.getBigCoverArt(song: song, album: album) + DispatchQueue.main.async { + self.getBigCoverArt(song: song, album: album) + } } } func getBigCoverArt(song: Song, album: Album) { - guard let imagePath = album.coverArtFilePath else { return } + let provider = MPDAlbumArtImageDataProvider( + songUri: song.mpdSong.uriString, + cacheKey: album.hash + ) - let imageURL = URL(fileURLWithPath: imagePath) - let provider = LocalFileImageDataProvider(fileURL: imageURL) albumCoverView.kf.setImage( with: .provider(provider), placeholder: NSImage.defaultCoverArt, @@ -146,13 +149,19 @@ class AlbumDetailView: NSViewController { ] ) - cacheSmallCover(provider: provider) + cacheSmallCover(song: song, album: album) } - func cacheSmallCover(provider: ImageDataProvider) { + func cacheSmallCover(song: Song, album: Album) { + let provider = MPDAlbumArtImageDataProvider( + songUri: song.mpdSong.uriString, + cacheKey: album.hash + ) + _ = KingfisherManager.shared.retrieveImage( with: .provider(provider), options: [ + .memoryCacheExpiration(.never), .processor(DownsamplingImageProcessor(size: .queueSongCoverSize)), .scaleFactor(2), ] diff --git a/Persephone/Components/Browser/Album Detail/AlbumTracksDataSource.swift b/Persephone/Components/Browser/Album Detail/AlbumTracksDataSource.swift index 4887468..16dd9e6 100644 --- a/Persephone/Components/Browser/Album Detail/AlbumTracksDataSource.swift +++ b/Persephone/Components/Browser/Album Detail/AlbumTracksDataSource.swift @@ -50,7 +50,8 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource { type: .albumSongItem(song.mpdSong.uriString), title: song.title, artist: song.artist, - cover: song.album.coverArtFilePath + album: song.album.title, + uri: song.mpdSong.uriString ), ofType: .songPasteboardType ) @@ -69,10 +70,11 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource { ) { draggingItem, index, stop in guard let item = draggingItem.item as? NSPasteboardItem, let draggedSong = item.draggedSong(forType: .songPasteboardType), - case let (title?, artist?, cover?) = ( + case let (title, artist, album, uri) = ( draggedSong.title, draggedSong.artist, - draggedSong.cover + draggedSong.album, + draggedSong.uri ) else { return } @@ -81,7 +83,8 @@ class AlbumTracksDataSource: NSObject, NSTableViewDataSource { let draggedSongView = DraggedSongView( title: title, artist: artist, - cover: cover + album: album, + uri: uri ) component.contents = draggedSongView.view.image() diff --git a/Persephone/Components/Preferences/CoverArtPrefsController.swift b/Persephone/Components/Preferences/CoverArtPrefsController.swift index b8aad25..23511e5 100644 --- a/Persephone/Components/Preferences/CoverArtPrefsController.swift +++ b/Persephone/Components/Preferences/CoverArtPrefsController.swift @@ -7,15 +7,12 @@ // import AppKit +import Kingfisher class CoverArtPrefsController: NSViewController { override func viewDidLoad() { super.viewDidLoad() - if let mpdLibraryDir = App.store.state.preferencesState.mpdLibraryDir { - mpdLibraryDirField.stringValue = mpdLibraryDir - } - if App.store.state.preferencesState.fetchMissingArtworkFromInternet { fetchMissingArtworkFromInternet.state = .on } else { @@ -33,12 +30,6 @@ class CoverArtPrefsController: NSViewController { self.parent?.view.window?.title = title } - @IBAction func updateMpdLibraryDir(_ sender: NSTextField) { - App.store.dispatch(UpdateMPDLibraryDir(mpdLibraryDir: sender.stringValue)) - } - - @IBOutlet var mpdLibraryDirField: NSTextField! - @IBAction func updateFetchMissingArtworkFromInternet(_ sender: NSButton) { App.store.dispatch( UpdateFetchMissingArtworkFromInternet( @@ -47,5 +38,10 @@ class CoverArtPrefsController: NSViewController { ) } + @IBAction func clearAlbumArtCache(_ sender: NSButton) { + KingfisherManager.shared.cache.clearDiskCache() + KingfisherManager.shared.cache.clearMemoryCache() + } + @IBOutlet var fetchMissingArtworkFromInternet: NSButton! } diff --git a/Persephone/Components/Queue/CurrentCoverArtView.swift b/Persephone/Components/Queue/CurrentCoverArtView.swift index d12af19..b91f03e 100644 --- a/Persephone/Components/Queue/CurrentCoverArtView.swift +++ b/Persephone/Components/Queue/CurrentCoverArtView.swift @@ -17,13 +17,23 @@ class CurrentCoverArtView: NSImageView { App.store.subscribe(self) { $0.select { $0.playerState.currentSong } } + + NotificationCenter.default.addObserver(self, selector: #selector(didReloadAlbumArt), name: .didReloadAlbumArt, object: nil) + } + + @objc func didReloadAlbumArt() { + guard let song = App.store.state.playerState.currentSong + else { return } + + setSongImage(song) } - func setAlbumImage(_ album: Album) { - guard let imagePath = album.coverArtFilePath else { return } + func setSongImage(_ song: Song) { + let provider = MPDAlbumArtImageDataProvider( + songUri: song.mpdSong.uriString, + cacheKey: song.album.hash + ) - let imageURL = URL(fileURLWithPath: imagePath) - let provider = LocalFileImageDataProvider(fileURL: imageURL) kf.setImage( with: .provider(provider), placeholder: NSImage.defaultCoverArt, @@ -44,6 +54,6 @@ extension CurrentCoverArtView: StoreSubscriber { return } - setAlbumImage(song.album) + setSongImage(song) } } diff --git a/Persephone/Components/Queue/QueueDataSource.swift b/Persephone/Components/Queue/QueueDataSource.swift index c58c819..b78c153 100644 --- a/Persephone/Components/Queue/QueueDataSource.swift +++ b/Persephone/Components/Queue/QueueDataSource.swift @@ -44,7 +44,8 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource { type: .queueItem(queueItem.queuePos), title: queueItem.song.title, artist: queueItem.song.artist, - cover: queueItem.song.album.coverArtFilePath + album: queueItem.song.album.title, + uri: queueItem.song.mpdSong.uriString ), ofType: .songPasteboardType ) @@ -128,10 +129,11 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource { guard let item = draggingItem.item as? NSPasteboardItem, let data = item.data(forType: .songPasteboardType), let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data), - case let (title?, artist?, cover?) = ( + case let (title, artist, album, uri) = ( draggedSong.title, draggedSong.artist, - draggedSong.cover + draggedSong.album, + draggedSong.uri ) else { return } @@ -140,7 +142,8 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource { let draggedSongView = DraggedSongView( title: title, artist: artist, - cover: cover + album: album, + uri: uri ) let view = draggedSongView.view diff --git a/Persephone/Components/Queue/QueueSongCoverView.swift b/Persephone/Components/Queue/QueueSongCoverView.swift index aa12000..854a090 100644 --- a/Persephone/Components/Queue/QueueSongCoverView.swift +++ b/Persephone/Components/Queue/QueueSongCoverView.swift @@ -60,14 +60,15 @@ class QueueSongCoverView: NSTableCellView { } func setSong(_ queueItem: QueueItem, queueIcon: NSImage?) { - guard let imagePath = queueItem.song.album.coverArtFilePath - else { return } + let song = queueItem.song isPlaying = queueItem.isPlaying - - let imageURL = URL(fileURLWithPath: imagePath) - let provider = LocalFileImageDataProvider(fileURL: imageURL) + let provider = MPDAlbumArtImageDataProvider( + songUri: song.mpdSong.uriString, + cacheKey: song.album.hash + ) + queueSongCover.kf.setImage( with: .provider(provider), placeholder: NSImage.defaultCoverArt, diff --git a/Persephone/Components/Queue/QueueViewController.swift b/Persephone/Components/Queue/QueueViewController.swift index 23d4905..670fa82 100644 --- a/Persephone/Components/Queue/QueueViewController.swift +++ b/Persephone/Components/Queue/QueueViewController.swift @@ -24,6 +24,7 @@ class QueueViewController: NSViewController { NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(didReloadAlbumArt), name: .didReloadAlbumArt, object: nil) queueView.dataSource = dataSource queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle @@ -61,6 +62,10 @@ class QueueViewController: NSViewController { App.store.dispatch(UpdateQueueAction(queue: [])) } } + + @objc func didReloadAlbumArt() { + queueView.reloadData() + } @IBAction func playTrack(_ sender: Any) { let queuePos = queueView.selectedRow diff --git a/Persephone/Components/Shared/DraggedSongView.swift b/Persephone/Components/Shared/DraggedSongView.swift index 49bcee6..b18b0b8 100644 --- a/Persephone/Components/Shared/DraggedSongView.swift +++ b/Persephone/Components/Shared/DraggedSongView.swift @@ -16,12 +16,14 @@ class DraggedSongView: NSViewController { private let songTitle: String private let songArtist: String - private let songCover: String? + private let songAlbum: String + private let songUri: String - init(title: String, artist: String, cover: String? = nil) { + init(title: String, artist: String, album: String, uri: String) { songTitle = title songArtist = artist - songCover = cover + songAlbum = album + songUri = uri super.init(nibName: nil, bundle: nil) } @@ -58,11 +60,13 @@ class DraggedSongView: NSViewController { } func setCoverArt() { - guard let imagePath = songCover else { return } + let mpdAlbum = MPDClient.MPDAlbum(title: songAlbum, artist: songArtist) + + let provider = MPDAlbumArtImageDataProvider( + songUri: songUri, + cacheKey: Album(mpdAlbum: mpdAlbum).hash + ) - let imageURL = URL(fileURLWithPath: imagePath) - let provider = LocalFileImageDataProvider(fileURL: imageURL) - coverImage.kf.setImage( with: .provider(provider), placeholder: NSImage.defaultCoverArt, diff --git a/Persephone/Components/Shared/Extensions/NSImage.swift b/Persephone/Components/Shared/Extensions/NSImage.swift index 6f4ca3f..cb336cd 100644 --- a/Persephone/Components/Shared/Extensions/NSImage.swift +++ b/Persephone/Components/Shared/Extensions/NSImage.swift @@ -15,34 +15,4 @@ extension NSImage { static let queuePauseIcon = NSImage(named: "queuePauseButton") static let defaultCoverArt = NSImage(named: "defaultCoverArt") - - func toFitBox(size: NSSize) -> NSImage { - var newSize: NSSize = NSSize.zero - let aspectRatio = self.size.width / self.size.height - let boxAspectRatio = size.width / size.height - - if aspectRatio > boxAspectRatio { - newSize = NSSize(width: size.width, height: size.width / aspectRatio) - } else { - newSize = NSSize(width: size.height * aspectRatio, height: size.height) - } - - let newImage = NSImage(size: newSize) - 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] - ) - } } diff --git a/Persephone/Components/Shared/Extensions/Notification.swift b/Persephone/Components/Shared/Extensions/Notification.swift index 47c1a72..45a5169 100644 --- a/Persephone/Components/Shared/Extensions/Notification.swift +++ b/Persephone/Components/Shared/Extensions/Notification.swift @@ -11,4 +11,5 @@ import Foundation extension Notification.Name { static let didConnect = Notification.Name("MPDClientDidConnect") static let willDisconnect = Notification.Name("MPDClientWillDisconnect") + static let didReloadAlbumArt = Notification.Name("MPDDidReloadAlbumArt") } diff --git a/Persephone/Components/Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift b/Persephone/Components/Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift new file mode 100644 index 0000000..a0b6f38 --- /dev/null +++ b/Persephone/Components/Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift @@ -0,0 +1,34 @@ +// +// MPDAlbumArtImageDataProvider.swift +// Persephone +// +// Created by Daniel Barber on 2/1/20. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import Foundation +import Kingfisher + +public struct MPDAlbumArtImageDataProvider: ImageDataProvider { + let songUri: String + + init(songUri: String, cacheKey: String? = nil) { + self.songUri = songUri + self.cacheKey = cacheKey ?? songUri + } + + public var cacheKey: String + + public func data(handler: @escaping (Result) -> Void) { + App.mpdClient.fetchAlbumArt(songUri: songUri, imageData: nil) { imageData in + guard let imageData = imageData + else { return } + + handler(.success(imageData)) + } + } + + public var contentURL: String? { + return songUri + } +} diff --git a/Persephone/Components/Shared/UserNotificationsController.swift b/Persephone/Components/Shared/UserNotificationsController.swift index 986cb9e..9fb5840 100644 --- a/Persephone/Components/Shared/UserNotificationsController.swift +++ b/Persephone/Components/Shared/UserNotificationsController.swift @@ -18,14 +18,16 @@ class UserNotificationsController { } func notifyTrack(_ state: Song?) { - guard let currentSong = state, - let coverArtFilePath = currentSong.album.coverArtFilePath, + guard let song = state, let status = App.mpdClient.status, status.state == .playing else { return } + + let provider = MPDAlbumArtImageDataProvider( + songUri: song.mpdSong.uriString, + cacheKey: song.album.hash + ) - let imageURL = URL(fileURLWithPath: coverArtFilePath) - let provider = LocalFileImageDataProvider(fileURL: imageURL) _ = KingfisherManager.shared.retrieveImage( with: .provider(provider), options: [ @@ -35,7 +37,7 @@ class UserNotificationsController { ) { result in switch result { case .success(let value): - SongNotifierService(song: currentSong, image: value.image) + SongNotifierService(song: song, image: value.image) .deliver() case .failure: break diff --git a/Persephone/Components/Window/Base.lproj/Main.storyboard b/Persephone/Components/Window/Base.lproj/Main.storyboard index 45e523c..c6a8ed9 100644 --- a/Persephone/Components/Window/Base.lproj/Main.storyboard +++ b/Persephone/Components/Window/Base.lproj/Main.storyboard @@ -1,8 +1,8 @@ - + - + @@ -429,30 +429,14 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - + @@ -647,18 +601,18 @@ - + - + - + - + @@ -666,7 +620,6 @@ - @@ -712,7 +665,6 @@ - @@ -761,7 +713,6 @@ - @@ -824,7 +775,7 @@ - + diff --git a/Persephone/MPDClient/Extensions/MPDClient+Album.swift b/Persephone/MPDClient/Extensions/MPDClient+Album.swift index ba52a13..fe678f8 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Album.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Album.swift @@ -74,8 +74,8 @@ extension MPDClient { let mpdAlbum = MPDAlbum( title: mpdSong.album.title, artist: mpdSong.artist, - date: mpdSong.date, - path: mpdSong.path + firstSong: mpdSong, + date: mpdSong.date ) if (mpdAlbum != albums.last) { albums.append(mpdAlbum) diff --git a/Persephone/MPDClient/Extensions/MPDClient+Command.swift b/Persephone/MPDClient/Extensions/MPDClient+Command.swift index af1ff43..9ab3026 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Command.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Command.swift @@ -119,6 +119,22 @@ extension MPDClient { else { return } albumSongs(for: album, callback: callback) + + // Song commands + case .fetchAlbumArt: + guard let songUri = userData["songUri"] as? String, + let offset = userData["offset"] as? Int32, + let callback = userData["callback"] as? (Data?) -> Void + else { return } + + var imageData = userData["imageData"] as? Data? ?? nil + + sendFetchAlbumArt( + forUri: songUri, + imageData: imageData, + offset: offset, + callback: callback + ) } } @@ -126,6 +142,7 @@ extension MPDClient { func enqueueCommand( command: MPDCommand, priority: BlockOperation.QueuePriority = .normal, + forceIdle: Bool = false, userData: Dictionary = [:] ) { guard isConnected else { return } @@ -135,7 +152,7 @@ extension MPDClient { let commandOperation = BlockOperation() { [unowned self] in self.sendCommand(command: command, userData: userData) - self.idle() + self.idle(forceIdle) } commandOperation.queuePriority = priority commandQueue.addOperation(commandOperation) diff --git a/Persephone/MPDClient/Extensions/MPDClient+Idle.swift b/Persephone/MPDClient/Extensions/MPDClient+Idle.swift index 9fc3f99..2cb7309 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Idle.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Idle.swift @@ -12,15 +12,23 @@ import mpdclient extension MPDClient { func noIdle() { if isIdle { - mpd_send_noidle(connection) - isIdle = false + do { + idleLock.lock() + defer { idleLock.unlock() } + mpd_send_noidle(connection) + isIdle = false + } } } - func idle() { - if !self.isIdle && self.commandQueue.operationCount == 1 { - mpd_send_idle(self.connection) - self.isIdle = true + func idle(_ force: Bool = false) { + if (!self.isIdle && self.commandQueue.operationCount == 1) || force { + do { + idleLock.lock() + defer { idleLock.unlock() } + mpd_send_idle(self.connection) + self.isIdle = true + } let result = mpd_recv_idle(self.connection, true) self.handleIdleResult(result) @@ -28,7 +36,11 @@ extension MPDClient { } func handleIdleResult(_ result: mpd_idle) { - isIdle = false + do { + idleLock.lock() + defer { idleLock.unlock() } + isIdle = false + } let mpdIdle = MPDIdle(rawValue: result.rawValue) diff --git a/Persephone/MPDClient/Extensions/MPDClient+Songs.swift b/Persephone/MPDClient/Extensions/MPDClient+Songs.swift index c05fc85..142345f 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Songs.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Songs.swift @@ -10,6 +10,23 @@ import Foundation import mpdclient extension MPDClient { + func fetchAlbumArt( + songUri: String, + imageData: Data?, + offset: Int32 = 0, + callback: @escaping (Data?) -> Void + ) { + enqueueCommand( + command: .fetchAlbumArt, + userData: [ + "songUri": songUri, + "callback": callback, + "imageData": imageData as Any, + "offset": offset, + ] + ) + } + func searchSongs(_ terms: [MPDClient.MPDTag: String]) -> [MPDSong] { var songs: [MPDSong] = [] @@ -25,4 +42,51 @@ extension MPDClient { return songs } + + func sendFetchAlbumArt( + forUri songUri: String, + imageData: Data?, + offset: Int32, + callback: @escaping (Data?) -> Void + ) -> Void { + var size: Int? + + mpd_send_albumart(self.connection, songUri, String(offset)) + + guard let sizePair = mpd_recv_pair(self.connection) else { + mpd_connection_clear_error(self.connection) + return + } + size = Int(MPDPair(sizePair).value) + mpd_return_pair(self.connection, sizePair) + + var data = imageData ?? Data(count: size!) + + let binaryPair = MPDPair(mpd_recv_pair(self.connection)) + let chunkSize = Int(binaryPair.value)! + mpd_return_pair(self.connection, binaryPair.pair) + + _ = data[offset...].withUnsafeMutableBytes { (pointer) in + mpd_recv_binary(self.connection, pointer, chunkSize) + } + + guard mpd_response_finish(self.connection) else { return } + + let newOffset = offset + Int32(chunkSize) + + if newOffset < size! { + DispatchQueue.main.async { + self.fetchAlbumArt( + songUri: songUri, + imageData: data, + offset: newOffset, + callback: callback + ) + } + } else { + DispatchQueue.main.async { + callback(data) + } + } + } } diff --git a/Persephone/MPDClient/Extensions/MPDClient+Transport.swift b/Persephone/MPDClient/Extensions/MPDClient+Transport.swift index 2c97a82..03d9eea 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Transport.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Transport.swift @@ -11,24 +11,26 @@ import mpdclient extension MPDClient { func playPause() { - enqueueCommand(command: .playPause) + enqueueCommand(command: .playPause, priority: .veryHigh, forceIdle: true) } func stop() { - enqueueCommand(command: .stop) + enqueueCommand(command: .stop, priority: .veryHigh, forceIdle: true) } func prevTrack() { - enqueueCommand(command: .prevTrack) + enqueueCommand(command: .prevTrack, priority: .veryHigh, forceIdle: true) } func nextTrack() { - enqueueCommand(command: .nextTrack) + enqueueCommand(command: .nextTrack, priority: .veryHigh, forceIdle: true) } func seekCurrentSong(timeInSeconds: Float) { enqueueCommand( command: .seekCurrentSong, + priority: .veryHigh, + forceIdle: true, userData: ["timeInSeconds": timeInSeconds] ) } @@ -36,6 +38,8 @@ extension MPDClient { func setShuffleState(shuffleState: Bool) { enqueueCommand( command: .setShuffleState, + priority: .veryHigh, + forceIdle: true, userData: ["shuffleState": shuffleState] ) } @@ -43,6 +47,8 @@ extension MPDClient { func setRepeatState(repeatState: Bool) { enqueueCommand( command: .setRepeatState, + priority: .veryHigh, + forceIdle: true, userData: ["repeatState": repeatState] ) } diff --git a/Persephone/MPDClient/Extensions/MPDClientWrapper.c b/Persephone/MPDClient/Extensions/MPDClientWrapper.c new file mode 100644 index 0000000..934539e --- /dev/null +++ b/Persephone/MPDClient/Extensions/MPDClientWrapper.c @@ -0,0 +1,15 @@ +// +// MPDClientWrapper.c +// Persephone +// +// Created by Daniel Barber on 1/31/20. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +#include "MPDClientWrapper.h" + +int +mpd_send_albumart(struct mpd_connection *connection, const char * uri, const char * offset) +{ + return mpd_send_command(connection, "albumart", uri, offset, NULL); +} diff --git a/Persephone/MPDClient/Extensions/MPDClientWrapper.h b/Persephone/MPDClient/Extensions/MPDClientWrapper.h new file mode 100644 index 0000000..1e48efb --- /dev/null +++ b/Persephone/MPDClient/Extensions/MPDClientWrapper.h @@ -0,0 +1,12 @@ +// +// MPDClientWrapper.h +// Persephone +// +// Created by Daniel Barber on 1/31/20. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +#include + +int +mpd_send_albumart(struct mpd_connection *connection, const char * uri, const char * offset); diff --git a/Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h b/Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h new file mode 100644 index 0000000..23a0836 --- /dev/null +++ b/Persephone/MPDClient/Extensions/Persephone-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import "MPDClientWrapper.h" diff --git a/Persephone/MPDClient/MPDClient.swift b/Persephone/MPDClient/MPDClient.swift index 9decfd8..c5c522e 100644 --- a/Persephone/MPDClient/MPDClient.swift +++ b/Persephone/MPDClient/MPDClient.swift @@ -21,6 +21,8 @@ class MPDClient { var queue: [MPDSong] = [] let commandQueue = OperationQueue() + + let idleLock = NSLock() init(withDelegate delegate: MPDClientDelegate?) { commandQueue.maxConcurrentOperationCount = 1 diff --git a/Persephone/MPDClient/Models/MPDAlbum.swift b/Persephone/MPDClient/Models/MPDAlbum.swift index a146710..3288ee3 100644 --- a/Persephone/MPDClient/Models/MPDAlbum.swift +++ b/Persephone/MPDClient/Models/MPDAlbum.swift @@ -12,8 +12,8 @@ extension MPDClient { struct MPDAlbum: Equatable { let title: String let artist: String + var firstSong: MPDSong? var date: String? - var path: String? static func == (lhs: MPDAlbum, rhs: MPDAlbum) -> Bool { return lhs.title == rhs.title && diff --git a/Persephone/MPDClient/Models/MPDCommand.swift b/Persephone/MPDClient/Models/MPDCommand.swift index 8c3d1ec..21a1a9c 100644 --- a/Persephone/MPDClient/Models/MPDCommand.swift +++ b/Persephone/MPDClient/Models/MPDCommand.swift @@ -48,5 +48,8 @@ extension MPDClient { case playAlbum case getAlbumFirstSong case getAlbumSongs + + // Song commands + case fetchAlbumArt } } diff --git a/Persephone/MPDClient/Models/MPDSong.swift b/Persephone/MPDClient/Models/MPDSong.swift index 1fc9366..47137ea 100644 --- a/Persephone/MPDClient/Models/MPDSong.swift +++ b/Persephone/MPDClient/Models/MPDSong.swift @@ -37,8 +37,7 @@ extension MPDClient { return MPDAlbum( title: getTag(.album), artist: artist, - date: date, - path: path + date: date ) } @@ -54,11 +53,6 @@ extension MPDClient { return getTag(.date) } - var path: String { - return NSString(string: uriString) - .deletingLastPathComponent - } - func getTag(_ tagType: MPDTag) -> String { guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0) else { return "" } diff --git a/Persephone/Models/Album.swift b/Persephone/Models/Album.swift index bcd933a..c6c2cc4 100644 --- a/Persephone/Models/Album.swift +++ b/Persephone/Models/Album.swift @@ -32,29 +32,6 @@ struct Album { var hash: String { return "\(title) - \(artist)".sha1() } - - var coverArtFilenames: [String] { - return [ - "cover.jpg", - "folder.jpg", - "\(artist) - \(title).jpg", - "cover.png", - "folder.png", - "\(artist) - \(title ).png", - ] - } - - var coverArtFilePath: String? { - let musicDir = App.store.state.preferencesState.expandedMpdLibraryDir - guard let albumPath = mpdAlbum.path else { return nil } - - return coverArtFilenames - .lazy - .map { "\(musicDir)/\(albumPath)/\($0)" } - .first { - FileManager.default.fileExists(atPath: $0) - } - } } extension Album: Equatable { diff --git a/Persephone/Models/DraggedSong.swift b/Persephone/Models/DraggedSong.swift index 544fec9..dcd1ebd 100644 --- a/Persephone/Models/DraggedSong.swift +++ b/Persephone/Models/DraggedSong.swift @@ -8,7 +8,8 @@ struct DraggedSong: Codable { var type: DraggedSongType - var title: String? - var artist: String? - var cover: String? + var title: String + var artist: String + var album: String + var uri: String } diff --git a/Persephone/Operations/CoverArtQueue.swift b/Persephone/Operations/CoverArtQueue.swift deleted file mode 100644 index 81ab3d5..0000000 --- a/Persephone/Operations/CoverArtQueue.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// CoverArtQueue.swift -// Persephone -// -// Created by Daniel Barber on 2019/2/26. -// Copyright © 2019 Dan Barber. All rights reserved. -// - -import AppKit - -class CoverArtQueue { - static let shared = CoverArtQueue() - - let queue = DispatchQueue(label: "CoverArtQueue") - 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) - } -} diff --git a/Persephone/Services/CoverArtService.swift b/Persephone/Services/CoverArtService.swift new file mode 100644 index 0000000..d19a307 --- /dev/null +++ b/Persephone/Services/CoverArtService.swift @@ -0,0 +1,56 @@ +// +// CoverArtService.swift +// Persephone +// +// Created by Daniel Barber on 2/3/20. +// Copyright © 2020 Dan Barber. All rights reserved. +// + +import Foundation +import Kingfisher + +struct CoverArtService { + let song: Song + + func refresh(callback: @escaping () -> Void?) { + let provider = MPDAlbumArtImageDataProvider( + songUri: song.mpdSong.uriString, + cacheKey: song.album.hash + ) + + _ = KingfisherManager.shared.retrieveImage( + with: .provider(provider), + options: [ + .forceRefresh, + .memoryCacheExpiration(.never), + .processor(DownsamplingImageProcessor(size: .albumListCoverSize)), + .scaleFactor(2), + ] + ) { _ in + callback() + } + + _ = KingfisherManager.shared.retrieveImage( + with: .provider(provider), + options: [ + .forceRefresh, + .memoryCacheExpiration(.never), + .processor(DownsamplingImageProcessor(size: .currentlyPlayingCoverSize)), + .scaleFactor(2), + .callbackQueue(.mainAsync) + ] + ) { _ in } + + _ = KingfisherManager.shared.retrieveImage( + with: .provider(provider), + options: [ + .forceRefresh, + .memoryCacheExpiration(.never), + .processor(DownsamplingImageProcessor(size: .queueSongCoverSize)), + .scaleFactor(2), + ] + ) { _ in + NotificationCenter.default.post(name: .didReloadAlbumArt, object: nil) + } + } +} diff --git a/Persephone/State/Actions/PreferencesActions.swift b/Persephone/State/Actions/PreferencesActions.swift index 518100f..51ed4d0 100644 --- a/Persephone/State/Actions/PreferencesActions.swift +++ b/Persephone/State/Actions/PreferencesActions.swift @@ -16,10 +16,6 @@ struct UpdateServerPort: Action { var port: Int? } -struct UpdateMPDLibraryDir: Action { - var mpdLibraryDir: String? -} - struct UpdateFetchMissingArtworkFromInternet: Action { var fetchMissingArtworkFromInternet: Bool } diff --git a/Persephone/State/PreferencesState.swift b/Persephone/State/PreferencesState.swift index 66e5bb8..9afeb77 100644 --- a/Persephone/State/PreferencesState.swift +++ b/Persephone/State/PreferencesState.swift @@ -13,17 +13,6 @@ struct PreferencesState: StateType, Equatable { let preferences = UserDefaults.standard var mpdServer: MPDServer - let mpdLibraryDirDefault = "~/Music" - - var mpdLibraryDir: String? - - var mpdLibraryDirOrDefault: String { - return mpdLibraryDir ?? mpdLibraryDirDefault - } - - var expandedMpdLibraryDir: String { - return NSString(string: mpdLibraryDirOrDefault).expandingTildeInPath - } var fetchMissingArtworkFromInternet: Bool @@ -32,7 +21,6 @@ struct PreferencesState: StateType, Equatable { host: preferences.string(forKey: "mpdHost"), port: preferences.value(forKey: "mpdPort") as? Int ) - self.mpdLibraryDir = preferences.string(forKey: "mpdLibraryDir") self.fetchMissingArtworkFromInternet = preferences.bool( forKey: "fetchMissingArtworkFromInternet" ) @@ -45,7 +33,6 @@ struct PreferencesState: StateType, Equatable { } else { preferences.removeObject(forKey: "mpdPort") } - preferences.set(mpdLibraryDir, forKey: "mpdLibraryDir") preferences.set(fetchMissingArtworkFromInternet, forKey: "fetchMissingArtworkFromInternet") } } diff --git a/Persephone/State/Reducers/PreferencesReducer.swift b/Persephone/State/Reducers/PreferencesReducer.swift index 8c6fae1..1ec6dee 100644 --- a/Persephone/State/Reducers/PreferencesReducer.swift +++ b/Persephone/State/Reducers/PreferencesReducer.swift @@ -20,9 +20,6 @@ func preferencesReducer(action: Action, state: PreferencesState?) -> Preferences case let action as UpdateServerPort: state.mpdServer.port = action.port - case let action as UpdateMPDLibraryDir: - state.mpdLibraryDir = action.mpdLibraryDir - case is SavePreferences: state.save()