Skip to content

Commit a6cc391

Browse files
authored
Merge pull request #774 from kagemomiji/issue662-fix-playlist-transaction-issue
#662 Fixed the logic of getting files from playlist
2 parents 8facb62 + ea852e7 commit a6cc391

File tree

9 files changed

+325
-94
lines changed

9 files changed

+325
-94
lines changed

.github/workflows/trivy_scan.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/checkout@v5
2020

2121
- name: Run Trivy vulnerability scanner
22-
uses: aquasecurity/trivy-action@0.32.0
22+
uses: aquasecurity/trivy-action@0.33.0
2323
with:
2424
scan-type: 'fs'
2525
format: 'sarif'

airsonic-main/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<dependency>
5353
<groupId>io.dropwizard.metrics</groupId>
5454
<artifactId>metrics-jmx</artifactId>
55-
<version>4.2.33</version>
55+
<version>4.2.35</version>
5656
</dependency>
5757
<!-- END Metrics -->
5858

airsonic-main/src/main/java/org/airsonic/player/ajax/PlaylistWSController.java

Lines changed: 8 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
package org.airsonic.player.ajax;
22

33
import org.airsonic.player.domain.MediaFile;
4-
import org.airsonic.player.domain.MusicFolder;
5-
import org.airsonic.player.domain.Player;
64
import org.airsonic.player.domain.Playlist;
75
import org.airsonic.player.i18n.LocaleResolver;
86
import org.airsonic.player.service.MediaFileService;
9-
import org.airsonic.player.service.MediaFolderService;
10-
import org.airsonic.player.service.PlayerService;
117
import org.airsonic.player.service.PlaylistService;
128
import org.springframework.beans.factory.annotation.Autowired;
139
import org.springframework.messaging.handler.annotation.DestinationVariable;
@@ -26,7 +22,6 @@
2622
import java.util.List;
2723
import java.util.Locale;
2824
import java.util.Objects;
29-
import java.util.ResourceBundle;
3025
import java.util.stream.Collectors;
3126
import java.util.stream.Stream;
3227

@@ -38,10 +33,6 @@ public class PlaylistWSController {
3833
@Autowired
3934
private PlaylistService playlistService;
4035
@Autowired
41-
private MediaFolderService mediaFolderService;
42-
@Autowired
43-
private PlayerService playerService;
44-
@Autowired
4536
private LocaleResolver localeResolver;
4637

4738
@SubscribeMapping("/readable")
@@ -55,66 +46,34 @@ public List<Playlist> getWritablePlaylists(Principal p) {
5546
return playlistService.getWritablePlaylistsForUser(p.getName());
5647
}
5748

58-
/**
59-
* Creates a playlist and broadcasts it to all users that have access to it.
60-
*
61-
* @param playlist the playlist to create
62-
* @return the id of the created playlist
63-
*/
64-
private Playlist createPlaylist(String name, boolean shared, String username) {
65-
Playlist result = playlistService.createPlaylist(name, shared, username);
66-
playlistService.broadcast(result);
67-
return result;
68-
}
69-
7049
@MessageMapping("/create/empty")
7150
@SendToUser(broadcast = false)
7251
public int createEmptyPlaylist(Principal p) {
7352
Locale locale = localeResolver.resolveLocale(p.getName());
7453
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).withLocale(locale);
7554
Instant now = Instant.now();
76-
Playlist result = createPlaylist(dateFormat.format(now.atZone(ZoneId.systemDefault())), false, p.getName());
55+
Playlist result = playlistService.createPlaylist(dateFormat.format(now.atZone(ZoneId.systemDefault())), false, p.getName());
56+
playlistService.broadcast(result);
7757
return result.getId();
7858
}
7959

8060
@MessageMapping("/create/starred")
8161
@SendToUser(broadcast = false)
8262
public int createPlaylistForStarredSongs(Principal p) {
83-
Locale locale = localeResolver.resolveLocale(p.getName());
84-
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).withLocale(locale);
85-
86-
ResourceBundle bundle = ResourceBundle.getBundle("org.airsonic.player.i18n.ResourceBundle", locale);
87-
Instant now = Instant.now();
88-
String name = bundle.getString("top.starred") + " " + dateFormat.format(now.atZone(ZoneId.systemDefault()));
8963
String username = p.getName();
90-
91-
Playlist result = createPlaylist(name, false, username);
92-
List<MusicFolder> musicFolders = mediaFolderService.getMusicFoldersForUser(username);
93-
List<MediaFile> songs = mediaFileService.getStarredSongs(0, Integer.MAX_VALUE, username, musicFolders);
94-
Integer playlistId = result.getId();
95-
playlistService.setFilesInPlaylist(playlistId, songs);
64+
Locale locale = localeResolver.resolveLocale(username);
65+
Integer playlistId = playlistService.createPlaylistForStarredSongs(username, locale);
9666
playlistService.broadcastFileChange(playlistId, false, true);
97-
return result.getId();
67+
return playlistId;
9868
}
9969

