Skip to content

Commit

Permalink
Use tag instead of directory based metadata
Browse files Browse the repository at this point in the history
Avoids the ugly contorting of the data model from arbitrary directory
structure to artist-album-track. This should make the API requests
actually match the original data model.

- I have only tested with Navidrome. Since Navidrome uses identical IDs
  for the fake hierarchy and the tags, the transition is seamless.
- I haven't tested Subsonic. Since it uses different IDs for directories
  vs. tags, it's likely this won't be so smooth. Recreate your server.
  (Could this be handled better?)
- Nor have I tested other implementations.
- Podcasts may be busted. The podcast functionality is really bitrotted
  and not well tested regardless.
- We could do real directory hierarchies (GH-173) correctly, with a data
  model actually modelling... directories.
  • Loading branch information
NattyNarwhal committed Nov 9, 2023
1 parent 4f688df commit ba596a4
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 33 deletions.
32 changes: 31 additions & 1 deletion Submariner/SBClientController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, catego
request(url: url, type: .getIndexes)
}

func getArtists() {
let url = URL.URLWith(string: server.url, command: "rest/getArtists.view", parameters: parameters)
request(url: url, type: .getIndexes)
}

@objc(getAlbumsForArtist:) func getAlbums(artist: SBArtist) {
var params = parameters
if artist.itemId == nil {
Expand All @@ -151,6 +156,21 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, catego
}
}

func get(artist: SBArtist) {
var params = parameters
if artist.itemId == nil {
// can happen because of now playing/search

return
}
params["id"] = artist.itemId

let url = URL.URLWith(string: server.url, command: "rest/getArtist.view", parameters: params)
request(url: url, type: .getArtist) { operation in
operation.currentArtistID = artist.itemId
}
}

@objc(getCoverWithID:) func getCover(id: String) {
var params = parameters
params["id"] = id
Expand All @@ -171,6 +191,16 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, catego
}
}

func get(album: SBAlbum) {
var params = parameters
params["id"] = album.itemId

let url = URL.URLWith(string: server.url, command: "rest/getAlbum.view", parameters: params)
request(url: url, type: .getAlbum) { operation in
operation.currentAlbumID = album.itemId
}
}

@objc func getPlaylists() {
let url = URL.URLWith(string: server.url, command: "rest/getPlaylists.view", parameters: parameters)
request(url: url, type: .getPlaylists)
Expand Down Expand Up @@ -273,7 +303,7 @@ fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, catego
abort()
}

let url = URL.URLWith(string: server.url, command: "rest/getAlbumList.view", parameters: params)
let url = URL.URLWith(string: server.url, command: "rest/getAlbumList2.view", parameters: params)
request(url: url, type: type)
}

