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 @@
+
+ |
+ |
+ |
+
-
+
- |
-
- |
- |
+ |
+ |
-
+
-
+
+
+
+ |
+ |
+ |
+
-
-
+
- |
+ |
+ |
-
-
+