10070
@MessageMapping("/create/playqueue")
10171
@SendToUser(broadcast = false)
10272
public int createPlaylistForPlayQueue(Principal p, Integer playerId) throws Exception {
103-
Player player = playerService.getPlayerById(playerId);
104-
Locale locale = localeResolver.resolveLocale(p.getName());
105-
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).withLocale(locale);
106-
107-
Instant now = Instant.now();
108-
Playlist playlist = new Playlist();
109-
playlist.setUsername(p.getName());
110-
playlist.setShared(false);
111-
playlist.setName(dateFormat.format(now.atZone(ZoneId.systemDefault())));
112-
113-
Playlist result = createPlaylist(dateFormat.format(now.atZone(ZoneId.systemDefault())), false, p.getName());
114-
Integer playlistId = result.getId();
115-
playlistService.setFilesInPlaylist(playlistId, player.getPlayQueue().getFiles());
73+
String username = p.getName();
74+
Locale locale = localeResolver.resolveLocale(username);
75+
Integer playlistId = playlistService.createPlaylistForPlayQueue(playerId, username, locale);
11676
playlistService.broadcastFileChange(playlistId, false, true);
117-
11877
return playlistId;
11978
}
12079

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,40 @@
1+
/*
2+
This file is part of Airsonic.
3+
4+
Airsonic is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
Airsonic is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with Airsonic. If not, see <http://www.gnu.org/licenses/>.
16+
17+
Copyright 2025 (C) Y.Tory
18+
*/
119
package org.airsonic.player.repository;
220

21+
import org.airsonic.player.domain.MediaFile;
322
import org.airsonic.player.domain.PlaylistMediaFile;
423
import org.springframework.data.jpa.repository.JpaRepository;
24+
import org.springframework.data.jpa.repository.Query;
25+
import org.springframework.data.repository.query.Param;
526
import org.springframework.stereotype.Repository;
627

28+
import java.util.List;
29+
730
@Repository
831
public interface PlaylistMediaFileRepository extends JpaRepository<PlaylistMediaFile, Integer> {
32+
33+
@Query("""
34+
select pmf.mediaFile
35+
from PlaylistMediaFile pmf
36+
where pmf.playlist.id = :playlistId
37+
order by pmf.orderIndex
38+
""")
39+
List<MediaFile> findMediaFilesByPlaylistId(@Param("playlistId") Integer playlistId);
940
}

airsonic-main/src/main/java/org/airsonic/player/service/PlaylistService.java

Lines changed: 78 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,20 @@
2020
package org.airsonic.player.service;
2121

2222
import org.airsonic.player.domain.MediaFile;
23+
import org.airsonic.player.domain.MusicFolder;
2324
import org.airsonic.player.domain.PlayQueue;
25+
import org.airsonic.player.domain.Player;
2426
import org.airsonic.player.domain.Playlist;
2527
import org.airsonic.player.domain.PlaylistMediaFile;
2628
import org.airsonic.player.domain.User;
29+
import org.airsonic.player.repository.PlaylistMediaFileRepository;
2730
import org.airsonic.player.repository.PlaylistRepository;
2831
import org.airsonic.player.repository.UserRepository;
2932
import org.airsonic.player.service.cache.PlaylistCache;
3033
import org.airsonic.player.service.websocket.AsyncWebSocketClient;
3134
import org.airsonic.player.util.LambdaUtils;
3235
import org.slf4j.Logger;
3336
import org.slf4j.LoggerFactory;
34-
import org.springframework.beans.factory.annotation.Autowired;
3537
import org.springframework.data.domain.Sort;
3638
import org.springframework.stereotype.Service;
3739
import org.springframework.transaction.annotation.Transactional;
@@ -40,6 +42,9 @@
4042
import jakarta.annotation.Nonnull;
4143