Expand Down
4 changes: 2 additions & 2 deletions Submariner/SBDatabaseController.m
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ - (void)reloadServerInternal: (SBServer*)server {
return;
}
[server getServerLicense];
[server getServerIndexes];
[server getArtists];
[server getServerPlaylists];
// XXX: Check if it's the current VC too?
if (server != nil && serverHomeController.server == server) {
Expand Down Expand Up @@ -1380,7 +1380,7 @@ - (void)subsonicConnectionFailed:(NSNotification *)notification {
- (void)subsonicConnectionSucceeded:(NSNotification *)notification {
// loading of server content, major !!!
[self.server getServerLicense];
[self.server getServerIndexes];
[self.server getArtists];
[self.server getServerPlaylists];
}

Expand Down
16 changes: 6 additions & 10 deletions Submariner/SBServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -386,20 +386,16 @@ public class SBServer: SBResource {

// #MARK: - Subsonic Client (Server Data)

@objc func getServerIndexes() {
if let lastIndexesDate = self.lastIndexesDate {
self.clientController.getIndexes(since: lastIndexesDate)
} else {
self.clientController.getIndexes()
}
@objc func getArtists() {
self.clientController.getArtists()
}

@objc func getAlbumsFor(artist: SBArtist) {
self.clientController.getAlbums(artist: artist)
@objc(getArtist:) func get(artist: SBArtist) {
self.clientController.get(artist: artist)
}

@objc func getTracksFor(albumID: String) {
self.clientController.getTracks(albumID: albumID)
@objc(getAlbum:) func get(album: SBAlbum) {
self.clientController.get(album: album)
}

@objc func getAlbumListFor(type: SBSubsonicParsingOperation.RequestType) {
Expand Down
2 changes: 1 addition & 1 deletion Submariner/SBServerHomeController.m
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ - (void)imageBrowserSelectionDidChange:(IKImageBrowserView *)aBrowser {

// reset current tracks
[tracksController setContent:nil];
[self.server getTracksForAlbumID: album.itemId];
[self.server getAlbum: album];

if([album.tracks count] == 0) {
// wait for new tracks
Expand Down
4 changes: 2 additions & 2 deletions Submariner/SBServerLibraryController.m
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ - (void)tableViewSelectionDidChange:(NSNotification *)notification {
if(selectedRow != -1) {
SBArtist *selectedArtist = [[artistsController arrangedObjects] objectAtIndex:selectedRow];
if(selectedArtist && [selectedArtist isKindOfClass:[SBArtist class]]) {
[self.server getAlbumsForArtist:selectedArtist];
[self.server getArtist:selectedArtist];
[albumsBrowserView setSelectionIndexes:nil byExtendingSelection:NO];
}
}
Expand Down Expand Up @@ -618,7 +618,7 @@ - (void)imageBrowserSelectionDidChange:(IKImageBrowserView *)aBrowser {
SBAlbum *album = [[albumsController arrangedObjects] objectAtIndex:selectedRow];
if(album) {

[self.server getTracksForAlbumID: album.itemId];
[self.server getAlbum: album];

if([album.tracks count] == 0) {
// wait for new tracks
Expand Down
53 changes: 36 additions & 17 deletions Submariner/SBSubsonicParsingOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
@objc(SBSubsonicRequestScanLibrary) case scanLibrary = 27
@objc(SBSubsonicRequestGetScanStatus) case getScanStatus = 28
@objc(SBSubsonicRequestUpdatePlaylist) case updatePlaylist = 29
@objc(SBSubsonicRequestGetArtists) case getArtists = 30
@objc(SBSubsonicRequestGetArtist) case getArtist = 31
@objc(SBSubsonicRequestGetAlbum) case getAlbum = 32
}

let clientController: SBClientController
Expand Down Expand Up @@ -329,22 +332,26 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {

private func parseElementAlbum(attributeDict: [String: String]) {
// We must have a parent (artist) to assign to.
// This will need adaptation for ID3 based approaches
// (if using ID3 endpoint, use currentArtist or artistId attrib instead)
if let parent = attributeDict["parent"], let id = attributeDict["id"] {
var artist = fetchArtist(id: parent)
// Use tag based approach; getAlbumList2 and search3 use this.
if let artistId = attributeDict["artistId"], let id = attributeDict["id"] {
var artist = fetchArtist(id: artistId)
if artist == nil {
// handles the different context fine
logger.info("Creating new artist with ID: \(parent, privacy: .public) for album ID \(id, privacy: .public)")
logger.info("Creating new artist with ID: \(artistId, privacy: .public) for album ID \(id, privacy: .public)")
artist = createArtist(attributes: attributeDict)
}

var album = fetchAlbum(id: id)
if album == nil {
logger.info("Creating new album with ID: \(id, privacy: .public) for artist ID \(parent, privacy: .public)")
logger.info("Creating new album with ID: \(id, privacy: .public) for artist ID \(artistId, privacy: .public)")
album = createAlbum(attributes: attributeDict)
}

// for future song elements under this one
if requestType == .getAlbum {
currentAlbum = album
}

if album!.artist == nil {
album!.artist = artist
artist?.addToAlbums(album!)
Expand Down Expand Up @@ -448,7 +455,7 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
nowPlaying.track = attachedTrack
attachedTrack?.nowPlaying = nowPlaying

updateTrackDependenciesForDirectoryIndex(attachedTrack!, attributeDict: attributeDict)
updateTrackDependenciesForTag(attachedTrack!, attributeDict: attributeDict)

// do it here
nowPlaying.server = server
Expand All @@ -466,26 +473,37 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
}

private func parseElementSong(attributeDict: [String: String]) {
if requestType != .search {
logger.warning("Got a song element outside of search")
return
}

if let currentSearch = self.currentSearch, let id = attributeDict["id"] {
if let track = fetchTrack(id: id) {
logger.info("Creating track ID \(id, privacy: .public) for search")
// the song element has the same format as the one used in nowPlaying, complete with artist name without ID
updateTrackDependenciesForDirectoryIndex(track, attributeDict: attributeDict)
updateTrackDependenciesForTag(track, attributeDict: attributeDict)
// objc version did some check in playlist, which didn't make sense
currentSearch.tracksToFetch.append(track.objectID)
} else {
logger.info("Creating track ID \(id, privacy: .public) for search")
let track = createTrack(attributes: attributeDict)
updateTrackDependenciesForDirectoryIndex(track, attributeDict: attributeDict)
updateTrackDependenciesForTag(track, attributeDict: attributeDict)
currentSearch.tracksToFetch.append(track.objectID)
}
} else if let currentAlbum = self.currentAlbum, let id = attributeDict["id"], let name = attributeDict["title"] {
// like parseElementChildForTrackDirectory; shouldn't need to call update dependencies...
if let track = fetchTrack(id: id) {
// Update
logger.info("Updating track with ID: \(id, privacy: .public) and name \(name, privacy: .public)")
updateTrack(track, attributes: attributeDict)
track.album = currentAlbum
currentAlbum.addToTracks(track)
} else {
// Create
logger.info("Creating new track with ID: \(id, privacy: .public) and name \(name, privacy: .public)")
let track = createTrack(attributes: attributeDict)
// now assume not nil
track.album = currentAlbum
currentAlbum.addToTracks(track)
}
} else {
logger.warning("Current search was null on a song element")
logger.warning("Song ID was nil for get album or search")
}
}

Expand Down Expand Up @@ -793,7 +811,7 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
artist.itemId = id
}
// in album element context
if let id = attributes["parent"] {
if let id = attributes["artistId"] {
artist.itemId = id
}

Expand All @@ -807,7 +825,8 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
private func createAlbum(attributes: [String: String]) -> SBAlbum {
let album = SBAlbum.insertInManagedObjectContext(context: threadedContext)

if let name = attributes["title"] {
// ID3 based routes use name instead of title
if let name = attributes["name"] {
album.itemName = name
}

Expand Down

0 comments on commit ba596a4

Please sign in to comment.