From 4ebc7a903c3b4551b65963781c798fc7ba299280 Mon Sep 17 00:00:00 2001 From: "Y.Tory" <5343692+kagemomiji@users.noreply.github.com> Date: Sat, 22 Jun 2024 14:14:15 +0000 Subject: [PATCH] kagemomiji/airsonic-advanced#318 Changed search results to show folder --- .../player/command/SearchCommand.java | 29 ++-- .../player/command/SearchResultAlbum.java | 81 ++++++++++ .../player/command/SearchResultArtist.java | 71 +++++++++ .../player/controller/SearchController.java | 146 +++++++++++++----- .../src/main/resources/templates/search.html | 78 +++++++--- 5 files changed, 330 insertions(+), 75 deletions(-) create mode 100644 airsonic-main/src/main/java/org/airsonic/player/command/SearchResultAlbum.java create mode 100644 airsonic-main/src/main/java/org/airsonic/player/command/SearchResultArtist.java diff --git a/airsonic-main/src/main/java/org/airsonic/player/command/SearchCommand.java b/airsonic-main/src/main/java/org/airsonic/player/command/SearchCommand.java index 4d3dc25c1..8499cb483 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/command/SearchCommand.java +++ b/airsonic-main/src/main/java/org/airsonic/player/command/SearchCommand.java @@ -14,6 +14,7 @@ You should have received a copy of the GNU General Public License along with Airsonic. If not, see . + Copyright 2024 (C) Airsonic Authors Copyright 2016 (C) Airsonic Authors Based upon Subsonic, Copyright 2009 (C) Sindre Mehus */ @@ -23,11 +24,9 @@ import org.airsonic.player.domain.MediaFile; import org.airsonic.player.domain.Player; import org.airsonic.player.domain.User; -import org.apache.commons.lang3.tuple.Pair; +import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Set; /** * Command used in {@link SearchController}. @@ -37,10 +36,10 @@ public class SearchCommand { private String query; - private Map> artists; - private Map> artistsFromTag; - private Map, Set> albums; - private Map, Set> albumsFromTag; + private List artists = new ArrayList<>(); + private List artistsFromTag = new ArrayList<>(); + private List albums; + private List albumsFromTag; private List songs; private boolean isIndexBeingCreated; private User user; @@ -63,35 +62,35 @@ public void setIndexBeingCreated(boolean indexBeingCreated) { isIndexBeingCreated = indexBeingCreated; } - public Map> getArtists() { + public List getArtists() { return artists; } - public void setArtists(Map> artists) { + public void setArtists(List artists) { this.artists = artists; } - public Map> getArtistsFromTag() { + public List getArtistsFromTag() { return artistsFromTag; } - public void setArtistsFromTag(Map> artistsFromTag) { + public void setArtistsFromTag(List artistsFromTag) { this.artistsFromTag = artistsFromTag; } - public Map, Set> getAlbums() { + public List getAlbums() { return albums; } - public void setAlbums(Map, Set> albums) { + public void setAlbums(List albums) { this.albums = albums; } - public Map, Set> getAlbumsFromTag() { + public List getAlbumsFromTag() { return albumsFromTag; } - public void setAlbumsFromTag(Map, Set> albumsFromTag) { + public void setAlbumsFromTag(List albumsFromTag) { this.albumsFromTag = albumsFromTag; } diff --git a/airsonic-main/src/main/java/org/airsonic/player/command/SearchResultAlbum.java b/airsonic-main/src/main/java/org/airsonic/player/command/SearchResultAlbum.java new file mode 100644 index 000000000..2a5b75f7b --- /dev/null +++ b/airsonic-main/src/main/java/org/airsonic/player/command/SearchResultAlbum.java @@ -0,0 +1,81 @@ +/* + This file is part of Airsonic. + + Airsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Airsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Airsonic. If not, see . + + Copyright 2024 (C) Y.Tory + */ +package org.airsonic.player.command; + +import org.airsonic.player.domain.MusicFolder; + +import java.util.HashSet; +import java.util.Set; + +public class SearchResultAlbum { + + private String album; + + private String artist; + + private MusicFolder folder; + + private Set mediaFileIds = new HashSet<>(); + + public SearchResultAlbum(String album, String artist, MusicFolder folder) { + this.album = album; + this.artist = artist; + this.folder = folder; + } + + public String getAlbum() { + return album; + } + + public void setAlbum(String album) { + this.album = album; + } + + public String getArtist() { + return artist; + } + + public void setArtist(String artist) { + this.artist = artist; + } + + public MusicFolder getFolder() { + return folder; + } + + public void setFolder(MusicFolder folder) { + this.folder = folder; + } + + public Set getMediaFileIds() { + return mediaFileIds; + } + + public void setMediaFileIds(Set mediaFileIds) { + this.mediaFileIds = mediaFileIds; + } + + public void addMediaFileId(Integer mediaFileId) { + this.mediaFileIds.add(mediaFileId); + } + + public void addMediaFileIds(Set mediaFileIds) { + this.mediaFileIds.addAll(mediaFileIds); + } +} diff --git a/airsonic-main/src/main/java/org/airsonic/player/command/SearchResultArtist.java b/airsonic-main/src/main/java/org/airsonic/player/command/SearchResultArtist.java new file mode 100644 index 000000000..77fd1d17a --- /dev/null +++ b/airsonic-main/src/main/java/org/airsonic/player/command/SearchResultArtist.java @@ -0,0 +1,71 @@ +/* + This file is part of Airsonic. + + Airsonic is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Airsonic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Airsonic. If not, see . + + Copyright 2024 (C) Y.Tory + */ +package org.airsonic.player.command; + +import org.airsonic.player.domain.MusicFolder; + +import java.util.HashSet; +import java.util.Set; + +public class SearchResultArtist { + + private String artist; + + private MusicFolder folder; + + private Set mediaFileIds = new HashSet<>(); + + public SearchResultArtist(String artist, MusicFolder folder) { + this.artist = artist; + this.folder = folder; + } + + public String getArtist() { + return artist; + } + + public void setArtist(String artist) { + this.artist = artist; + } + + public MusicFolder getFolder() { + return folder; + } + + public void setFolder(MusicFolder folder) { + this.folder = folder; + } + + public Set getMediaFileIds() { + return mediaFileIds; + } + + public void setMediaFileIds(Set mediaFileIds) { + this.mediaFileIds = mediaFileIds; + } + + public void addMediaFileId(Integer mediaFileId) { + this.mediaFileIds.add(mediaFileId); + } + + public void addMediaFileIds(Set mediaFileIds) { + this.mediaFileIds.addAll(mediaFileIds); + } + +} diff --git a/airsonic-main/src/main/java/org/airsonic/player/controller/SearchController.java b/airsonic-main/src/main/java/org/airsonic/player/controller/SearchController.java index 06712feaf..0227613f6 100644 --- a/airsonic-main/src/main/java/org/airsonic/player/controller/SearchController.java +++ b/airsonic-main/src/main/java/org/airsonic/player/controller/SearchController.java @@ -20,6 +20,8 @@ package org.airsonic.player.controller; import org.airsonic.player.command.SearchCommand; +import org.airsonic.player.command.SearchResultAlbum; +import org.airsonic.player.command.SearchResultArtist; import org.airsonic.player.domain.*; import org.airsonic.player.service.AlbumService; import org.airsonic.player.service.MediaFileService; @@ -31,6 +33,7 @@ import org.airsonic.player.service.search.IndexType; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.tuple.Triple; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -42,14 +45,12 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.mapping; -import static java.util.stream.Collectors.toSet; - /** * Controller for the search page. * @@ -103,40 +104,13 @@ protected String onSubmit(HttpServletRequest request, HttpServletResponse respon SearchResult artists = searchService.search(criteria, musicFolders, IndexType.ARTIST); SearchResult artistsId3 = searchService.search(criteria, musicFolders, IndexType.ARTIST_ID3); - artistsId3.getArtists().stream().map(Artist::getName).flatMap(ar -> albumService.getAlbumsByArtist(ar, musicFolders).stream().map(al -> mediaFileService.getMediaFile(al.getPath(), al.getFolder())).filter(Objects::nonNull).map(MediaFile::getId).map(m -> Pair.of(ar, m))).collect(groupingBy(p -> p.getKey(), mapping(p -> p.getValue(), toSet()))); - artists.getMediaFiles().stream().map(m -> Pair.of(Optional.ofNullable(m.getArtist()).or(() -> Optional.ofNullable(m.getAlbumArtist())).orElse("(Unknown)"), m.getId())); - command.setArtists( - artists.getMediaFiles().stream() - .map(m -> Pair.of( - Optional.ofNullable(m.getArtist()) - .or(() -> Optional.ofNullable(m.getAlbumArtist())) - .orElse("(Unknown)"), - m.getId())) - .collect(groupingBy(p -> p.getKey(), mapping(p -> p.getValue(), toSet())))); - command.setArtistsFromTag( - artistsId3.getArtists().stream() - .map(Artist::getName) - .flatMap(ar -> albumService.getAlbumsByArtist(ar, musicFolders).stream() - .map(al -> mediaFileService.getMediaFile(al.getPath(), al.getFolder())) - .filter(Objects::nonNull) - .map(MediaFile::getId) - .map(m -> Pair.of(ar, m))) - .collect(groupingBy(p -> p.getKey(), mapping(p -> p.getValue(), toSet()))) - ); + command.setArtists(createArtistResults(artists)); + command.setArtistsFromTag(createArtistResultsFromId3Tag(artistsId3, musicFolders)); SearchResult albums = searchService.search(criteria, musicFolders, IndexType.ALBUM); SearchResult albumsId3 = searchService.search(criteria, musicFolders, IndexType.ALBUM_ID3); - command.setAlbums( - albums.getMediaFiles().stream() - .map(m -> Pair.of(Pair.of(m.getAlbumName(), m.getAlbumArtist()), m.getId())) - .collect(groupingBy(p -> p.getKey(), mapping(p -> p.getValue(), toSet())))); - command.setAlbumsFromTag( - albumsId3.getAlbums().stream() - .map(a -> Optional.ofNullable(mediaFileService.getMediaFile(a.getPath(), a.getFolder())) - .map(m -> Pair.of(Pair.of(a.getName(), a.getArtist()), m.getId())).orElse(null)) - .filter(Objects::nonNull) - .collect(groupingBy(p -> p.getKey(), mapping(p -> p.getValue(), toSet()))) - ); + command.setAlbums(createAlbumResults(albums)); + command.setAlbumsFromTag(createAlbumResultsFromId3Tag(albumsId3)); SearchResult songs = searchService.search(criteria, musicFolders, IndexType.SONG); command.setSongs(songs.getMediaFiles()); @@ -146,4 +120,106 @@ protected String onSubmit(HttpServletRequest request, HttpServletResponse respon return "search"; } + + /** + * Create a list of search result artists from the search result. + * + * @param artists the search result to create the artists from (media files) + * @return a list of search result artists + */ + private List createArtistResults(SearchResult artists) { + + Map, SearchResultArtist> artistMap = new LinkedHashMap<>(); + + artists.getMediaFiles().stream().forEach(m -> { + String artist = Optional.ofNullable(m.getArtist()) + .or(() -> Optional.ofNullable(m.getAlbumArtist())) + .orElse("(Unknown)"); + SearchResultArtist artistResult = artistMap.computeIfAbsent(Pair.of(artist, m.getFolder().getId()), + k -> new SearchResultArtist(artist, m.getFolder())); + artistResult.addMediaFileId(m.getId()); + }); + + return artistMap.values().stream().toList(); + } + + /** + * Create a list of search result artists from the search result. + * + * @param artistsId3 the search result to create the artists from (ID3 tags) + * @param musicFolders the music folders to search for the media files + * @return a list of search result artists + */ + private List createArtistResultsFromId3Tag(SearchResult artistsId3, + List musicFolders) { + + Map, SearchResultArtist> artistMapFromTag = new LinkedHashMap<>(); + + artistsId3.getArtists().stream() + .map(Artist::getName) + .flatMap(ar -> albumService.getAlbumsByArtist(ar, musicFolders).stream() + .map(al -> mediaFileService.getMediaFile(al.getPath(), al.getFolder())) + .filter(Objects::nonNull) + .map(m -> Pair.of(ar, m))) + .forEach(p -> { + SearchResultArtist artistResult = artistMapFromTag.computeIfAbsent( + Pair.of(p.getKey(), p.getValue().getFolder().getId()), + k -> new SearchResultArtist(p.getKey(), p.getValue().getFolder())); + artistResult.addMediaFileId(p.getValue().getId()); + }); + + return artistMapFromTag.values().stream().toList(); + } + + /** + * Create a list of search result albums from the search result. + * + * @param albums the search result to create the albums from (media files) + * @return a list of search result albums + */ + private List createAlbumResults(SearchResult albums) { + + Map, SearchResultAlbum> albumMap = new LinkedHashMap<>(); + + albums.getMediaFiles().stream().forEach(m -> { + String album = m.getAlbumName(); + String artist = Optional.ofNullable(m.getArtist()) + .or(() -> Optional.ofNullable(m.getAlbumArtist())) + .orElse("(Unknown)"); + SearchResultAlbum albumResult = albumMap.computeIfAbsent(Triple.of(album, artist, m.getFolder().getId()), + k -> new SearchResultAlbum(album, artist, m.getFolder())); + albumResult.addMediaFileId(m.getId()); + }); + + return albumMap.values().stream().toList(); + } + + /** + * Create a list of search result albums from the search result. + * + * @param albums the search result to create the albums from (media files) + * @return a list of search result albums + */ + private List createAlbumResultsFromId3Tag(SearchResult albumsId3) { + + Map, SearchResultAlbum> albumId3Map = new LinkedHashMap<>(); + + albumsId3.getAlbums().stream() + .forEach(a -> { + MediaFile mediaFile = mediaFileService.getMediaFile(a.getPath(), a.getFolder()); + if (mediaFile == null) { + return; + } + String album = a.getName(); + String artist = a.getArtist(); + SearchResultAlbum albumResult = albumId3Map.computeIfAbsent( + Triple.of(album, artist, a.getFolder().getId()), + k -> new SearchResultAlbum(album, artist, a.getFolder())); + albumResult.addMediaFileId(mediaFile.getId()); + }); + + return albumId3Map.values().stream().toList(); + } + + } diff --git a/airsonic-main/src/main/resources/templates/search.html b/airsonic-main/src/main/resources/templates/search.html index 33e75d408..9a2984640 100644 --- a/airsonic-main/src/main/resources/templates/search.html +++ b/airsonic-main/src/main/resources/templates/search.html @@ -23,9 +23,12 @@ $('.songRow').show();$('#moreSongs').hide(); } + function init() { + feather.replace(); + } - +

@@ -36,20 +39,27 @@

+

+ + + + + - - + - + + - +
@@ -59,17 +69,21 @@

+ + + + + - - - + + @@ -83,42 +97,51 @@

+ + + + + - + - - - + +
-
-
+
-

+

+

+ + + + + - - + - + + - - +
@@ -128,17 +151,22 @@

- + + + + + + - - + +