Skip to content

Commit

Permalink
Merge branch 'get-top-tracks'
Browse files Browse the repository at this point in the history
  • Loading branch information
NattyNarwhal committed Dec 13, 2024
2 parents dc4def3 + e937177 commit f5b32e6
Show file tree
Hide file tree
Showing 13 changed files with 182 additions and 33 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ Doing so isn't fatal (it's not a secret), but it is annoying for other contribut

### Not yet released

* Basic support for displaying related tracks. This reuses the search infrastructure. The server may call external servers if configured to do so.
* Top tracks for an artist can now be displayed
* Similar tracks for an artist (sometimes called "radio") can now be displayed
* Fix searches being ran twice.
* Directories can be starred.

### Version 3.2.1
Expand Down
3 changes: 3 additions & 0 deletions Submariner/SBDatabaseController.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@
- (IBAction)showDownloadView:(id)sender;
- (IBAction)showLibraryView:(id)sender;

- (void)getTopTracksFor:(NSString*)artistName;
- (void)getSimilarTracksTo:(SBArtist*)artist;

- (IBAction)openAudioFiles:(id)sender;
- (IBAction)toggleTrackList:(id)sender;
- (IBAction)toggleServerUsers:(id)sender;
Expand Down
55 changes: 49 additions & 6 deletions Submariner/SBDatabaseController.m
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ - (void)dealloc
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"SBSubsonicConnectionFailedNotification" object:nil];
// remove window observers
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSWindowDidChangeOcclusionStateNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"SBTitleUpdated" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"SBFirstResponderBecame" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:@"SBFirstResponderNoLonger" object:nil];
// remove selection observers
Expand Down Expand Up @@ -267,6 +268,11 @@ - (void)windowDidLoad {
name:NSWindowDidChangeOcclusionStateNotification
object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateTitle:)
name:@"SBTitleUpdated"
object:nil];

// update the selectedMusicItem for binding (SBPlaylistSelectionChanged)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateMenuBindings:)
Expand Down Expand Up @@ -915,17 +921,28 @@ - (IBAction)search:(id)sender {
return;
}


