diff --git a/Amperfy.xcodeproj/project.pbxproj b/Amperfy.xcodeproj/project.pbxproj index 5f71d962..0b683eca 100644 --- a/Amperfy.xcodeproj/project.pbxproj +++ b/Amperfy.xcodeproj/project.pbxproj @@ -764,6 +764,7 @@ 5068D37F26A85C2D0006710D /* DownloadError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadError.swift; sourceTree = ""; }; 506B3A3823B4539D00E31F21 /* Amperfy v2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v2.xcdatamodel"; sourceTree = ""; }; 506C314D2EE6D2100011A2C3 /* Amperfy v49.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v49.xcdatamodel"; sourceTree = ""; }; + 50B9C4D92EF0000100C0DEC0 /* Amperfy v50.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v50.xcdatamodel"; sourceTree = ""; }; 5070ED2C2D46979A00EB2972 /* Amperfy v42.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Amperfy v42.xcdatamodel"; sourceTree = ""; }; 507148AB2B767FE200557904 /* ContextQueuePrevSectionHeader.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ContextQueuePrevSectionHeader.xib; sourceTree = ""; }; 507148AC2B767FE200557904 /* ContextQueuePrevSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextQueuePrevSectionHeader.swift; sourceTree = ""; }; @@ -3516,6 +3517,7 @@ 500BB49521CAAA2700D367CF /* Amperfy.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 50B9C4D92EF0000100C0DEC0 /* Amperfy v50.xcdatamodel */, 506C314D2EE6D2100011A2C3 /* Amperfy v49.xcdatamodel */, 5084F70C2ED9D87500D8D3DA /* Amperfy v48.xcdatamodel */, 507C9AD82E29905D001589F8 /* Amperfy v47.xcdatamodel */, @@ -3566,7 +3568,7 @@ 506B3A3823B4539D00E31F21 /* Amperfy v2.xcdatamodel */, 500BB49621CAAA2700D367CF /* Amperfy.xcdatamodel */, ); - currentVersion = 506C314D2EE6D2100011A2C3 /* Amperfy v49.xcdatamodel */; + currentVersion = 50B9C4D92EF0000100C0DEC0 /* Amperfy v50.xcdatamodel */; path = Amperfy.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Amperfy/Screens/Player/PlayerUIHandler.swift b/Amperfy/Screens/Player/PlayerUIHandler.swift index 2f0df0a8..b2ace92a 100644 --- a/Amperfy/Screens/Player/PlayerUIHandler.swift +++ b/Amperfy/Screens/Player/PlayerUIHandler.swift @@ -279,7 +279,7 @@ class PlayerUIHandler: NSObject { albumLabel?.text = playableInfo.asSong?.album?.name ?? "" albumButton?.isEnabled = playableInfo.isSong albumContainerView?.isHidden = !playableInfo.isSong - artistLabel.text = playableInfo.creatorName + artistLabel.text = playableInfo.asSong?.creatorNameWithComposer ?? playableInfo.creatorName } } else { switch player.playerMode { diff --git a/Amperfy/Screens/View/PlayableTableCell.swift b/Amperfy/Screens/View/PlayableTableCell.swift index 70a7e555..d039411b 100644 --- a/Amperfy/Screens/View/PlayableTableCell.swift +++ b/Amperfy/Screens/View/PlayableTableCell.swift @@ -323,7 +323,7 @@ class PlayableTableCell: BasicTableCell { func refresh() { guard let playable = playable else { return } titleLabel.text = playable.title - artistLabel.text = playable.creatorName + artistLabel.text = playable.asSong?.creatorNameWithComposer ?? playable.creatorName configureStyle( playable: playable, diff --git a/AmperfyKit/Api/Ampache/SongParserDelegate.swift b/AmperfyKit/Api/Ampache/SongParserDelegate.swift index 52b0809c..82f168bb 100644 --- a/AmperfyKit/Api/Ampache/SongParserDelegate.swift +++ b/AmperfyKit/Api/Ampache/SongParserDelegate.swift @@ -140,6 +140,8 @@ class SongParserDelegate: PlayableParserDelegate { songBuffer?.genre = genre genreIdToCreate = nil } + case "composer": + songBuffer?.composer = buffer case "song": parsedCount += 1 parseNotifier?.notifyParsedObject(ofType: .song) diff --git a/AmperfyKit/Api/Subsonic/SsSongParserDelegate.swift b/AmperfyKit/Api/Subsonic/SsSongParserDelegate.swift index 144b0e8d..aa54292a 100644 --- a/AmperfyKit/Api/Subsonic/SsSongParserDelegate.swift +++ b/AmperfyKit/Api/Subsonic/SsSongParserDelegate.swift @@ -136,6 +136,9 @@ class SsSongParserDelegate: SsPlayableParserDelegate { dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds] songBuffer?.addedDate = dateFormatter.date(from: createdTag) } + if let composer = attributeDict["composer"] { + songBuffer?.composer = composer + } } super.parser( diff --git a/AmperfyKit/Storage/EntityWrappers/Song.swift b/AmperfyKit/Storage/EntityWrappers/Song.swift index c3571ad0..cbb7c040 100644 --- a/AmperfyKit/Storage/EntityWrappers/Song.swift +++ b/AmperfyKit/Storage/EntityWrappers/Song.swift @@ -86,6 +86,18 @@ public class Song: AbstractPlayable, Identifyable { } } + public var composer: String? { + get { + guard let composer = managedObject.composer, + !composer.isEmpty else { return nil } + return composer + } + set { + let composer = newValue?.trimmingCharacters(in: .whitespacesAndNewlines) + managedObject.composer = composer?.isEmpty == true ? nil : composer + } + } + public var isOrphaned: Bool { guard let album = album else { return true } return album.isOrphaned @@ -110,6 +122,11 @@ public class Song: AbstractPlayable, Identifyable { artist?.name ?? "Unknown Artist" } + public var creatorNameWithComposer: String { + guard let composer = composer else { return creatorName } + return "\(creatorName) \(CommonString.oneMiddleDot) \(composer)" + } + public var detailInfo: String { var info = displayString info += " (" @@ -117,6 +134,8 @@ public class Song: AbstractPlayable, Identifyable { info += "album: \(albumName)," let genreName = genre?.name ?? "-" info += " genre: \(genreName)," + let composerInfo = composer ?? "-" + info += " composer: \(composerInfo)," info += " id: \(id)," info += " track: \(track)," @@ -149,6 +168,9 @@ public class Song: AbstractPlayable, Identifyable { if let genre = genre { infoContent.append("Genre: \(genre.name)") } + if let composer = composer { + infoContent.append("Composer: \(composer)") + } if details.isShowDetailedInfo { if bitrate > 0 { infoContent.append("Bitrate: \(bitrate)") diff --git a/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/.xccurrentversion b/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/.xccurrentversion index 887518ef..82cb0268 100644 --- a/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/.xccurrentversion +++ b/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Amperfy v49.xcdatamodel + Amperfy v50.xcdatamodel diff --git a/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/Amperfy v50.xcdatamodel/contents b/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/Amperfy v50.xcdatamodel/contents new file mode 100644 index 00000000..14b4a20c --- /dev/null +++ b/AmperfyKit/Storage/ManagedObjects/Amperfy.xcdatamodeld/Amperfy v50.xcdatamodel/contents @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/AmperfyKit/Storage/ManagedObjects/Migration/CoreDataMigrationVersion.swift b/AmperfyKit/Storage/ManagedObjects/Migration/CoreDataMigrationVersion.swift index 66183286..93b2ee74 100644 --- a/AmperfyKit/Storage/ManagedObjects/Migration/CoreDataMigrationVersion.swift +++ b/AmperfyKit/Storage/ManagedObjects/Migration/CoreDataMigrationVersion.swift @@ -60,6 +60,7 @@ enum CoreDataMigrationVersion: String, CaseIterable { case v48 = "Amperfy v48" // Account support: add account (url + user) case v49 = "Amperfy v49" // Remove PlayableFile and Artwork data (they were already deprecated); Account: add apiType + case v50 = "Amperfy v50" // Add composer metadata for songs // MARK: - Current @@ -172,6 +173,8 @@ enum CoreDataMigrationVersion: String, CaseIterable { case .v48: return .v49 case .v49: + return .v50 + case .v50: return nil } } diff --git a/AmperfyKit/Storage/ManagedObjects/SongMO+CoreDataProperties.swift b/AmperfyKit/Storage/ManagedObjects/SongMO+CoreDataProperties.swift index 8e84b2d3..ce40e086 100644 --- a/AmperfyKit/Storage/ManagedObjects/SongMO+CoreDataProperties.swift +++ b/AmperfyKit/Storage/ManagedObjects/SongMO+CoreDataProperties.swift @@ -33,6 +33,8 @@ extension SongMO { @NSManaged public var addedDate: Date? @NSManaged + public var composer: String? + @NSManaged public var album: AlbumMO? @NSManaged public var artist: ArtistMO? diff --git a/AmperfyKitTests/Cases/API/Ampache/PlaylistSongsParserTest.swift b/AmperfyKitTests/Cases/API/Ampache/PlaylistSongsParserTest.swift index 55164b8d..97e275e3 100644 --- a/AmperfyKitTests/Cases/API/Ampache/PlaylistSongsParserTest.swift +++ b/AmperfyKitTests/Cases/API/Ampache/PlaylistSongsParserTest.swift @@ -147,6 +147,7 @@ class PlaylistSongsParserTest: AbstractAmpacheTest { XCTAssertEqual(song.id, "56") XCTAssertEqual(song.title, "Black&BlueSmoke") XCTAssertEqual(song.rating, 4) + XCTAssertNil(song.composer) XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "2") @@ -182,6 +183,7 @@ class PlaylistSongsParserTest: AbstractAmpacheTest { XCTAssertEqual(song.id, "107") XCTAssertEqual(song.title, "Arrest Me") XCTAssertEqual(song.rating, 1) + XCTAssertNil(song.composer) XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "20") @@ -217,6 +219,7 @@ class PlaylistSongsParserTest: AbstractAmpacheTest { XCTAssertEqual(song.id, "115") XCTAssertEqual(song.title, "Are we going Crazy") XCTAssertEqual(song.rating, 0) + XCTAssertNil(song.composer) XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "27") @@ -248,6 +251,7 @@ class PlaylistSongsParserTest: AbstractAmpacheTest { XCTAssertEqual(song.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.id, "85") XCTAssertEqual(song.title, "Beq Ultra Fat") + XCTAssertEqual(song.composer, "Jeffrey Melton") XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "14") diff --git a/AmperfyKitTests/Cases/API/Ampache/SongParserTest.swift b/AmperfyKitTests/Cases/API/Ampache/SongParserTest.swift index d8d3e0d2..84a063f0 100644 --- a/AmperfyKitTests/Cases/API/Ampache/SongParserTest.swift +++ b/AmperfyKitTests/Cases/API/Ampache/SongParserTest.swift @@ -55,6 +55,7 @@ class SongParserTest: AbstractAmpacheTest { XCTAssertEqual(song.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.id, "115") XCTAssertEqual(song.title, "Are we going Crazy") + XCTAssertNil(song.composer) XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "27") @@ -92,6 +93,7 @@ class SongParserTest: AbstractAmpacheTest { XCTAssertEqual(song.id, "107") XCTAssertEqual(song.title, "Arrest Me") XCTAssertEqual(song.rating, 2) + XCTAssertNil(song.composer) XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "20") @@ -131,6 +133,7 @@ class SongParserTest: AbstractAmpacheTest { XCTAssertEqual(song.id, "85") XCTAssertEqual(song.title, "Beq Ultra Fat") XCTAssertEqual(song.rating, 1) + XCTAssertEqual(song.composer, "Jeffrey Melton") XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "14") @@ -166,6 +169,7 @@ class SongParserTest: AbstractAmpacheTest { XCTAssertEqual(song.id, "56") XCTAssertEqual(song.title, "Black&BlueSmoke") XCTAssertEqual(song.rating, 0) + XCTAssertNil(song.composer) XCTAssertEqual(song.artist?.account?.serverHash, TestAccountInfo.test1ServerHash) XCTAssertEqual(song.artist?.account?.userHash, TestAccountInfo.test1UserHash) XCTAssertEqual(song.artist?.id, "2") // Artist not pre created diff --git a/AmperfyKitTests/Cases/Storage/ManagedObjects/SongTest.swift b/AmperfyKitTests/Cases/Storage/ManagedObjects/SongTest.swift index f8a98f09..d50daf17 100644 --- a/AmperfyKitTests/Cases/Storage/ManagedObjects/SongTest.swift +++ b/AmperfyKitTests/Cases/Storage/ManagedObjects/SongTest.swift @@ -49,6 +49,7 @@ class SongTest: XCTestCase { XCTAssertEqual(song.title, "Unknown Title") XCTAssertEqual(song.track, 0) XCTAssertEqual(song.url, nil) + XCTAssertNil(song.composer) XCTAssertEqual(song.album, nil) XCTAssertEqual(song.artist, nil) XCTAssertNil(song.addedDate) @@ -121,6 +122,17 @@ class SongTest: XCTestCase { XCTAssertEqual(songFetched.url, testUrl) } + func testComposer() { + testSong.composer = " Florence Price " + XCTAssertEqual(testSong.composer, "Florence Price") + library.saveContext() + guard let songFetched = library.getSong(for: account, id: testId) else { XCTFail(); return } + XCTAssertEqual(songFetched.composer, "Florence Price") + + songFetched.composer = "" + XCTAssertNil(songFetched.composer) + } + func testArtworkAndImage() { let testData = UIImage.getGeneratedArtwork(theme: .blue, artworkType: .song).pngData()! let relFilePath = URL(string: "testArtwork")!