4244
import java.time.Instant;
45+
import java.time.ZoneId;
46+
import java.time.format.DateTimeFormatter;
47+
import java.time.format.FormatStyle;
4348
import java.util.*;
4449
import java.util.stream.Collectors;
4550
import java.util.stream.Stream;
@@ -55,14 +60,34 @@ public class PlaylistService {
5560

5661
private static final Logger LOG = LoggerFactory.getLogger(PlaylistService.class);
5762

58-
@Autowired
59-
private UserRepository userRepository;
60-
@Autowired
61-
private PlaylistRepository playlistRepository;
62-
@Autowired
63-
private AsyncWebSocketClient asyncWebSocketClient;
64-
@Autowired
65-
private PlaylistCache playlistCache;
63+
private final UserRepository userRepository;
64+
private final PlaylistRepository playlistRepository;
65+
private final AsyncWebSocketClient asyncWebSocketClient;
66+
private final PlaylistCache playlistCache;
67+
private final PlaylistMediaFileRepository playlistMediaFileRepository;
68+
private final MediaFileService mediaFileService;
69+
private final MediaFolderService mediaFolderService;
70+
private final PlayerService playerService;
71+
72+
public PlaylistService(
73+
UserRepository userRepository,
74+
PlaylistRepository playlistRepository,
75+
AsyncWebSocketClient asyncWebSocketClient,
76+
PlaylistCache playlistCache,
77+
PlaylistMediaFileRepository playlistMediaFileRepository,
78+
PlayerService playerService,
79+
MediaFolderService mediaFolderService,
80+
MediaFileService mediaFileService
81+
) {
82+
this.userRepository = userRepository;
83+
this.playlistRepository = playlistRepository;
84+
this.asyncWebSocketClient = asyncWebSocketClient;
85+
this.playlistCache = playlistCache;
86+
this.playlistMediaFileRepository = playlistMediaFileRepository;
87+
this.playerService = playerService;
88+
this.mediaFolderService = mediaFolderService;
89+
this.mediaFileService = mediaFileService;
90+
}
6691

6792

6893
/**
@@ -142,25 +167,18 @@ public List<String> getPlaylistUsers(Integer playlistId) {
142167
return result;
143168
}
144169

170+
@Transactional(readOnly = true)
145171
public List<MediaFile> getFilesInPlaylist(int id) {
146172
return getFilesInPlaylist(id, false);
147173
}
148174

149175

150176
@Transactional(readOnly = true)
151177
public List<MediaFile> getFilesInPlaylist(int id, boolean includeNotPresent) {
152-
return playlistRepository.findById(id).map(p -> {
153-
return p.getPlaylistMediaFiles().stream()
154-
.map(PlaylistMediaFile::getMediaFile)
178+
return playlistMediaFileRepository.findMediaFilesByPlaylistId(id).stream()
155179
.filter(Objects::nonNull)
156180
.filter(x -> x.isPresent() || includeNotPresent)
157181
.collect(Collectors.toList());
158-
}).orElseGet(
159-
() -> {
160-
LOG.warn("Playlist {} not found", id);
161-
return new ArrayList<>();
162-
}
163-
);
164182
}
165183

166184
private List<MediaFile> filterNoDurationFiles(List<MediaFile> files) {
@@ -189,7 +207,6 @@ public Playlist setFilesInPlaylist(int id, List<MediaFile> files) {
189207
});
190208
}
191209

192-
193210
private Playlist setFilesInPlaylist(Playlist playlist, List<MediaFile> files) {
194211

195212
List<MediaFile> filteredFiles = filterNoDurationFiles(files);
@@ -431,6 +448,48 @@ public void broadcastFileChange(Integer id, boolean isShared, boolean filesChang
431448
});
432449
}
433450

451+
/**
452+
* Creates a new playlist for the specified play queue.
453+
*
454+
* @param playerId the ID of the player
455+
* @param username the username of the user creating the playlist
456+
* @param locale the locale to use for formatting
457+
* @return the ID of the created playlist
458+
*/
459+
@Transactional
460+
public Integer createPlaylistForPlayQueue(@Nonnull Integer playerId, @Nonnull String username, Locale locale) {
461+
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).withLocale(locale);
462+
Instant now = Instant.now();
463+
String name = dateFormat.format(now.atZone(ZoneId.systemDefault()));
464+
465+
Playlist playlist = createPlaylist(name, false, username);
466+
broadcast(playlist);
467+
468+
Player player = playerService.getPlayerById(playerId);
469+
playlist = setFilesInPlaylist(playlist, player.getPlayQueue().getFiles());
470+
471+
playlistRepository.saveAndFlush(playlist);
472+
return playlist.getId();
473+
}
474+
475+
@Transactional
476+
public Integer createPlaylistForStarredSongs(@Nonnull String username, Locale locale) {
477+
478+
DateTimeFormatter dateFormat = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).withLocale(locale);
479+
ResourceBundle bundle = ResourceBundle.getBundle("org.airsonic.player.i18n.ResourceBundle", locale);
480+
Instant now = Instant.now();
481+
String name = bundle.getString("top.starred") + " " + dateFormat.format(now.atZone(ZoneId.systemDefault()));
482+
483+
Playlist playlist = createPlaylist(name, false, username);
484+
broadcast(playlist);
485+
486+
List<MusicFolder> musicFolders = mediaFolderService.getMusicFoldersForUser(username);
487+
List<MediaFile> musicFiles = mediaFileService.getStarredSongs(0, Integer.MAX_VALUE, username, musicFolders);
488+
setFilesInPlaylist(playlist, musicFiles);
489+
Integer playlistId = playlist.getId();
490+
return playlistId;
491+
}
492+
434493
public static class BroadcastedPlaylist extends Playlist {
435494
private final boolean filesChanged;
436495

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.airsonic.player.service;
2+
3+
import org.springframework.stereotype.Service;
4+
5+
@Service
6+
public class StreamService {
7+
8+
}

0 commit comments

Comments
 (0)