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

Compare commits

...

3 Commits

17 changed files with 118 additions and 371 deletions

View File

@ -29,9 +29,6 @@
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 /* CoverArtService+Caching.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */; };
E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */; };
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */; };
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.swift */; };
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
@ -49,6 +46,7 @@
E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67A822909793007DCF55 /* AlbumDetailView.swift */; };
E43B67AB22909793007DCF55 /* AlbumDetailView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E43B67A922909793007DCF55 /* AlbumDetailView.xib */; };
E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */; };
E43BECA0238835DC00CAF1EB /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = E43BEC9F238835DC00CAF1EB /* Kingfisher */; };
E4405192227644340090CD6F /* MPDServerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4405191227644340090CD6F /* MPDServerController.swift */; };
E44051942278765A0090CD6F /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E44051932278765A0090CD6F /* App.swift */; };
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E440519B227BAF2E0090CD6F /* UIActions.swift */; };
@ -84,7 +82,6 @@
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 */; };
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* CoverArtService.swift */; };
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 */; };
@ -233,9 +230,6 @@
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 /* CoverArtService+Caching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Caching.swift"; sourceTree = "<group>"; };
E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Remote.swift"; sourceTree = "<group>"; };
E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Filesystem.swift"; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; };
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
@ -283,7 +277,6 @@
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = "<group>"; };
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E4A83BF3222207D50098FED6 /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = "<group>"; };
E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerReducer.swift; sourceTree = "<group>"; };
E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
@ -330,6 +323,7 @@
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 */,
E49A5482233E580800EED353 /* PromiseKit in Frameworks */,
@ -446,9 +440,9 @@
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
E41E5306223C019100173814 /* MPDClient+Status.swift */,
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -512,16 +506,6 @@
path = mpd;
sourceTree = "<group>";
};
E41E530C223EF4BA00173814 /* Extensions */ = {
isa = PBXGroup;
children = (
E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */,
E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */,
E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
E442CCC42347D5B900004E0C /* Components */ = {
isa = PBXGroup;
children = (
@ -640,8 +624,6 @@
E4A83BF2222207BE0098FED6 /* Services */ = {
isa = PBXGroup;
children = (
E41E530C223EF4BA00173814 /* Extensions */,
E4A83BF3222207D50098FED6 /* CoverArtService.swift */,
E439109722640213002982E9 /* SongNotifierService.swift */,
);
path = Services;
@ -765,6 +747,7 @@
E49A548D233E5B6000EED353 /* SwiftyJSON */,
E4677C47233E60E70041474F /* MediaKeyTap */,
E4E96D12233E630800AFD36F /* PMKFoundation */,
E43BEC9F238835DC00CAF1EB /* Kingfisher */,
);
productName = Persephone;
productReference = E40786182110CE6E006887B1 /* Persephone.app */;
@ -857,6 +840,7 @@
E49A548C233E5B6000EED353 /* XCRemoteSwiftPackageReference "SwiftyJSON" */,
E4A9BEF9233E5F9000457785 /* XCRemoteSwiftPackageReference "MediaKeyTap" */,
E4E96D11233E630800AFD36F /* XCRemoteSwiftPackageReference "Foundation" */,
E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */,
);
productRefGroup = E40786192110CE6E006887B1 /* Products */;
projectDirPath = "";
@ -945,7 +929,6 @@
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */,
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */,
E442CCCD2347E73C00004E0C /* Artist.swift in Sources */,
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
@ -1001,18 +984,15 @@
E4120D6C22AD8139004CB1F8 /* QueueView.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */,
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
E4B11B66226A4F830075461B /* PlayerState.swift in Sources */,
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */,
E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */,
E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */,
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */,
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */,
E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */,
E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */,
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
@ -1385,6 +1365,14 @@
/* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher.git";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 5.10.1;
};
};
E49A5480233E580800EED353 /* XCRemoteSwiftPackageReference "PromiseKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mxcl/PromiseKit";
@ -1444,6 +1432,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
E43BEC9F238835DC00CAF1EB /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
E4677C47233E60E70041474F /* MediaKeyTap */ = {
isa = XCSwiftPackageProductDependency;
package = E4A9BEF9233E5F9000457785 /* XCRemoteSwiftPackageReference "MediaKeyTap" */;

View File

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

View File

@ -23,24 +23,6 @@ class AlbumDataSource: NSObject, NSCollectionViewDataSource {
albumViewItem.view.wantsLayer = true
albumViewItem.setAlbum(albums[indexPath.item])
switch albums[indexPath.item].coverArt {
case .notLoaded:
let album = albums[indexPath.item]
guard let path = album.mpdAlbum.path else { break }
CoverArtService(path: path, album: album)
.fetchCoverArt()
.done { image in
DispatchQueue.main.async {
App.store.dispatch(
UpdateCoverArtAction(coverArt: image, albumIndex: indexPath.item)
)
}
}
default:
break
}
return albumViewItem
}
}

View File

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

View File

@ -7,6 +7,7 @@
//
import AppKit
import Kingfisher
class AlbumDetailView: NSViewController {
var observer: NSKeyValueObservation?
@ -54,13 +55,6 @@ class AlbumDetailView: NSViewController {
albumTitle.stringValue = album.title
albumMetadata.stringValue = "\(album.artist) · \(date)"
switch album.coverArt {
case .loaded(let coverArt):
albumCoverView.image = coverArt ?? .defaultCoverArt
default:
albumCoverView.image = .defaultCoverArt
}
super.viewWillAppear()
}
@ -137,15 +131,18 @@ class AlbumDetailView: NSViewController {
}
func getBigCoverArt(song: Song, album: Album) {
let coverArtService = CoverArtService(path: song.mpdSong.path, album: album)
guard let imagePath = album.coverArtFilePath else { return }
coverArtService.fetchBigCoverArt()
.done(on: DispatchQueue.main) { [weak self] image in
if let image = image {
self?.albumCoverView.image = image
}
}
.cauterize()
let imageURL = URL(fileURLWithPath: imagePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
albumCoverView.kf.setImage(
with: .provider(provider),
placeholder: NSImage.defaultCoverArt,
options: [
.processor(DownsamplingImageProcessor(size: NSSize(width: 500, height: 500))),
.scaleFactor(2),
]
)
}
func setAppearance() {

View File

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

View File

@ -8,8 +8,11 @@
import Foundation
import ReSwift
import Kingfisher
class UserNotificationsController {
let cache = ImageCache.default
init() {
App.store.subscribe(self) {
$0.select { $0.playerState.currentSong }
@ -18,18 +21,28 @@ class UserNotificationsController {
func notifyTrack(_ state: Song?) {
guard let currentSong = state,
let coverArtFilePath = currentSong.album.coverArtFilePath,
let status = App.mpdClient.status,
status.state == .playing
else { return }
let coverArtService = CoverArtService(path: currentSong.mpdSong.path, album: currentSong.album)
coverArtService.fetchBigCoverArt()
.done() {
SongNotifierService(song: currentSong, image: $0)
let imageURL = URL(fileURLWithPath: coverArtFilePath)
let provider = LocalFileImageDataProvider(fileURL: imageURL)
_ = KingfisherManager.shared.retrieveImage(
with: .provider(provider),
options: [
.processor(DownsamplingImageProcessor(size: NSSize(width: 180, height: 180))),
.scaleFactor(2),
]
) { result in
switch result {
case .success(let value):
SongNotifierService(song: currentSong, image: value.image)
.deliver()
case .failure:
break
}
.cauterize()
}
}
}

View File

@ -162,7 +162,6 @@ class WindowController: NSWindowController {
@IBAction func handleSearchQuery(_ sender: NSSearchField) {
//App.store.dispatch(SetSearchQuery(searchQuery: sender.stringValue))
CoverArtService.coverArtQueue
App.mpdClient.fetchAlbums(filter: sender.stringValue)
}
}

View File

@ -11,7 +11,6 @@ import CryptoSwift
struct Album {
var mpdAlbum: MPDClient.MPDAlbum
var coverArt: Loading<NSImage?> = .notLoaded
init(mpdAlbum: MPDClient.MPDAlbum) {
self.mpdAlbum = mpdAlbum
@ -33,11 +32,30 @@ struct Album {
var hash: String {
return "\(title) - \(artist)".sha1()
}
var coverArtFilenames: [String] {
return [
"folder.jpg",
"cover.jpg",
"\(artist) - \(title ).jpg"
]
}
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 {
static func == (lhs: Album, rhs: Album) -> Bool {
return (lhs.mpdAlbum == rhs.mpdAlbum) &&
(lhs.coverArt ~= rhs.coverArt)
return (lhs.mpdAlbum == rhs.mpdAlbum)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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