diff --git a/Cartfile b/Cartfile index e87cab7..1289acc 100644 --- a/Cartfile +++ b/Cartfile @@ -1,4 +1,3 @@ -github "Alamofire/Alamofire" github "SwiftyJSON/SwiftyJSON" ~> 4.0 -github "mxcl/PromiseKit" ~> 6.8 +github "PromiseKit/Foundation" ~> 3.0 github "nhurden/MediaKeyTap" "fix-tis-tsm-error" diff --git a/Cartfile.resolved b/Cartfile.resolved index 38136e4..9e54dc0 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,4 +1,4 @@ -github "Alamofire/Alamofire" "4.8.1" +github "PromiseKit/Foundation" "3.3.1" github "SwiftyJSON/SwiftyJSON" "4.2.0" github "mxcl/PromiseKit" "6.8.3" github "nhurden/MediaKeyTap" "355d346c56243e6d56487fa46fcad945251e16ae" diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index d06f0d3..4847861 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -32,15 +32,16 @@ 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, ); }; }; - E450AD8B22262B590091BED3 /* AlbumArtOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD8A22262B590091BED3 /* AlbumArtOperation.swift */; }; 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 /* AlbumArtOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* AlbumArtOperations.swift */; }; + E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */; }; E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; }; - E450AD9A222633AB0091BED3 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD97222633920091BED3 /* Alamofire.framework */; }; - E450AD9B222633AB0091BED3 /* Alamofire.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD97222633920091BED3 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 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, ); }; }; E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; }; E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */; }; E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; }; @@ -90,9 +91,9 @@ dstSubfolderSpec = 10; files = ( E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */, + E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */, E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */, E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */, - E450AD9B222633AB0091BED3 /* Alamofire.framework in Embed Libraries */, E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */, ); name = "Embed Libraries"; @@ -175,13 +176,16 @@ E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = ""; }; E450AD7D222620A10091BED3 /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = ""; }; E450AD8522262AE60091BED3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/Mac/SwiftyJSON.framework; sourceTree = ""; }; - E450AD8A22262B590091BED3 /* AlbumArtOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtOperation.swift; sourceTree = ""; }; E450AD8C22262C590091BED3 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = ""; }; E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = ""; }; E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = SwiftyJSON.framework.dSYM; path = Carthage/Build/Mac/SwiftyJSON.framework.dSYM; sourceTree = ""; }; - E450AD9422262DF10091BED3 /* AlbumArtOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtOperations.swift; sourceTree = ""; }; + E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtQueue.swift; sourceTree = ""; }; E450AD96222633920091BED3 /* Alamofire.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Alamofire.framework.dSYM; path = Carthage/Build/Mac/Alamofire.framework.dSYM; sourceTree = ""; }; E450AD97222633920091BED3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = ""; }; + E450AD9C2229B9050091BED3 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; + E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = ""; }; + E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PMKFoundation.framework.dSYM; path = Carthage/Build/Mac/PMKFoundation.framework.dSYM; sourceTree = ""; }; + E450ADA02229E7C90091BED3 /* PMKFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PMKFoundation.framework; path = Carthage/Build/Mac/PMKFoundation.framework; sourceTree = ""; }; E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; }; E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = MediaKeyTap.framework.dSYM; path = Carthage/Build/Mac/MediaKeyTap.framework.dSYM; sourceTree = ""; }; E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSplitViewController.swift; sourceTree = ""; }; @@ -214,7 +218,7 @@ E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */, E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */, E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */, - E450AD9A222633AB0091BED3 /* Alamofire.framework in Frameworks */, + E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */, E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -262,6 +266,7 @@ E407861A2110CE6E006887B1 /* Persephone */ = { isa = PBXGroup; children = ( + E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */, E450AD8922262B420091BED3 /* Operations */, E4A83BF2222207BE0098FED6 /* Services */, E4A83BEC2221F5DD0098FED6 /* Preferences */, @@ -309,6 +314,7 @@ E40FE71A221B904300A4223F /* NSEvent.swift */, E435E3E1221CD4E200184CFC /* NSFont.swift */, E435E3E3221CD75D00184CFC /* NSImage.swift */, + E450AD9C2229B9050091BED3 /* String.swift */, ); path = Extensions; sourceTree = ""; @@ -333,6 +339,8 @@ E41B22BE21FB6B3300D544F6 /* Frameworks */ = { isa = PBXGroup; children = ( + E450ADA02229E7C90091BED3 /* PMKFoundation.framework */, + E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */, E450AD97222633920091BED3 /* Alamofire.framework */, E450AD96222633920091BED3 /* Alamofire.framework.dSYM */, E450AD8C22262C590091BED3 /* PromiseKit.framework */, @@ -400,8 +408,7 @@ E450AD8922262B420091BED3 /* Operations */ = { isa = PBXGroup; children = ( - E450AD8A22262B590091BED3 /* AlbumArtOperation.swift */, - E450AD9422262DF10091BED3 /* AlbumArtOperations.swift */, + E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */, ); path = Operations; sourceTree = ""; @@ -635,6 +642,7 @@ E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */, E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */, E40786232110CE70006887B1 /* Main.storyboard in Resources */, + E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */, E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -688,11 +696,10 @@ E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */, E4A642DA22090CBE00067D21 /* Status.swift in Sources */, E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */, - E450AD9522262DF10091BED3 /* AlbumArtOperations.swift in Sources */, + E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */, E450AD7E222620A10091BED3 /* AlbumItem.swift in Sources */, E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, - E450AD8B22262B590091BED3 /* AlbumArtOperation.swift in Sources */, E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */, E4F6B463221E125900ACF42A /* SongItem.swift in Sources */, @@ -713,6 +720,7 @@ E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */, + E450AD9D2229B9050091BED3 /* String.swift in Sources */, E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */, E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */, ); @@ -817,6 +825,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OBJC_BRIDGING_HEADER = Persephone/PersephoneBridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -870,6 +879,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OBJC_BRIDGING_HEADER = Persephone/PersephoneBridgingHeader.h; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; diff --git a/Persephone/AppDelegate.swift b/Persephone/AppDelegate.swift index a6df2e5..6f6f249 100644 --- a/Persephone/AppDelegate.swift +++ b/Persephone/AppDelegate.swift @@ -18,14 +18,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate { withDelegate: NotificationsController() ) - @IBAction func fetchCoverArt(_ sender: NSMenuItem) { - NotificationCenter.default.post( - name: Notification.Name("fetchAlbumArt"), - object: self, - userInfo: nil - ) - } - func applicationDidFinishLaunching(_ aNotification: Notification) { connect() diff --git a/Persephone/Controllers/AlbumViewItem.swift b/Persephone/Controllers/AlbumViewItem.swift index 6931c0c..1c93150 100644 --- a/Persephone/Controllers/AlbumViewItem.swift +++ b/Persephone/Controllers/AlbumViewItem.swift @@ -25,23 +25,6 @@ class AlbumViewItem: NSCollectionViewItem { self.setAppearance() } } - - NotificationCenter.default.addObserver( - self, - selector: #selector(fetchAlbumArt(_:)), - name: Notification.Name("fetchAlbumArt"), - object: nil - ) - } - - @objc func fetchAlbumArt(_ notification: Notification) { - guard let album = album else { return } - - AlbumArtService.shared.fetchAlbumArt(for: album) { image in - DispatchQueue.main.async { [unowned self] in - self.albumCoverView.image = image - } - } } func setAlbum(_ album: AlbumItem) { diff --git a/Persephone/DataSources/AlbumDataSource.swift b/Persephone/DataSources/AlbumDataSource.swift index 965c798..cd8d8fd 100644 --- a/Persephone/DataSources/AlbumDataSource.swift +++ b/Persephone/DataSources/AlbumDataSource.swift @@ -22,6 +22,16 @@ class AlbumDataSource: NSObject, NSCollectionViewDataSource { albumViewItem.view.wantsLayer = true albumViewItem.setAlbum(albums[indexPath.item]) + if albums[indexPath.item].coverArt == nil { + AlbumArtService.shared.fetchAlbumArt(for: albums[indexPath.item]) { image in + self.albums[indexPath.item].coverArt = image + + DispatchQueue.main.async { + albumViewItem.setAlbum(self.albums[indexPath.item]) + } + } + } + return albumViewItem } } diff --git a/Persephone/Extensions/String.swift b/Persephone/Extensions/String.swift new file mode 100644 index 0000000..e81abfe --- /dev/null +++ b/Persephone/Extensions/String.swift @@ -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() + } +} diff --git a/Persephone/Models/AlbumItem.swift b/Persephone/Models/AlbumItem.swift index eafb627..dcc5043 100644 --- a/Persephone/Models/AlbumItem.swift +++ b/Persephone/Models/AlbumItem.swift @@ -19,4 +19,8 @@ struct AlbumItem { var artist: String { return album.artist } + + var hash: String { + return "\(title) - \(artist)".sha1() + } } diff --git a/Persephone/Operations/AlbumArtOperation.swift b/Persephone/Operations/AlbumArtOperation.swift deleted file mode 100644 index 352c413..0000000 --- a/Persephone/Operations/AlbumArtOperation.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// File.swift -// Persephone -// -// Created by Daniel Barber on 2019/2/26. -// Copyright © 2019 Dan Barber. All rights reserved. -// - -import Cocoa - -class AlbumArtOperation: Operation { - -} diff --git a/Persephone/Operations/AlbumArtOperations.swift b/Persephone/Operations/AlbumArtOperations.swift deleted file mode 100644 index ed91018..0000000 --- a/Persephone/Operations/AlbumArtOperations.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// AlbumArtOperationQueue.swift -// Persephone -// -// Created by Daniel Barber on 2019/2/26. -// Copyright © 2019 Dan Barber. All rights reserved. -// - -import Cocoa - -class AlbumArtOperations { - static let shared = AlbumArtOperations() - - lazy var fetchAlbumArtQueue: OperationQueue = { - var queue = OperationQueue() - queue.name = "Fetch Album Art queue" - queue.maxConcurrentOperationCount = 1 - return queue - }() -} diff --git a/Persephone/Operations/AlbumArtQueue.swift b/Persephone/Operations/AlbumArtQueue.swift new file mode 100644 index 0000000..1bc24ac --- /dev/null +++ b/Persephone/Operations/AlbumArtQueue.swift @@ -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) + } +} diff --git a/Persephone/PersephoneBridgingHeader.h b/Persephone/PersephoneBridgingHeader.h new file mode 100644 index 0000000..b1dc434 --- /dev/null +++ b/Persephone/PersephoneBridgingHeader.h @@ -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 diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index b05c8ab..89f45ee 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -62,6 +62,16 @@ + + + + + + + + + + @@ -494,7 +504,7 @@ - + diff --git a/Persephone/Services/AlbumArtService.swift b/Persephone/Services/AlbumArtService.swift index cfa7a12..80e3ef3 100644 --- a/Persephone/Services/AlbumArtService.swift +++ b/Persephone/Services/AlbumArtService.swift @@ -8,27 +8,69 @@ import Cocoa import SwiftyJSON +import PromiseKit +import PMKFoundation class AlbumArtService: NSObject { static var shared = AlbumArtService() var session = URLSession(configuration: .default) + let cacheQueue = DispatchQueue(label: "albumArtCacheQueue", attributes: .concurrent) - func fetchAlbumArt(for album: AlbumItem, callBack: @escaping (_ image: NSImage) -> Void) { - let artist = album.artist - let title = album.title - - getArtworkFromMusicBrainz(artist: artist, title: title, callBack: callBack) + func fetchAlbumArt(for album: AlbumItem, callback: @escaping (_ image: NSImage) -> Void) { + cacheQueue.async { + if !self.getCachedArtwork(for: album, callback: callback) { + self.getRemoteArtwork(for: album, callback: callback) + } + } } - func getArtworkFromMusicBrainz(artist: String, title: String, callBack: @escaping (_ image: NSImage) -> Void) { + func getCachedArtwork(for album: AlbumItem, callback: @escaping (_ image: NSImage) -> Void) -> Bool { + guard let bundleIdentifier = Bundle.main.bundleIdentifier, + let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + .appendingPathComponent(bundleIdentifier) + else { return false } + + let cacheFilePath = cacheDir.appendingPathComponent(album.hash).path + + if FileManager.default.fileExists(atPath: cacheFilePath) { + guard let data = FileManager.default.contents(atPath: cacheFilePath), + let image = NSImage(data: data) + else { return false } + + callback(image) + + return true + } else { + return false + } + } + + func cacheArtwork(for album: AlbumItem, 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) + } + + func getRemoteArtwork(for album: AlbumItem, callback: @escaping (_ image: NSImage) -> Void) { + let albumArtWorkItem = DispatchWorkItem() { + self.getArtworkFromMusicBrainz(for: album, callback: callback) + } + + AlbumArtQueue.shared.addToQueue(workItem: albumArtWorkItem) + } + + func getArtworkFromMusicBrainz(for album: AlbumItem, callback: @escaping (_ image: NSImage) -> Void) { if var urlComponents = URLComponents(string: "https://musicbrainz.org/ws/2/release/") { - urlComponents.query = "query=artist:\(artist) AND release:\(title) AND country:US&limit=1&fmt=json" + urlComponents.query = "query=artist:\(album.artist) AND release:\(album.title) AND country:US&limit=1&fmt=json" guard let searchURL = urlComponents.url else { return } - print(searchURL) - let releaseTask = session.dataTask(with: searchURL) { data, response, error in if let _ = error { return @@ -44,19 +86,22 @@ class AlbumArtService: NSObject { let json = try? JSON(data: data) { let releaseId = json["releases"][0]["id"] - let coverURL = URLComponents(string: "https://coverartarchive.org/release/\(releaseId)/front") - print(coverURL) + + let coverURL = URLComponents(string: "https://coverartarchive.org/release/\(releaseId)/front-500") + let coverArtTask = self.session.dataTask(with: coverURL!.url!) { data, response, error in + guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else { return } - print(httpResponse.mimeType) + if let mimeType = httpResponse.mimeType, mimeType == "image/jpeg", let data = data, let coverImage = NSImage(data: data) { - callBack(coverImage) + self.cacheArtwork(for: album, data: data) + callback(coverImage) } }