if(query && [query length] > 0) {
[searchToolbarItem endSearchInteraction];
if (query && [query length] > 0) {
SBNavigationItem *navItem = nil;
// Seems if we hit enter, it triggers search: (good), and then again if the search field resigns first responder (bad)
// So, check if this is redundant (XXX: ugly, lack of DRY, and needs some if let)
SBNavigationItem *topItem = rightVC.arrangedObjects[rightVC.selectedIndex];
if (self.server) {
if ([topItem isKindOfClass: SBServerSearchNavigationItem.class] &&
[[(SBServerSearchNavigationItem*)topItem searchQuery] isEqualToString:query]) {
return;
}
navItem = [[SBServerSearchNavigationItem alloc] initWithServer: self.server query: query];
} else {
if ([topItem isKindOfClass: SBLocalSearchNavigationItem.class] &&
[[(SBServerSearchNavigationItem*)topItem searchQuery] isEqualToString:query]) {
return;
}
navItem = [[SBLocalSearchNavigationItem alloc] initWithQuery: query];
}

[self navigateForwardToNavItem: navItem];
} else {
[searchToolbarItem endSearchInteraction];
if ([rightVC.selectedViewController isKindOfClass: SBMusicSearchController.class]
|| [rightVC.selectedViewController isKindOfClass: SBServerSearchController.class]) {
[rightVC navigateBack: sender];
Expand All @@ -934,6 +951,18 @@ - (IBAction)search:(id)sender {
}


- (void)getTopTracksFor:(NSString*)artistName {
SBNavigationItem *navItem = [[SBServerSearchNavigationItem alloc] initWithServer: self.server topTracksFor:artistName];
[self navigateForwardToNavItem: navItem];
}


- (void)getSimilarTracksTo:(SBArtist*)artist {
SBNavigationItem *navItem = [[SBServerSearchNavigationItem alloc] initWithServer: self.server similarTo:artist];
[self navigateForwardToNavItem: navItem];
}


- (IBAction)cleanTracklist:(id)sender {
[self stop: sender];
[tracklistController cleanTracklist: sender];
Expand Down Expand Up @@ -1461,6 +1490,11 @@ - (void)updateMenuBindings: (NSNotification *)notification {
}


- (void)updateTitle:(NSNotification*)notification {
[self updateTitle];
}


#pragma mark -
#pragma mark Player Notifications (Private)

Expand Down Expand Up @@ -1874,8 +1908,17 @@ - (void)pageController:(NSPageController *)pageController didTransitionToObject:
[searchField setStringValue: searchNavItem.query];
} else if ([navItem isKindOfClass: SBServerSearchNavigationItem.class]) {
SBServerSearchNavigationItem *searchNavItem = (SBServerSearchNavigationItem*)navItem;
[self.server searchWithQuery: searchNavItem.query];
[searchField setStringValue: searchNavItem.query];
// HACK: No sum types in ObjC, see SBNavigationItem
if (searchNavItem.searchQuery) {
[self.server searchWithQuery: searchNavItem.searchQuery];
[searchField setStringValue: searchNavItem.searchQuery];
} else if (searchNavItem.topTracksForArtist) {
[self.server getTopTracksForArtistName: searchNavItem.topTracksForArtist];
[searchField setStringValue: @""];
} else if (searchNavItem.similarToArtist) {
[self.server getSimilarTracksTo: searchNavItem.similarToArtist];
[searchField setStringValue: @""];
}
} else {
[searchField setStringValue: @""];
[searchToolbarItem endSearchInteraction];
Expand Down Expand Up @@ -1922,7 +1965,7 @@ - (void)pageController:(NSPageController *)pageController didTransitionToObject:
if ([navItem isKindOfClass: SBLocalMusicNavigationItem.class] || [navItem isKindOfClass: SBLocalSearchNavigationItem.class]) {
[searchToolbarItem setEnabled: YES];
[searchField setPlaceholderString: @"Local Search"];
} else if ([navItem isKindOfClass: SBServerNavigationItem.class] || [navItem isKindOfClass: SBServerSearchNavigationItem.class]) {
} else if ([navItem isKindOfClass: SBServerNavigationItem.class]) {
[searchToolbarItem setEnabled: YES];
[searchField setPlaceholderString: @"Server Search"];
} else {
Expand Down
38 changes: 35 additions & 3 deletions Submariner/SBNavigationItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,42 @@ import Cocoa
@objc class SBServerSearchNavigationItem: SBServerNavigationItem {
override var identifier: NSString { "ServerSearch" }

@objc var query: NSString
var query: SBSearchResult.QueryType

@objc init(server: SBServer, query: NSString) {
self.query = query
// HACK: Workaround for ObjC not having sum types (remove when we can just expose query to DatabaseController)
@objc var searchQuery: NSString? {
if case let .search(query) = self.query {
return query as NSString
}
return nil
}

@objc var topTracksForArtist: NSString? {
if case let .topTracksFor(artistName) = self.query {
return artistName as NSString
}
return nil
}

@objc var similarToArtist: SBArtist? {
if case let .similarTo(artist) = self.query {
return artist
}
return nil
}

@objc init(server: SBServer, query: String) {
self.query = .search(query: query)
super.init(server: server)
}

@objc init(server: SBServer, topTracksFor artistName: String) {
self.query = .topTracksFor(artistName: artistName)
super.init(server: server)
}

@objc init(server: SBServer, similarTo artist: SBArtist) {
self.query = .similarTo(artist: artist)
super.init(server: server)
}
}
Expand Down
12 changes: 9 additions & 3 deletions Submariner/SBSearchResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@
import Cocoa

@objc class SBSearchResult: NSObject {
enum QueryType {
case search(query: String)
case similarTo(artist: SBArtist)
case topTracksFor(artistName: String)
}

/// Used for bindings and contains the actual tracks fetched from `fetchTracks:`.
@objc var tracks: [SBTrack] = []
@objc let query: String // NSString
let query: QueryType

/// Contains the list of tracks to fetch on the main thread, and fills `tracks` from that.
///
Expand All @@ -21,13 +27,13 @@ import Cocoa
/// Updates the tracks array after getting the results.
///
/// This has to be done on the main thread, as the parse operation that builds the list runs off the main thread.
@objc func fetchTracks(managedObjectContext: NSManagedObjectContext) {
func fetchTracks(managedObjectContext: NSManagedObjectContext) {
tracks = tracksToFetch.map { trackID in
managedObjectContext.object(with: trackID) as! SBTrack
}
}

@objc(initWithQuery:) init(query: String) {
init(query: QueryType) {
self.query = query
super.init()
}
Expand Down
10 changes: 10 additions & 0 deletions Submariner/SBServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,16 @@ public class SBServer: SBResource {
OperationQueue.sharedServerQueue.addOperation(request)
}

@objc(getTopTracksForArtistName:) func getTopTracks(artistName: String) {
let request = SBSubsonicRequestOperation(server: self, request: .getTopTracks(artistName: artistName))
OperationQueue.sharedServerQueue.addOperation(request)
}

@objc func getSimilarTracks(to artist: SBArtist) {
let request = SBSubsonicRequestOperation(server: self, request: .getSimilarTracks(artist: artist))
OperationQueue.sharedServerQueue.addOperation(request)
}

// #MARK: - Subsonic Client (Rating)

@objc(setRating:forID:) func setRating(_ rating: Int, id: String) {
Expand Down
17 changes: 17 additions & 0 deletions Submariner/SBServerLibraryController.m
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,23 @@ - (IBAction)filterArtist:(id)sender {
}


- (IBAction)getTopTracksForSelectedArtist:(id)sender {
SBArtist *artist = [[self selectedArtists] firstObject];
if (artist) {
NSString *name = [artist itemName];
[self.databaseController getTopTracksFor: name];
}
}


- (IBAction)getSimilarTracksForSelectedArtist:(id)sender {
SBArtist *artist = [[self selectedArtists] firstObject];
if (artist) {
[self.databaseController getSimilarTracksTo: artist];
}
}


- (void)showTrackInLibrary:(SBTrack*)track {
[artistsController setSelectedObjects: @[track.album.artist]];
[artistsTableView scrollRowToVisible: [artistsTableView selectedRow]];
Expand Down
27 changes: 14 additions & 13 deletions Submariner/SBServerSearchController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@
import Cocoa

@objc class SBServerSearchController: SBServerViewController, NSTableViewDataSource {
@objc dynamic var searchResult: SBSearchResult?
@objc dynamic var searchResult: SBSearchResult? {
didSet {
switch self.searchResult?.query {
case .search(let query):
self.title = "Search Results for \(query)"
case .similarTo(let artist):
self.title = "Similar Tracks to \(artist.itemName ?? "(unknown artist)")"
case .topTracksFor(let artistName):
self.title = "Top Tracks for \(artistName)"
default:
self.title = "Search Results"
}
}
}

@IBOutlet var tracksTableView: NSTableView!
@IBOutlet var tracksController: NSArrayController!
Expand Down Expand Up @@ -40,18 +53,6 @@ import Cocoa
}
}

// this may be better
override var title: String? {
get {
if let searchResult = self.searchResult {
return "Search Results for \(searchResult.query)"
} else {
return "Search Results"
}
}
set {}
}

// #MARK: - Properties

// should be empty if no results
Expand Down
2 changes: 1 addition & 1 deletion Submariner/SBSubsonicParsingOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ class SBSubsonicParsingOperation: SBOperation, XMLParserDelegate {
postServerNotification(.SBSubsonicPlaylistsCreated)
case .getNowPlaying:
postServerNotification(.SBSubsonicNowPlayingUpdated)
case .search(_):
case .search(_), .getTopTracks(artistName: _), .getSimilarTracks(artist: _):
NotificationCenter.default.post(name: .SBSubsonicSearchResultUpdated, object: currentSearch)
case .getPodcasts:
postServerNotification(.SBSubsonicPodcastsUpdated)
Expand Down
14 changes: 13 additions & 1 deletion Submariner/SBSubsonicRequestOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ class SBSubsonicRequestOperation: SBOperation {
parameters["songCount"] = "100" // XXX: Configurable? Pagination?
url = URL.URLWith(string: server.url, command: "rest/search3.view", parameters: parameters)
customization = { operation in
operation.currentSearch = SBSearchResult(query: query)
operation.currentSearch = SBSearchResult(query: .search(query: query))
}
case .setRating(id: let id, rating: let rating):
parameters["rating"] = String(rating)
Expand Down Expand Up @@ -270,6 +270,18 @@ class SBSubsonicRequestOperation: SBOperation {
(albums.map { album in URLQueryItem(name: "albumId", value: album.itemId) } ) +
(artists.map { artist in URLQueryItem(name: "artistId", value: artist.itemId) } )
url = URL.URLWith(string: server.url, command: "rest/unstar.view", queryItems: allParams)
case .getTopTracks(let artistName):
parameters["artist"] = artistName
url = URL.URLWith(string: server.url, command: "rest/getTopSongs.view", parameters: parameters)
customization = { operation in
operation.currentSearch = SBSearchResult(query: .topTracksFor(artistName: artistName))
}
case .getSimilarTracks(let artist):
parameters["id"] = artist.itemId
url = URL.URLWith(string: server.url, command: "rest/getSimilarSongs2.view", parameters: parameters)
customization = { operation in
operation.currentSearch = SBSearchResult(query: .similarTo(artist: artist))
}
}
}
}
2 changes: 2 additions & 0 deletions Submariner/SBSubsonicRequestType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ enum SBSubsonicRequestType: Equatable {
case getDirectory(id: String)
case star(tracks: [SBTrack], albums: [SBAlbum], artists: [SBArtist], directories: [SBDirectory])
case unstar(tracks: [SBTrack], albums: [SBAlbum], artists: [SBArtist], directories: [SBDirectory])
case getTopTracks(artistName: String)
case getSimilarTracks(artist: SBArtist)
}

@objc enum SBAlbumListType: Int {
Expand Down
6 changes: 6 additions & 0 deletions Submariner/SBViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ - (NSInteger)selectedTrackRow {
}


- (void)setTitle:(NSString *)title {
[super setTitle:title];
[[NSNotificationCenter defaultCenter] postNotificationName:@"SBTitleUpdated" object:self];
}


#pragma mark - IBActions

#pragma mark Playing
Expand Down
Loading

0 comments on commit f5b32e6

Please sign in to comment.