My fork of airsonic with experimental fixes and improvements. See branch "custom"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
airsonic-custom/airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java

2389 lines
106 KiB

/*
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 <http://www.gnu.org/licenses/>.
Copyright 2016 (C) Airsonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.airsonic.player.controller;
import org.airsonic.player.ajax.LyricsInfo;
import org.airsonic.player.ajax.LyricsService;
import org.airsonic.player.ajax.PlayQueueService;
import org.airsonic.player.command.UserSettingsCommand;
import org.airsonic.player.dao.AlbumDao;
import org.airsonic.player.dao.ArtistDao;
import org.airsonic.player.dao.MediaFileDao;
import org.airsonic.player.dao.PlayQueueDao;
import org.airsonic.player.domain.*;
import org.airsonic.player.domain.Bookmark;
import org.airsonic.player.domain.PlayQueue;
import org.airsonic.player.i18n.LocaleResolver;
import org.airsonic.player.service.*;
import org.airsonic.player.service.search.IndexType;
import org.airsonic.player.util.StringUtil;
import org.airsonic.player.util.Util;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.subsonic.restapi.*;
import org.subsonic.restapi.PodcastStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.xml.datatype.XMLGregorianCalendar;
import java.util.*;
import static org.airsonic.player.security.RESTRequestParameterProcessingFilter.decrypt;
import static org.springframework.web.bind.ServletRequestUtils.*;
/**
* Multi-controller used for the REST API.
* <p/>
* For documentation, please refer to api.jsp.
* <p/>
* Note: Exceptions thrown from the methods are intercepted by RESTFilter.
*
* @author Sindre Mehus
*/
@Controller
@RequestMapping(value = "/rest", method = {RequestMethod.GET, RequestMethod.POST})
public class SubsonicRESTController {
private static final Logger LOG = LoggerFactory.getLogger(SubsonicRESTController.class);
@Autowired
private SettingsService settingsService;
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private MediaFileService mediaFileService;
@Autowired
private LastFmService lastFmService;
@Autowired
private MusicIndexService musicIndexService;
@Autowired
private TranscodingService transcodingService;
@Autowired
private DownloadController downloadController;
@Autowired
private CoverArtController coverArtController;
@Autowired
private AvatarController avatarController;
@Autowired
private UserSettingsController userSettingsController;
@Autowired
private LeftController leftController;
@Autowired
private StatusService statusService;
@Autowired
private StreamController streamController;
@Autowired
private HLSController hlsController;
@Autowired
private ShareService shareService;
@Autowired
private PlaylistService playlistService;
@Autowired
private LyricsService lyricsService;
@Autowired
private PlayQueueService playQueueService;
@Autowired
private JukeboxService jukeboxService;
@Autowired
private AudioScrobblerService audioScrobblerService;
@Autowired
private PodcastService podcastService;
@Autowired
private RatingService ratingService;
@Autowired
private SearchService searchService;
@Autowired
private MediaFileDao mediaFileDao;
@Autowired
private ArtistDao artistDao;
@Autowired
private AlbumDao albumDao;
@Autowired
private BookmarkService bookmarkService;
@Autowired
private PlayQueueDao playQueueDao;
@Autowired
private MediaScannerService mediaScannerService;
@Autowired
private LocaleResolver localeResolver;
private final JAXBWriter jaxbWriter = new JAXBWriter();
private static final String NOT_YET_IMPLEMENTED = "Not yet implemented";
private static final String NO_LONGER_SUPPORTED = "No longer supported";
@ExceptionHandler(MissingServletRequestParameterException.class)
public void handleMissingRequestParam(HttpServletRequest request,
HttpServletResponse response,
MissingServletRequestParameterException exception) {
error(request, response, ErrorCode.MISSING_PARAMETER, "Required param (" + exception.getParameterName() + ") is missing");
}
@RequestMapping("/ping")
public void ping(HttpServletRequest request, HttpServletResponse response) {
Response res = createResponse();
jaxbWriter.writeResponse(request, response, res);
}
/**
* CAUTION : this method is required by mobile applications and must not be removed.
*/
@RequestMapping("/getLicense")
public void getLicense(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
License license = new License();
license.setEmail("airsonic@github.com");
license.setValid(true);
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.YEAR, 100);
XMLGregorianCalendar farFuture = jaxbWriter.convertCalendar(calendar);
license.setLicenseExpires(farFuture);
license.setTrialExpires(farFuture);
Response res = createResponse();
res.setLicense(license);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getMusicFolders")
public void getMusicFolders(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
MusicFolders musicFolders = new MusicFolders();
String username = securityService.getCurrentUsername(request);
for (org.airsonic.player.domain.MusicFolder musicFolder : settingsService.getMusicFoldersForUser(username)) {
org.subsonic.restapi.MusicFolder mf = new org.subsonic.restapi.MusicFolder();
mf.setId(musicFolder.getId());
mf.setName(musicFolder.getName());
musicFolders.getMusicFolder().add(mf);
}
Response res = createResponse();
res.setMusicFolders(musicFolders);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getIndexes")
public void getIndexes(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Response res = createResponse();
String username = securityService.getCurrentUser(request).getUsername();
long ifModifiedSince = getLongParameter(request, "ifModifiedSince", 0L);
long lastModified = leftController.getLastModified(request);
if (lastModified <= ifModifiedSince) {
jaxbWriter.writeResponse(request, response, res);
return;
}
Indexes indexes = new Indexes();
indexes.setLastModified(lastModified);
indexes.setIgnoredArticles(settingsService.getIgnoredArticles());
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
if (musicFolderId != null) {
for (org.airsonic.player.domain.MusicFolder musicFolder : musicFolders) {
if (musicFolderId.equals(musicFolder.getId())) {
musicFolders = Collections.singletonList(musicFolder);
break;
}
}
}
for (MediaFile shortcut : musicIndexService.getShortcuts(musicFolders)) {
indexes.getShortcut().add(createJaxbArtist(shortcut, username));
}
MusicFolderContent musicFolderContent = musicIndexService.getMusicFolderContent(musicFolders, false);
for (Map.Entry<MusicIndex, List<MusicIndex.SortableArtistWithMediaFiles>> entry : musicFolderContent.getIndexedArtists().entrySet()) {
Index index = new Index();
indexes.getIndex().add(index);
index.setName(entry.getKey().getIndex());
for (MusicIndex.SortableArtistWithMediaFiles artist : entry.getValue()) {
for (MediaFile mediaFile : artist.getMediaFiles()) {
if (mediaFile.isDirectory()) {
Date starredDate = mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username);
org.subsonic.restapi.Artist a = new org.subsonic.restapi.Artist();
index.getArtist().add(a);
a.setId(String.valueOf(mediaFile.getId()));
a.setName(artist.getName());
a.setStarred(jaxbWriter.convertDate(starredDate));
if (mediaFile.isAlbum()) {
a.setAverageRating(ratingService.getAverageRating(mediaFile));
a.setUserRating(ratingService.getRatingForUser(username, mediaFile));
}
}
}
}
}
// Add children
Player player = playerService.getPlayer(request, response);
for (MediaFile singleSong : musicFolderContent.getSingleSongs()) {
indexes.getChild().add(createJaxbChild(player, singleSong, username));
}
res.setIndexes(indexes);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getGenres")
public void getGenres(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
org.subsonic.restapi.Genres genres = new org.subsonic.restapi.Genres();
for (org.airsonic.player.domain.Genre genre : mediaFileDao.getGenres(false)) {
org.subsonic.restapi.Genre g = new org.subsonic.restapi.Genre();
genres.getGenre().add(g);
g.setContent(genre.getName());
g.setAlbumCount(genre.getAlbumCount());
g.setSongCount(genre.getSongCount());
}
Response res = createResponse();
res.setGenres(genres);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getSongsByGenre")
public void getSongsByGenre(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
Songs songs = new Songs();
String genre = getRequiredStringParameter(request, "genre");
int offset = getIntParameter(request, "offset", 0);
int count = getIntParameter(request, "count", 10);
count = Math.max(0, Math.min(count, 500));
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
for (MediaFile mediaFile : mediaFileDao.getSongsByGenre(genre, offset, count, musicFolders)) {
songs.getSong().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setSongsByGenre(songs);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getArtists")
public void getArtists(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
ArtistsID3 result = new ArtistsID3();
result.setIgnoredArticles(settingsService.getIgnoredArticles());
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<org.airsonic.player.domain.Artist> artists = artistDao.getAlphabetialArtists(0, Integer.MAX_VALUE, musicFolders);
SortedMap<MusicIndex, List<MusicIndex.SortableArtistWithArtist>> indexedArtists = musicIndexService.getIndexedArtists(artists);
for (Map.Entry<MusicIndex, List<MusicIndex.SortableArtistWithArtist>> entry : indexedArtists.entrySet()) {
IndexID3 index = new IndexID3();
result.getIndex().add(index);
index.setName(entry.getKey().getIndex());
for (MusicIndex.SortableArtistWithArtist sortableArtist : entry.getValue()) {
index.getArtist().add(createJaxbArtist(new ArtistID3(), sortableArtist.getArtist(), username));
}
}
Response res = createResponse();
res.setArtists(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getSimilarSongs")
public void getSimilarSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
int count = getIntParameter(request, "count", 50);
SimilarSongs result = new SimilarSongs();
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile == null) {
error(request, response, ErrorCode.NOT_FOUND, "Media file not found.");
return;
}
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> similarSongs = lastFmService.getSimilarSongs(mediaFile, count, musicFolders);
Player player = playerService.getPlayer(request, response);
for (MediaFile similarSong : similarSongs) {
result.getSong().add(createJaxbChild(player, similarSong, username));
}
Response res = createResponse();
res.setSimilarSongs(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getSimilarSongs2")
public void getSimilarSongs2(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
int count = getIntParameter(request, "count", 50);
SimilarSongs2 result = new SimilarSongs2();
org.airsonic.player.domain.Artist artist = artistDao.getArtist(id);
if (artist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Artist not found.");
return;
}
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> similarSongs = lastFmService.getSimilarSongs(artist, count, musicFolders);
Player player = playerService.getPlayer(request, response);
for (MediaFile similarSong : similarSongs) {
result.getSong().add(createJaxbChild(player, similarSong, username));
}
Response res = createResponse();
res.setSimilarSongs2(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getTopSongs")
public void getTopSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
String artist = getRequiredStringParameter(request, "artist");
int count = getIntParameter(request, "count", 50);
TopSongs result = new TopSongs();
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> topSongs = lastFmService.getTopSongs(artist, count, musicFolders);
Player player = playerService.getPlayer(request, response);
for (MediaFile topSong : topSongs) {
result.getSong().add(createJaxbChild(player, topSong, username));
}
Response res = createResponse();
res.setTopSongs(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getArtistInfo")
public void getArtistInfo(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
int count = getIntParameter(request, "count", 20);
boolean includeNotPresent = ServletRequestUtils.getBooleanParameter(request, "includeNotPresent", false);
ArtistInfo result = new ArtistInfo();
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile == null) {
error(request, response, ErrorCode.NOT_FOUND, "Media file not found.");
return;
}
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<MediaFile> similarArtists = lastFmService.getSimilarArtists(mediaFile, count, includeNotPresent, musicFolders);
for (MediaFile similarArtist : similarArtists) {
result.getSimilarArtist().add(createJaxbArtist(similarArtist, username));
}
ArtistBio artistBio = lastFmService.getArtistBio(mediaFile, localeResolver.resolveLocale(request));
if (artistBio != null) {
result.setBiography(artistBio.getBiography());
result.setMusicBrainzId(artistBio.getMusicBrainzId());
result.setLastFmUrl(artistBio.getLastFmUrl());
result.setSmallImageUrl(artistBio.getSmallImageUrl());
result.setMediumImageUrl(artistBio.getMediumImageUrl());
result.setLargeImageUrl(artistBio.getLargeImageUrl());
}
Response res = createResponse();
res.setArtistInfo(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getArtistInfo2")
public void getArtistInfo2(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
int count = getIntParameter(request, "count", 20);
boolean includeNotPresent = ServletRequestUtils.getBooleanParameter(request, "includeNotPresent", false);
ArtistInfo2 result = new ArtistInfo2();
org.airsonic.player.domain.Artist artist = artistDao.getArtist(id);
if (artist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Artist not found.");
return;
}
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
List<org.airsonic.player.domain.Artist> similarArtists = lastFmService.getSimilarArtists(artist, count, includeNotPresent, musicFolders);
for (org.airsonic.player.domain.Artist similarArtist : similarArtists) {
result.getSimilarArtist().add(createJaxbArtist(new ArtistID3(), similarArtist, username));
}
ArtistBio artistBio = lastFmService.getArtistBio(artist, localeResolver.resolveLocale(request));
if (artistBio != null) {
result.setBiography(artistBio.getBiography());
result.setMusicBrainzId(artistBio.getMusicBrainzId());
result.setLastFmUrl(artistBio.getLastFmUrl());
result.setSmallImageUrl(artistBio.getSmallImageUrl());
result.setMediumImageUrl(artistBio.getMediumImageUrl());
result.setLargeImageUrl(artistBio.getLargeImageUrl());
}
Response res = createResponse();
res.setArtistInfo2(result);
jaxbWriter.writeResponse(request, response, res);
}
private <T extends ArtistID3> T createJaxbArtist(T jaxbArtist, org.airsonic.player.domain.Artist artist, String username) {
jaxbArtist.setId(String.valueOf(artist.getId()));
jaxbArtist.setName(artist.getName());
jaxbArtist.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(artist.getId(), username)));
jaxbArtist.setAlbumCount(artist.getAlbumCount());
if (artist.getCoverArtPath() != null) {
jaxbArtist.setCoverArt(CoverArtController.ARTIST_COVERART_PREFIX + artist.getId());
}
return jaxbArtist;
}
private org.subsonic.restapi.Artist createJaxbArtist(MediaFile artist, String username) {
org.subsonic.restapi.Artist result = new org.subsonic.restapi.Artist();
result.setId(String.valueOf(artist.getId()));
result.setName(artist.getArtist());
Date starred = mediaFileDao.getMediaFileStarredDate(artist.getId(), username);
result.setStarred(jaxbWriter.convertDate(starred));
return result;
}
@RequestMapping("/getArtist")
public void getArtist(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
org.airsonic.player.domain.Artist artist = artistDao.getArtist(id);
if (artist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Artist not found.");
return;
}
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
ArtistWithAlbumsID3 result = createJaxbArtist(new ArtistWithAlbumsID3(), artist, username);
for (Album album : albumDao.getAlbumsForArtist(artist.getName(), musicFolders)) {
result.getAlbum().add(createJaxbAlbum(new AlbumID3(), album, username));
}
Response res = createResponse();
res.setArtist(result);
jaxbWriter.writeResponse(request, response, res);
}
private <T extends AlbumID3> T createJaxbAlbum(T jaxbAlbum, Album album, String username) {
jaxbAlbum.setId(String.valueOf(album.getId()));
jaxbAlbum.setName(album.getName());
if (album.getArtist() != null) {
jaxbAlbum.setArtist(album.getArtist());
org.airsonic.player.domain.Artist artist = artistDao.getArtist(album.getArtist());
if (artist != null) {
jaxbAlbum.setArtistId(String.valueOf(artist.getId()));
}
}
if (album.getCoverArtPath() != null) {
jaxbAlbum.setCoverArt(CoverArtController.ALBUM_COVERART_PREFIX + album.getId());
}
jaxbAlbum.setSongCount(album.getSongCount());
jaxbAlbum.setDuration(album.getDurationSeconds());
jaxbAlbum.setCreated(jaxbWriter.convertDate(album.getCreated()));
jaxbAlbum.setStarred(jaxbWriter.convertDate(albumDao.getAlbumStarredDate(album.getId(), username)));
jaxbAlbum.setYear(album.getYear());
jaxbAlbum.setGenre(album.getGenre());
return jaxbAlbum;
}
private <T extends org.subsonic.restapi.Playlist> T createJaxbPlaylist(T jaxbPlaylist, org.airsonic.player.domain.Playlist playlist) {
jaxbPlaylist.setId(String.valueOf(playlist.getId()));
jaxbPlaylist.setName(playlist.getName());
jaxbPlaylist.setComment(playlist.getComment());
jaxbPlaylist.setOwner(playlist.getUsername());
jaxbPlaylist.setPublic(playlist.isShared());
jaxbPlaylist.setSongCount(playlist.getFileCount());
jaxbPlaylist.setDuration(playlist.getDurationSeconds());
jaxbPlaylist.setCreated(jaxbWriter.convertDate(playlist.getCreated()));
jaxbPlaylist.setChanged(jaxbWriter.convertDate(playlist.getChanged()));
jaxbPlaylist.setCoverArt(CoverArtController.PLAYLIST_COVERART_PREFIX + playlist.getId());
for (String username : playlistService.getPlaylistUsers(playlist.getId())) {
jaxbPlaylist.getAllowedUser().add(username);
}
return jaxbPlaylist;
}
@RequestMapping("/getAlbum")
public void getAlbum(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
Album album = albumDao.getAlbum(id);
if (album == null) {
error(request, response, ErrorCode.NOT_FOUND, "Album not found.");
return;
}
AlbumWithSongsID3 result = createJaxbAlbum(new AlbumWithSongsID3(), album, username);
for (MediaFile mediaFile : mediaFileDao.getSongsForAlbum(album.getArtist(), album.getName())) {
result.getSong().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setAlbum(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getSong")
public void getSong(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
MediaFile song = mediaFileDao.getMediaFile(id);
if (song == null || song.isDirectory()) {
error(request, response, ErrorCode.NOT_FOUND, "Song not found.");
return;
}
if (!securityService.isFolderAccessAllowed(song, username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Access denied");
return;
}
Response res = createResponse();
res.setSong(createJaxbChild(player, song, username));
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getMusicDirectory")
public void getMusicDirectory(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
MediaFile dir = mediaFileService.getMediaFile(id);
if (dir == null) {
error(request, response, ErrorCode.NOT_FOUND, "Directory not found");
return;
}
if (!securityService.isFolderAccessAllowed(dir, username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Access denied");
return;
}
MediaFile parent = mediaFileService.getParentOf(dir);
Directory directory = new Directory();
directory.setId(String.valueOf(id));
try {
if (!mediaFileService.isRoot(parent)) {
directory.setParent(String.valueOf(parent.getId()));
}
} catch (SecurityException x) {
// Ignored.
}
directory.setName(dir.getName());
directory.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(id, username)));
directory.setPlayCount((long) dir.getPlayCount());
if (dir.isAlbum()) {
directory.setAverageRating(ratingService.getAverageRating(dir));
directory.setUserRating(ratingService.getRatingForUser(username, dir));
}
for (MediaFile child : mediaFileService.getChildrenOf(dir, true, true, true)) {
directory.getChild().add(createJaxbChild(player, child, username));
}
Response res = createResponse();
res.setDirectory(directory);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/search")
public void search(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
String any = request.getParameter("any");
String artist = request.getParameter("artist");
String album = request.getParameter("album");
String title = request.getParameter("title");
StringBuilder query = new StringBuilder();
if (any != null) {
query.append(any).append(" ");
}
if (artist != null) {
query.append(artist).append(" ");
}
if (album != null) {
query.append(album).append(" ");
}
if (title != null) {
query.append(title);
}
SearchCriteria criteria = new SearchCriteria();
criteria.setQuery(query.toString().trim());
criteria.setCount(getIntParameter(request, "count", 20));
criteria.setOffset(getIntParameter(request, "offset", 0));
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
org.airsonic.player.domain.SearchResult result = searchService.search(criteria, musicFolders, IndexType.SONG);
org.subsonic.restapi.SearchResult searchResult = new org.subsonic.restapi.SearchResult();
searchResult.setOffset(result.getOffset());
searchResult.setTotalHits(result.getTotalHits());
for (MediaFile mediaFile : result.getMediaFiles()) {
searchResult.getMatch().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setSearchResult(searchResult);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/search2")
public void search2(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
SearchResult2 searchResult = new SearchResult2();
String query = request.getParameter("query");
SearchCriteria criteria = new SearchCriteria();
criteria.setQuery(StringUtils.trimToEmpty(query));
criteria.setCount(getIntParameter(request, "artistCount", 20));
criteria.setOffset(getIntParameter(request, "artistOffset", 0));
org.airsonic.player.domain.SearchResult artists = searchService.search(criteria, musicFolders, IndexType.ARTIST);
for (MediaFile mediaFile : artists.getMediaFiles()) {
searchResult.getArtist().add(createJaxbArtist(mediaFile, username));
}
criteria.setCount(getIntParameter(request, "albumCount", 20));
criteria.setOffset(getIntParameter(request, "albumOffset", 0));
org.airsonic.player.domain.SearchResult albums = searchService.search(criteria, musicFolders, IndexType.ALBUM);
for (MediaFile mediaFile : albums.getMediaFiles()) {
searchResult.getAlbum().add(createJaxbChild(player, mediaFile, username));
}
criteria.setCount(getIntParameter(request, "songCount", 20));
criteria.setOffset(getIntParameter(request, "songOffset", 0));
org.airsonic.player.domain.SearchResult songs = searchService.search(criteria, musicFolders, IndexType.SONG);
for (MediaFile mediaFile : songs.getMediaFiles()) {
searchResult.getSong().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setSearchResult2(searchResult);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/search3")
public void search3(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
SearchResult3 searchResult = new SearchResult3();
String query = request.getParameter("query");
SearchCriteria criteria = new SearchCriteria();
criteria.setQuery(StringUtils.trimToEmpty(query));
criteria.setCount(getIntParameter(request, "artistCount", 20));
criteria.setOffset(getIntParameter(request, "artistOffset", 0));
org.airsonic.player.domain.SearchResult result = searchService.search(criteria, musicFolders, IndexType.ARTIST_ID3);
for (org.airsonic.player.domain.Artist artist : result.getArtists()) {
searchResult.getArtist().add(createJaxbArtist(new ArtistID3(), artist, username));
}
criteria.setCount(getIntParameter(request, "albumCount", 20));
criteria.setOffset(getIntParameter(request, "albumOffset", 0));
result = searchService.search(criteria, musicFolders, IndexType.ALBUM_ID3);
for (Album album : result.getAlbums()) {
searchResult.getAlbum().add(createJaxbAlbum(new AlbumID3(), album, username));
}
criteria.setCount(getIntParameter(request, "songCount", 20));
criteria.setOffset(getIntParameter(request, "songOffset", 0));
result = searchService.search(criteria, musicFolders, IndexType.SONG);
for (MediaFile song : result.getMediaFiles()) {
searchResult.getSong().add(createJaxbChild(player, song, username));
}
Response res = createResponse();
res.setSearchResult3(searchResult);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getPlaylists")
public void getPlaylists(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
String authenticatedUsername = user.getUsername();
String requestedUsername = request.getParameter("username");
if (requestedUsername == null) {
requestedUsername = authenticatedUsername;
} else if (!user.isAdminRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, authenticatedUsername + " is not authorized to get playlists for " + requestedUsername);
return;
}
Playlists result = new Playlists();
for (org.airsonic.player.domain.Playlist playlist : playlistService.getReadablePlaylistsForUser(requestedUsername)) {
result.getPlaylist().add(createJaxbPlaylist(new org.subsonic.restapi.Playlist(), playlist));
}
Response res = createResponse();
res.setPlaylists(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getPlaylist")
public void getPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
org.airsonic.player.domain.Playlist playlist = playlistService.getPlaylist(id);
if (playlist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + id);
return;
}
if (!playlistService.isReadAllowed(playlist, username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + id);
return;
}
PlaylistWithSongs result = createJaxbPlaylist(new PlaylistWithSongs(), playlist);
for (MediaFile mediaFile : playlistService.getFilesInPlaylist(id)) {
if (securityService.isFolderAccessAllowed(mediaFile, username)) {
result.getEntry().add(createJaxbChild(player, mediaFile, username));
}
}
Response res = createResponse();
res.setPlaylist(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/jukeboxControl")
public void jukeboxControl(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request, true);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isJukeboxRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to use jukebox.");
return;
}
Player player = playerService.getPlayer(request, response);
boolean returnPlaylist = false;
String action = getRequiredStringParameter(request, "action");
switch (action) {
case "start":
player.getPlayQueue().setStatus(PlayQueue.Status.PLAYING);
jukeboxService.start(player);
break;
case "stop":
player.getPlayQueue().setStatus(PlayQueue.Status.STOPPED);
jukeboxService.stop(player);
break;
case "skip":
int index = getRequiredIntParameter(request, "index");
int offset = getIntParameter(request, "offset", 0);
player.getPlayQueue().setIndex(index);
jukeboxService.skip(player,index,offset);
break;
case "add":
int[] ids = getIntParameters(request, "id");
playQueueService.addMediaFilesToPlayQueue(player.getPlayQueue(),ids,null,true);
break;
case "set":
ids = getIntParameters(request, "id");
playQueueService.resetPlayQueue(player.getPlayQueue(),ids,true);
break;
case "clear":
player.getPlayQueue().clear();
break;
case "remove":
index = getRequiredIntParameter(request, "index");
player.getPlayQueue().removeFileAt(index);
break;
case "shuffle":
player.getPlayQueue().shuffle();
break;
case "setGain":
float gain = getRequiredFloatParameter(request, "gain");
jukeboxService.setGain(player,gain);
break;
case "get":
returnPlaylist = true;
break;
case "status":
// No action necessary.
break;
default:
throw new Exception("Unknown jukebox action: '" + action + "'.");
}
String username = securityService.getCurrentUsername(request);
PlayQueue playQueue = player.getPlayQueue();
// this variable is only needed for the JukeboxLegacySubsonicService. To be removed.
boolean controlsJukebox = jukeboxService.canControl(player);
int currentIndex = controlsJukebox && !playQueue.isEmpty() ? playQueue.getIndex() : -1;
boolean playing = controlsJukebox && !playQueue.isEmpty() && playQueue.getStatus() == PlayQueue.Status.PLAYING;
float gain;
int position;
gain = jukeboxService.getGain(player);
position = controlsJukebox && !playQueue.isEmpty() ? jukeboxService.getPosition(player) : 0;
Response res = createResponse();
if (returnPlaylist) {
JukeboxPlaylist result = new JukeboxPlaylist();
res.setJukeboxPlaylist(result);
result.setCurrentIndex(currentIndex);
result.setPlaying(playing);
result.setGain(gain);
result.setPosition(position);
for (MediaFile mediaFile : playQueue.getFiles()) {
result.getEntry().add(createJaxbChild(player, mediaFile, username));
}
} else {
JukeboxStatus result = new JukeboxStatus();
res.setJukeboxStatus(result);
result.setCurrentIndex(currentIndex);
result.setPlaying(playing);
result.setGain(gain);
result.setPosition(position);
}
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/createPlaylist")
public void createPlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request, true);
String username = securityService.getCurrentUsername(request);
Integer playlistId = getIntParameter(request, "playlistId");
String name = request.getParameter("name");
if (playlistId == null && name == null) {
error(request, response, ErrorCode.MISSING_PARAMETER, "Playlist ID or name must be specified.");
return;
}
org.airsonic.player.domain.Playlist playlist;
if (playlistId != null) {
playlist = playlistService.getPlaylist(playlistId);
if (playlist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + playlistId);
return;
}
if (!playlistService.isWriteAllowed(playlist, username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + playlistId);
return;
}
} else {
playlist = new org.airsonic.player.domain.Playlist();
playlist.setName(name);
playlist.setCreated(new Date());
playlist.setChanged(new Date());
playlist.setShared(false);
playlist.setUsername(username);
playlistService.createPlaylist(playlist);
}
List<MediaFile> songs = new ArrayList<MediaFile>();
for (int id : getIntParameters(request, "songId")) {
MediaFile song = mediaFileService.getMediaFile(id);
if (song != null) {
songs.add(song);
}
}
playlistService.setFilesInPlaylist(playlist.getId(), songs);
writeEmptyResponse(request, response);
}
@RequestMapping("/updatePlaylist")
public void updatePlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request, true);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "playlistId");
org.airsonic.player.domain.Playlist playlist = playlistService.getPlaylist(id);
if (playlist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + id);
return;
}
if (!playlistService.isWriteAllowed(playlist, username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + id);
return;
}
String name = request.getParameter("name");
if (name != null) {
playlist.setName(name);
}
String comment = request.getParameter("comment");
if (comment != null) {
playlist.setComment(comment);
}
Boolean shared = getBooleanParameter(request, "public");
if (shared != null) {
playlist.setShared(shared);
}
playlistService.updatePlaylist(playlist);
// TODO: Add later
// for (String usernameToAdd : ServletRequestUtils.getStringParameters(request, "usernameToAdd")) {
// if (securityService.getUserByName(usernameToAdd) != null) {
// playlistService.addPlaylistUser(id, usernameToAdd);
// }
// }
// for (String usernameToRemove : ServletRequestUtils.getStringParameters(request, "usernameToRemove")) {
// if (securityService.getUserByName(usernameToRemove) != null) {
// playlistService.deletePlaylistUser(id, usernameToRemove);
// }
// }
List<MediaFile> songs = playlistService.getFilesInPlaylist(id);
boolean songsChanged = false;
SortedSet<Integer> tmp = new TreeSet<Integer>();
for (int songIndexToRemove : getIntParameters(request, "songIndexToRemove")) {
tmp.add(songIndexToRemove);
}
List<Integer> songIndexesToRemove = new ArrayList<Integer>(tmp);
Collections.reverse(songIndexesToRemove);
for (Integer songIndexToRemove : songIndexesToRemove) {
songs.remove(songIndexToRemove.intValue());
songsChanged = true;
}
for (int songToAdd : getIntParameters(request, "songIdToAdd")) {
MediaFile song = mediaFileService.getMediaFile(songToAdd);
if (song != null) {
songs.add(song);
songsChanged = true;
}
}
if (songsChanged) {
playlistService.setFilesInPlaylist(id, songs);
}
writeEmptyResponse(request, response);
}
@RequestMapping("/deletePlaylist")
public void deletePlaylist(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request, true);
String username = securityService.getCurrentUsername(request);
int id = getRequiredIntParameter(request, "id");
org.airsonic.player.domain.Playlist playlist = playlistService.getPlaylist(id);
if (playlist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Playlist not found: " + id);
return;
}
if (!playlistService.isWriteAllowed(playlist, username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Permission denied for playlist " + id);
return;
}
playlistService.deletePlaylist(id);
writeEmptyResponse(request, response);
}
@RequestMapping("/getAlbumList")
public void getAlbumList(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int size = getIntParameter(request, "size", 10);
int offset = getIntParameter(request, "offset", 0);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
size = Math.max(0, Math.min(size, 500));
String type = getRequiredStringParameter(request, "type");
List<MediaFile> albums;
if ("highest".equals(type)) {
albums = ratingService.getHighestRatedAlbums(offset, size, musicFolders);
} else if ("frequent".equals(type)) {
albums = mediaFileService.getMostFrequentlyPlayedAlbums(offset, size, musicFolders);
} else if ("recent".equals(type)) {
albums = mediaFileService.getMostRecentlyPlayedAlbums(offset, size, musicFolders);
} else if ("newest".equals(type)) {
albums = mediaFileService.getNewestAlbums(offset, size, musicFolders);
} else if ("starred".equals(type)) {
albums = mediaFileService.getStarredAlbums(offset, size, username, musicFolders);
} else if ("alphabeticalByArtist".equals(type)) {
albums = mediaFileService.getAlphabeticalAlbums(offset, size, true, musicFolders);
} else if ("alphabeticalByName".equals(type)) {
albums = mediaFileService.getAlphabeticalAlbums(offset, size, false, musicFolders);
} else if ("byGenre".equals(type)) {
albums = mediaFileService.getAlbumsByGenre(offset, size, getRequiredStringParameter(request, "genre"), musicFolders);
} else if ("byYear".equals(type)) {
albums = mediaFileService.getAlbumsByYear(offset, size, getRequiredIntParameter(request, "fromYear"),
getRequiredIntParameter(request, "toYear"), musicFolders);
} else if ("random".equals(type)) {
albums = searchService.getRandomAlbums(size, musicFolders);
} else {
throw new Exception("Invalid list type: " + type);
}
AlbumList result = new AlbumList();
for (MediaFile album : albums) {
result.getAlbum().add(createJaxbChild(player, album, username));
}
Response res = createResponse();
res.setAlbumList(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getAlbumList2")
public void getAlbumList2(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
int size = getIntParameter(request, "size", 10);
int offset = getIntParameter(request, "offset", 0);
size = Math.max(0, Math.min(size, 500));
String type = getRequiredStringParameter(request, "type");
String username = securityService.getCurrentUsername(request);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
List<Album> albums;
if ("frequent".equals(type)) {
albums = albumDao.getMostFrequentlyPlayedAlbums(offset, size, musicFolders);
} else if ("recent".equals(type)) {
albums = albumDao.getMostRecentlyPlayedAlbums(offset, size, musicFolders);
} else if ("newest".equals(type)) {
albums = albumDao.getNewestAlbums(offset, size, musicFolders);
} else if ("alphabeticalByArtist".equals(type)) {
albums = albumDao.getAlphabeticalAlbums(offset, size, true, false, musicFolders);
} else if ("alphabeticalByName".equals(type)) {
albums = albumDao.getAlphabeticalAlbums(offset, size, false, false, musicFolders);
} else if ("byGenre".equals(type)) {
albums = albumDao.getAlbumsByGenre(offset, size, getRequiredStringParameter(request, "genre"), musicFolders);
} else if ("byYear".equals(type)) {
albums = albumDao.getAlbumsByYear(offset, size, getRequiredIntParameter(request, "fromYear"),
getRequiredIntParameter(request, "toYear"), musicFolders);
} else if ("starred".equals(type)) {
albums = albumDao.getStarredAlbums(offset, size, securityService.getCurrentUser(request).getUsername(), musicFolders);
} else if ("random".equals(type)) {
albums = searchService.getRandomAlbumsId3(size, musicFolders);
} else {
throw new Exception("Invalid list type: " + type);
}
AlbumList2 result = new AlbumList2();
for (Album album : albums) {
result.getAlbum().add(createJaxbAlbum(new AlbumID3(), album, username));
}
Response res = createResponse();
res.setAlbumList2(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getRandomSongs")
public void getRandomSongs(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int size = getIntParameter(request, "size", 10);
size = Math.max(0, Math.min(size, 500));
String genre = getStringParameter(request, "genre");
Integer fromYear = getIntParameter(request, "fromYear");
Integer toYear = getIntParameter(request, "toYear");
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
RandomSearchCriteria criteria = new RandomSearchCriteria(size, genre, fromYear, toYear, musicFolders);
Songs result = new Songs();
for (MediaFile mediaFile : searchService.getRandomSongs(criteria)) {
result.getSong().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setRandomSongs(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getVideos")
public void getVideos(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int size = getIntParameter(request, "size", Integer.MAX_VALUE);
int offset = getIntParameter(request, "offset", 0);
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
Videos result = new Videos();
for (MediaFile mediaFile : mediaFileDao.getVideos(size, offset, musicFolders)) {
result.getVideo().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setVideos(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getNowPlaying")
public void getNowPlaying(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
NowPlaying result = new NowPlaying();
for (PlayStatus status : statusService.getPlayStatuses()) {
Player player = status.getPlayer();
MediaFile mediaFile = status.getMediaFile();
String username = player.getUsername();
if (username == null) {
continue;
}
UserSettings userSettings = settingsService.getUserSettings(username);
if (!userSettings.isNowPlayingAllowed()) {
continue;
}
long minutesAgo = status.getMinutesAgo();
if (minutesAgo < 60) {
NowPlayingEntry entry = new NowPlayingEntry();
entry.setUsername(username);
entry.setPlayerId(player.getId());
entry.setPlayerName(player.getName());
entry.setMinutesAgo((int) minutesAgo);
result.getEntry().add(createJaxbChild(entry, player, mediaFile, username));
}
}
Response res = createResponse();
res.setNowPlaying(result);
jaxbWriter.writeResponse(request, response, res);
}
private Child createJaxbChild(Player player, MediaFile mediaFile, String username) {
return createJaxbChild(new Child(), player, mediaFile, username);
}
private <T extends Child> T createJaxbChild(T child, Player player, MediaFile mediaFile, String username) {
MediaFile parent = mediaFileService.getParentOf(mediaFile);
child.setId(String.valueOf(mediaFile.getId()));
try {
if (!mediaFileService.isRoot(parent)) {
child.setParent(String.valueOf(parent.getId()));
}
} catch (SecurityException x) {
// Ignored.
}
child.setTitle(mediaFile.getName());
child.setAlbum(mediaFile.getAlbumName());
child.setArtist(mediaFile.getArtist());
child.setIsDir(mediaFile.isDirectory());
child.setCoverArt(findCoverArt(mediaFile, parent));
child.setYear(mediaFile.getYear());
child.setGenre(mediaFile.getGenre());
child.setCreated(jaxbWriter.convertDate(mediaFile.getCreated()));
child.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username)));
child.setUserRating(ratingService.getRatingForUser(username, mediaFile));
child.setAverageRating(ratingService.getAverageRating(mediaFile));
child.setPlayCount((long) mediaFile.getPlayCount());
if (mediaFile.isFile()) {
child.setDuration(mediaFile.getDurationSeconds());
child.setBitRate(mediaFile.getBitRate());
child.setTrack(mediaFile.getTrackNumber());
child.setDiscNumber(mediaFile.getDiscNumber());
child.setSize(mediaFile.getFileSize());
String suffix = mediaFile.getFormat();
child.setSuffix(suffix);
child.setContentType(StringUtil.getMimeType(suffix));
child.setIsVideo(mediaFile.isVideo());
child.setPath(getRelativePath(mediaFile, settingsService));
if (mediaFile.getAlbumArtist() != null && mediaFile.getAlbumName() != null) {
Album album = albumDao.getAlbum(mediaFile.getAlbumArtist(), mediaFile.getAlbumName());
if (album != null) {
child.setAlbumId(String.valueOf(album.getId()));
}
}
if (mediaFile.getArtist() != null) {
org.airsonic.player.domain.Artist artist = artistDao.getArtist(mediaFile.getArtist());
if (artist != null) {
child.setArtistId(String.valueOf(artist.getId()));
}
}
switch (mediaFile.getMediaType()) {
case MUSIC:
child.setType(MediaType.MUSIC);
break;
case PODCAST:
child.setType(MediaType.PODCAST);
break;
case AUDIOBOOK:
child.setType(MediaType.AUDIOBOOK);
break;
case VIDEO:
child.setType(MediaType.VIDEO);
child.setOriginalWidth(mediaFile.getWidth());
child.setOriginalHeight(mediaFile.getHeight());
break;
default:
break;
}
if (transcodingService.isTranscodingRequired(mediaFile, player)) {
String transcodedSuffix = transcodingService.getSuffix(player, mediaFile, null);
child.setTranscodedSuffix(transcodedSuffix);
child.setTranscodedContentType(StringUtil.getMimeType(transcodedSuffix));
}
}
return child;
}
private String findCoverArt(MediaFile mediaFile, MediaFile parent) {
MediaFile dir = mediaFile.isDirectory() ? mediaFile : parent;
if (dir != null && dir.getCoverArtPath() != null) {
return String.valueOf(dir.getId());
}
return null;
}
public static String getRelativePath(MediaFile musicFile, SettingsService settingsService) {
String filePath = musicFile.getPath();
// Convert slashes.
filePath = filePath.replace('\\', '/');
String filePathLower = filePath.toLowerCase();
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getAllMusicFolders(false, true);
for (org.airsonic.player.domain.MusicFolder musicFolder : musicFolders) {
String folderPath = musicFolder.getPath().getPath();
folderPath = folderPath.replace('\\', '/');
String folderPathLower = folderPath.toLowerCase();
if (!folderPathLower.endsWith("/")) {
folderPathLower += "/";
}
if (filePathLower.startsWith(folderPathLower)) {
String relativePath = filePath.substring(folderPath.length());
return relativePath.startsWith("/") ? relativePath.substring(1) : relativePath;
}
}
return null;
}
@RequestMapping("/download")
public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isDownloadRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to download files.");
return;
}
long ifModifiedSince = request.getDateHeader("If-Modified-Since");
long lastModified = downloadController.getLastModified(request);
if (ifModifiedSince != -1 && lastModified != -1 && lastModified <= ifModifiedSince) {
response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
if (lastModified != -1) {
response.setDateHeader("Last-Modified", lastModified);
}
downloadController.handleRequest(request, response);
}
@RequestMapping("/stream")
public void stream(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isStreamRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to play files.");
return;
}
streamController.handleRequest(request, response);
}
@RequestMapping("/hls")
public void hls(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isStreamRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to play files.");
return;
}
int id = getRequiredIntParameter(request, "id");
MediaFile video = mediaFileDao.getMediaFile(id);
if (video == null || video.isDirectory()) {
error(request, response, ErrorCode.NOT_FOUND, "Video not found.");
return;
}
if (!securityService.isFolderAccessAllowed(video, user.getUsername())) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Access denied");
return;
}
hlsController.handleRequest(request, response);
}
@RequestMapping("/scrobble")
public void scrobble(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
boolean submission = getBooleanParameter(request, "submission", true);
int[] ids = getRequiredIntParameters(request, "id");
long[] times = getLongParameters(request, "time");
if (times.length > 0 && times.length != ids.length) {
error(request, response, ErrorCode.GENERIC, "Wrong number of timestamps: " + times.length);
return;
}
for (int i = 0; i < ids.length; i++) {
int id = ids[i];
MediaFile file = mediaFileService.getMediaFile(id);
if (file == null) {
LOG.warn("File to scrobble not found: " + id);
continue;
}
Date time = times.length == 0 ? null : new Date(times[i]);
statusService.addRemotePlay(new PlayStatus(file, player, time == null ? new Date() : time));
mediaFileService.incrementPlayCount(file);
if (settingsService.getUserSettings(player.getUsername()).isLastFmEnabled()) {
audioScrobblerService.register(file, player.getUsername(), submission, time);
}
}
writeEmptyResponse(request, response);
}
@RequestMapping("/star")
public void star(HttpServletRequest request, HttpServletResponse response) {
starOrUnstar(request, response, true);
}
@RequestMapping("/unstar")
public void unstar(HttpServletRequest request, HttpServletResponse response) {
starOrUnstar(request, response, false);
}
private void starOrUnstar(HttpServletRequest request, HttpServletResponse response, boolean star) {
request = wrapRequest(request);
String username = securityService.getCurrentUser(request).getUsername();
for (int id : getIntParameters(request, "id")) {
MediaFile mediaFile = mediaFileDao.getMediaFile(id);
if (mediaFile == null) {
error(request, response, ErrorCode.NOT_FOUND, "Media file not found: " + id);
return;
}
if (star) {
mediaFileDao.starMediaFile(id, username);
} else {
mediaFileDao.unstarMediaFile(id, username);
}
}
for (int albumId : getIntParameters(request, "albumId")) {
Album album = albumDao.getAlbum(albumId);
if (album == null) {
error(request, response, ErrorCode.NOT_FOUND, "Album not found: " + albumId);
return;
}
if (star) {
albumDao.starAlbum(albumId, username);
} else {
albumDao.unstarAlbum(albumId, username);
}
}
for (int artistId : getIntParameters(request, "artistId")) {
org.airsonic.player.domain.Artist artist = artistDao.getArtist(artistId);
if (artist == null) {
error(request, response, ErrorCode.NOT_FOUND, "Artist not found: " + artistId);
return;
}
if (star) {
artistDao.starArtist(artistId, username);
} else {
artistDao.unstarArtist(artistId, username);
}
}
writeEmptyResponse(request, response);
}
@RequestMapping("/getStarred")
public void getStarred(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
Starred result = new Starred();
for (MediaFile artist : mediaFileDao.getStarredDirectories(0, Integer.MAX_VALUE, username, musicFolders)) {
result.getArtist().add(createJaxbArtist(artist, username));
}
for (MediaFile album : mediaFileDao.getStarredAlbums(0, Integer.MAX_VALUE, username, musicFolders)) {
result.getAlbum().add(createJaxbChild(player, album, username));
}
for (MediaFile song : mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders)) {
result.getSong().add(createJaxbChild(player, song, username));
}
Response res = createResponse();
res.setStarred(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getStarred2")
public void getStarred2(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
Integer musicFolderId = getIntParameter(request, "musicFolderId");
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username, musicFolderId);
Starred2 result = new Starred2();
for (org.airsonic.player.domain.Artist artist : artistDao.getStarredArtists(0, Integer.MAX_VALUE, username, musicFolders)) {
result.getArtist().add(createJaxbArtist(new ArtistID3(), artist, username));
}
for (Album album : albumDao.getStarredAlbums(0, Integer.MAX_VALUE, username, musicFolders)) {
result.getAlbum().add(createJaxbAlbum(new AlbumID3(), album, username));
}
for (MediaFile song : mediaFileDao.getStarredFiles(0, Integer.MAX_VALUE, username, musicFolders)) {
result.getSong().add(createJaxbChild(player, song, username));
}
Response res = createResponse();
res.setStarred2(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getPodcasts")
public void getPodcasts(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
boolean includeEpisodes = getBooleanParameter(request, "includeEpisodes", true);
Integer channelId = getIntParameter(request, "id");
Podcasts result = new Podcasts();
for (org.airsonic.player.domain.PodcastChannel channel : podcastService.getAllChannels()) {
if (channelId == null || channelId.equals(channel.getId())) {
org.subsonic.restapi.PodcastChannel c = new org.subsonic.restapi.PodcastChannel();
result.getChannel().add(c);
c.setId(String.valueOf(channel.getId()));
c.setUrl(channel.getUrl());
c.setStatus(PodcastStatus.valueOf(channel.getStatus().name()));
c.setTitle(channel.getTitle());
c.setDescription(channel.getDescription());
c.setCoverArt(CoverArtController.PODCAST_COVERART_PREFIX + channel.getId());
c.setOriginalImageUrl(channel.getImageUrl());
c.setErrorMessage(channel.getErrorMessage());
if (includeEpisodes) {
List<org.airsonic.player.domain.PodcastEpisode> episodes = podcastService.getEpisodes(channel.getId());
for (org.airsonic.player.domain.PodcastEpisode episode : episodes) {
c.getEpisode().add(createJaxbPodcastEpisode(player, username, episode));
}
}
}
}
Response res = createResponse();
res.setPodcasts(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getNewestPodcasts")
public void getNewestPodcasts(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
int count = getIntParameter(request, "count", 20);
NewestPodcasts result = new NewestPodcasts();
for (org.airsonic.player.domain.PodcastEpisode episode : podcastService.getNewestEpisodes(count)) {
result.getEpisode().add(createJaxbPodcastEpisode(player, username, episode));
}
Response res = createResponse();
res.setNewestPodcasts(result);
jaxbWriter.writeResponse(request, response, res);
}
private org.subsonic.restapi.PodcastEpisode createJaxbPodcastEpisode(Player player, String username, org.airsonic.player.domain.PodcastEpisode episode) {
org.subsonic.restapi.PodcastEpisode e = new org.subsonic.restapi.PodcastEpisode();
String path = episode.getPath();
if (path != null) {
MediaFile mediaFile = mediaFileService.getMediaFile(path);
e = createJaxbChild(new org.subsonic.restapi.PodcastEpisode(), player, mediaFile, username);
e.setStreamId(String.valueOf(mediaFile.getId()));
}
e.setId(String.valueOf(episode.getId())); // Overwrites the previous "id" attribute.
e.setChannelId(String.valueOf(episode.getChannelId()));
e.setStatus(PodcastStatus.valueOf(episode.getStatus().name()));
e.setTitle(episode.getTitle());
e.setDescription(episode.getDescription());
e.setPublishDate(jaxbWriter.convertDate(episode.getPublishDate()));
return e;
}
@RequestMapping("/refreshPodcasts")
public void refreshPodcasts(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isPodcastRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to administrate podcasts.");
return;
}
podcastService.refreshAllChannels(true);
writeEmptyResponse(request, response);
}
@RequestMapping("/createPodcastChannel")
public void createPodcastChannel(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isPodcastRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to administrate podcasts.");
return;
}
String url = getRequiredStringParameter(request, "url");
podcastService.createChannel(url);
writeEmptyResponse(request, response);
}
@RequestMapping("/deletePodcastChannel")
public void deletePodcastChannel(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isPodcastRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to administrate podcasts.");
return;
}
int id = getRequiredIntParameter(request, "id");
podcastService.deleteChannel(id);
writeEmptyResponse(request, response);
}
@RequestMapping("/deletePodcastEpisode")
public void deletePodcastEpisode(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isPodcastRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to administrate podcasts.");
return;
}
int id = getRequiredIntParameter(request, "id");
podcastService.deleteEpisode(id, true);
writeEmptyResponse(request, response);
}
@RequestMapping("/downloadPodcastEpisode")
public void downloadPodcastEpisode(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isPodcastRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to administrate podcasts.");
return;
}
int id = getRequiredIntParameter(request, "id");
org.airsonic.player.domain.PodcastEpisode episode = podcastService.getEpisode(id, true);
if (episode == null) {
error(request, response, ErrorCode.NOT_FOUND, "Podcast episode " + id + " not found.");
return;
}
podcastService.downloadEpisode(episode);
writeEmptyResponse(request, response);
}
@RequestMapping("/getInternetRadioStations")
public void getInternetRadioStations(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
InternetRadioStations result = new InternetRadioStations();
for (InternetRadio radio : settingsService.getAllInternetRadios()) {
InternetRadioStation i = new InternetRadioStation();
i.setId(String.valueOf(radio.getId()));
i.setName(radio.getName());
i.setStreamUrl(radio.getStreamUrl());
i.setHomePageUrl(radio.getHomepageUrl());
result.getInternetRadioStation().add(i);
}
Response res = createResponse();
res.setInternetRadioStations(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getBookmarks")
public void getBookmarks(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
Bookmarks result = new Bookmarks();
for (Bookmark bookmark : bookmarkService.getBookmarks(username)) {
org.subsonic.restapi.Bookmark b = new org.subsonic.restapi.Bookmark();
result.getBookmark().add(b);
b.setPosition(bookmark.getPositionMillis());
b.setUsername(bookmark.getUsername());
b.setComment(bookmark.getComment());
b.setCreated(jaxbWriter.convertDate(bookmark.getCreated()));
b.setChanged(jaxbWriter.convertDate(bookmark.getChanged()));
MediaFile mediaFile = mediaFileService.getMediaFile(bookmark.getMediaFileId());
b.setEntry(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setBookmarks(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/createBookmark")
public void createBookmark(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int mediaFileId = getRequiredIntParameter(request, "id");
long position = getRequiredLongParameter(request, "position");
String comment = request.getParameter("comment");
Date now = new Date();
Bookmark bookmark = new Bookmark(0, mediaFileId, position, username, comment, now, now);
bookmarkService.createOrUpdateBookmark(bookmark);
writeEmptyResponse(request, response);
}
@RequestMapping("/deleteBookmark")
public void deleteBookmark(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
int mediaFileId = getRequiredIntParameter(request, "id");
bookmarkService.deleteBookmark(username, mediaFileId);
writeEmptyResponse(request, response);
}
@RequestMapping("/getPlayQueue")
public void getPlayQueue(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
Player player = playerService.getPlayer(request, response);
SavedPlayQueue playQueue = playQueueDao.getPlayQueue(username);
if (playQueue == null) {
writeEmptyResponse(request, response);
return;
}
org.subsonic.restapi.PlayQueue restPlayQueue = new org.subsonic.restapi.PlayQueue();
restPlayQueue.setUsername(playQueue.getUsername());
restPlayQueue.setCurrent(playQueue.getCurrentMediaFileId());
restPlayQueue.setPosition(playQueue.getPositionMillis());
restPlayQueue.setChanged(jaxbWriter.convertDate(playQueue.getChanged()));
restPlayQueue.setChangedBy(playQueue.getChangedBy());
for (Integer mediaFileId : playQueue.getMediaFileIds()) {
MediaFile mediaFile = mediaFileService.getMediaFile(mediaFileId);
if (mediaFile != null) {
restPlayQueue.getEntry().add(createJaxbChild(player, mediaFile, username));
}
}
Response res = createResponse();
res.setPlayQueue(restPlayQueue);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/savePlayQueue")
public void savePlayQueue(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = securityService.getCurrentUsername(request);
List<Integer> mediaFileIds = Util.toIntegerList(getIntParameters(request, "id"));
Integer current = getIntParameter(request, "current");
Long position = getLongParameter(request, "position");
Date changed = new Date();
String changedBy = getRequiredStringParameter(request, "c");
if (!mediaFileIds.contains(current)) {
error(request, response, ErrorCode.GENERIC, "Current track is not included in play queue");
return;
}
SavedPlayQueue playQueue = new SavedPlayQueue(null, username, mediaFileIds, current, position, changed, changedBy);
playQueueDao.savePlayQueue(playQueue);
writeEmptyResponse(request, response);
}
@RequestMapping("/getShares")
public void getShares(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
Shares result = new Shares();
for (org.airsonic.player.domain.Share share : shareService.getSharesForUser(user)) {
org.subsonic.restapi.Share s = createJaxbShare(request, share);
result.getShare().add(s);
for (MediaFile mediaFile : shareService.getSharedFiles(share.getId(), musicFolders)) {
s.getEntry().add(createJaxbChild(player, mediaFile, username));
}
}
Response res = createResponse();
res.setShares(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/createShare")
public void createShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Player player = playerService.getPlayer(request, response);
String username = securityService.getCurrentUsername(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isShareRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to share media.");
return;
}
List<MediaFile> files = new ArrayList<MediaFile>();
for (int id : getRequiredIntParameters(request, "id")) {
files.add(mediaFileService.getMediaFile(id));
}
org.airsonic.player.domain.Share share = shareService.createShare(request, files);
share.setDescription(request.getParameter("description"));
long expires = getLongParameter(request, "expires", 0L);
if (expires != 0) {
share.setExpires(new Date(expires));
}
shareService.updateShare(share);
Shares result = new Shares();
org.subsonic.restapi.Share s = createJaxbShare(request, share);
result.getShare().add(s);
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
for (MediaFile mediaFile : shareService.getSharedFiles(share.getId(), musicFolders)) {
s.getEntry().add(createJaxbChild(player, mediaFile, username));
}
Response res = createResponse();
res.setShares(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/deleteShare")
public void deleteShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
int id = getRequiredIntParameter(request, "id");
org.airsonic.player.domain.Share share = shareService.getShareById(id);
if (share == null) {
error(request, response, ErrorCode.NOT_FOUND, "Shared media not found.");
return;
}
if (!user.isAdminRole() && !share.getUsername().equals(user.getUsername())) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Not authorized to delete shared media.");
return;
}
shareService.deleteShare(id);
writeEmptyResponse(request, response);
}
@RequestMapping("/updateShare")
public void updateShare(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
int id = getRequiredIntParameter(request, "id");
org.airsonic.player.domain.Share share = shareService.getShareById(id);
if (share == null) {
error(request, response, ErrorCode.NOT_FOUND, "Shared media not found.");
return;
}
if (!user.isAdminRole() && !share.getUsername().equals(user.getUsername())) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Not authorized to modify shared media.");
return;
}
share.setDescription(request.getParameter("description"));
String expiresString = request.getParameter("expires");
if (expiresString != null) {
long expires = Long.parseLong(expiresString);
share.setExpires(expires == 0L ? null : new Date(expires));
}
shareService.updateShare(share);
writeEmptyResponse(request, response);
}
private org.subsonic.restapi.Share createJaxbShare(HttpServletRequest request, org.airsonic.player.domain.Share share) {
org.subsonic.restapi.Share result = new org.subsonic.restapi.Share();
result.setId(String.valueOf(share.getId()));
result.setUrl(shareService.getShareUrl(request, share));
result.setUsername(share.getUsername());
result.setCreated(jaxbWriter.convertDate(share.getCreated()));
result.setVisitCount(share.getVisitCount());
result.setDescription(share.getDescription());
result.setExpires(jaxbWriter.convertDate(share.getExpires()));
result.setLastVisited(jaxbWriter.convertDate(share.getLastVisited()));
return result;
}
@RequestMapping("/getCoverArt")
public void getCoverArt(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
coverArtController.handleRequest(request, response);
}
@RequestMapping("/getAvatar")
public void getAvatar(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
avatarController.handleRequest(request, response);
}
@RequestMapping("/changePassword")
public void changePassword(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = getRequiredStringParameter(request, "username");
String password = decrypt(getRequiredStringParameter(request, "password"));
org.airsonic.player.domain.User authUser = securityService.getCurrentUser(request);
boolean allowed = authUser.isAdminRole()
|| username.equals(authUser.getUsername()) && authUser.isSettingsRole();
if (!allowed) {
error(request, response, ErrorCode.NOT_AUTHORIZED, authUser.getUsername() + " is not authorized to change password for " + username);
return;
}
org.airsonic.player.domain.User user = securityService.getUserByName(username);
user.setPassword(password);
securityService.updateUser(user);
writeEmptyResponse(request, response);
}
@RequestMapping("/getUser")
public void getUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
String username = getRequiredStringParameter(request, "username");
org.airsonic.player.domain.User currentUser = securityService.getCurrentUser(request);
if (!username.equals(currentUser.getUsername()) && !currentUser.isAdminRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, currentUser.getUsername() + " is not authorized to get details for other users.");
return;
}
org.airsonic.player.domain.User requestedUser = securityService.getUserByName(username);
if (requestedUser == null) {
error(request, response, ErrorCode.NOT_FOUND, "No such user: " + username);
return;
}
Response res = createResponse();
res.setUser(createJaxbUser(requestedUser));
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/getUsers")
public void getUsers(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
org.airsonic.player.domain.User currentUser = securityService.getCurrentUser(request);
if (!currentUser.isAdminRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, currentUser.getUsername() + " is not authorized to get details for other users.");
return;
}
Users result = new Users();
for (org.airsonic.player.domain.User user : securityService.getAllUsers()) {
result.getUser().add(createJaxbUser(user));
}
Response res = createResponse();
res.setUsers(result);
jaxbWriter.writeResponse(request, response, res);
}
private org.subsonic.restapi.User createJaxbUser(org.airsonic.player.domain.User user) {
UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
org.subsonic.restapi.User result = new org.subsonic.restapi.User();
result.setUsername(user.getUsername());
result.setEmail(user.getEmail());
result.setScrobblingEnabled(userSettings.isLastFmEnabled());
result.setAdminRole(user.isAdminRole());
result.setSettingsRole(user.isSettingsRole());
result.setDownloadRole(user.isDownloadRole());
result.setUploadRole(user.isUploadRole());
result.setPlaylistRole(true); // Since 1.8.0
result.setCoverArtRole(user.isCoverArtRole());
result.setCommentRole(user.isCommentRole());
result.setPodcastRole(user.isPodcastRole());
result.setStreamRole(user.isStreamRole());
result.setJukeboxRole(user.isJukeboxRole());
result.setShareRole(user.isShareRole());
// currently this role isn't supported by airsonic
result.setVideoConversionRole(false);
// Useless
result.setAvatarLastChanged(null);
TranscodeScheme transcodeScheme = userSettings.getTranscodeScheme();
if (transcodeScheme != null && transcodeScheme != TranscodeScheme.OFF) {
result.setMaxBitRate(transcodeScheme.getMaxBitRate());
}
List<org.airsonic.player.domain.MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(user.getUsername());
for (org.airsonic.player.domain.MusicFolder musicFolder : musicFolders) {
result.getFolder().add(musicFolder.getId());
}
return result;
}
@RequestMapping("/createUser")
public void createUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isAdminRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to create new users.");
return;
}
UserSettingsCommand command = new UserSettingsCommand();
command.setUsername(getRequiredStringParameter(request, "username"));
command.setPassword(decrypt(getRequiredStringParameter(request, "password")));
command.setEmail(getRequiredStringParameter(request, "email"));
command.setLdapAuthenticated(getBooleanParameter(request, "ldapAuthenticated", false));
command.setAdminRole(getBooleanParameter(request, "adminRole", false));
command.setCommentRole(getBooleanParameter(request, "commentRole", false));
command.setCoverArtRole(getBooleanParameter(request, "coverArtRole", false));
command.setDownloadRole(getBooleanParameter(request, "downloadRole", false));
command.setStreamRole(getBooleanParameter(request, "streamRole", true));
command.setUploadRole(getBooleanParameter(request, "uploadRole", false));
command.setJukeboxRole(getBooleanParameter(request, "jukeboxRole", false));
command.setPodcastRole(getBooleanParameter(request, "podcastRole", false));
command.setSettingsRole(getBooleanParameter(request, "settingsRole", true));
command.setShareRole(getBooleanParameter(request, "shareRole", false));
command.setTranscodeSchemeName(TranscodeScheme.OFF.name());
int[] folderIds = ServletRequestUtils.getIntParameters(request, "musicFolderId");
if (folderIds.length == 0) {
folderIds = Util.toIntArray(org.airsonic.player.domain.MusicFolder.toIdList(settingsService.getAllMusicFolders()));
}
command.setAllowedMusicFolderIds(folderIds);
userSettingsController.createUser(command);
writeEmptyResponse(request, response);
}
@RequestMapping("/updateUser")
public void updateUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isAdminRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to update users.");
return;
}
String username = getRequiredStringParameter(request, "username");
org.airsonic.player.domain.User u = securityService.getUserByName(username);
UserSettings s = settingsService.getUserSettings(username);
if (u == null) {
error(request, response, ErrorCode.NOT_FOUND, "No such user: " + username);
return;
} else if (org.airsonic.player.domain.User.USERNAME_ADMIN.equals(username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Not allowed to change admin user");
return;
}
UserSettingsCommand command = new UserSettingsCommand();
command.setUsername(username);
command.setEmail(getStringParameter(request, "email", u.getEmail()));
command.setLdapAuthenticated(getBooleanParameter(request, "ldapAuthenticated", u.isLdapAuthenticated()));
command.setAdminRole(getBooleanParameter(request, "adminRole", u.isAdminRole()));
command.setCommentRole(getBooleanParameter(request, "commentRole", u.isCommentRole()));
command.setCoverArtRole(getBooleanParameter(request, "coverArtRole", u.isCoverArtRole()));
command.setDownloadRole(getBooleanParameter(request, "downloadRole", u.isDownloadRole()));
command.setStreamRole(getBooleanParameter(request, "streamRole", u.isDownloadRole()));
command.setUploadRole(getBooleanParameter(request, "uploadRole", u.isUploadRole()));
command.setJukeboxRole(getBooleanParameter(request, "jukeboxRole", u.isJukeboxRole()));
command.setPodcastRole(getBooleanParameter(request, "podcastRole", u.isPodcastRole()));
command.setSettingsRole(getBooleanParameter(request, "settingsRole", u.isSettingsRole()));
command.setShareRole(getBooleanParameter(request, "shareRole", u.isShareRole()));
int maxBitRate = getIntParameter(request, "maxBitRate", s.getTranscodeScheme().getMaxBitRate());
command.setTranscodeSchemeName(TranscodeScheme.fromMaxBitRate(maxBitRate).name());
if (hasParameter(request, "password")) {
command.setPassword(decrypt(getRequiredStringParameter(request, "password")));
command.setPasswordChange(true);
}
int[] folderIds = ServletRequestUtils.getIntParameters(request, "musicFolderId");
if (folderIds.length == 0) {
folderIds = Util.toIntArray(org.airsonic.player.domain.MusicFolder.toIdList(settingsService.getMusicFoldersForUser(username)));
}
command.setAllowedMusicFolderIds(folderIds);
userSettingsController.updateUser(command);
writeEmptyResponse(request, response);
}
private boolean hasParameter(HttpServletRequest request, String name) {
return request.getParameter(name) != null;
}
@RequestMapping("/deleteUser")
public void deleteUser(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isAdminRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to delete users.");
return;
}
String username = getRequiredStringParameter(request, "username");
if (org.airsonic.player.domain.User.USERNAME_ADMIN.equals(username)) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Not allowed to delete admin user");
return;
}
securityService.deleteUser(username);
writeEmptyResponse(request, response);
}
@RequestMapping("/getChatMessages")
public ResponseEntity<String> getChatMessages(HttpServletRequest request, HttpServletResponse response) {
return ResponseEntity.status(HttpStatus.SC_GONE).body(NO_LONGER_SUPPORTED);
}
@RequestMapping("/addChatMessage")
public ResponseEntity<String> addChatMessage(HttpServletRequest request, HttpServletResponse response) {
return ResponseEntity.status(HttpStatus.SC_GONE).body(NO_LONGER_SUPPORTED);
}
@RequestMapping("/getLyrics")
public void getLyrics(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
String artist = request.getParameter("artist");
String title = request.getParameter("title");
LyricsInfo lyrics = lyricsService.getLyrics(artist, title);
Lyrics result = new Lyrics();
result.setArtist(lyrics.getArtist());
result.setTitle(lyrics.getTitle());
result.setContent(lyrics.getLyrics());
Response res = createResponse();
res.setLyrics(result);
jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping("/setRating")
public void setRating(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
Integer rating = getRequiredIntParameter(request, "rating");
if (rating == 0) {
rating = null;
}
int id = getRequiredIntParameter(request, "id");
MediaFile mediaFile = mediaFileService.getMediaFile(id);
if (mediaFile == null) {
error(request, response, ErrorCode.NOT_FOUND, "File not found: " + id);
return;
}
String username = securityService.getCurrentUsername(request);
ratingService.setRatingForUser(username, mediaFile, rating);
writeEmptyResponse(request, response);
}
@RequestMapping(path = "/getAlbumInfo")
public void getAlbumInfo(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
MediaFile mediaFile = this.mediaFileService.getMediaFile(id);
if (mediaFile == null) {
error(request, response, SubsonicRESTController.ErrorCode.NOT_FOUND, "Media file not found.");
return;
}
AlbumNotes albumNotes = this.lastFmService.getAlbumNotes(mediaFile);
AlbumInfo result = getAlbumInfoInternal(albumNotes);
Response res = createResponse();
res.setAlbumInfo(result);
this.jaxbWriter.writeResponse(request, response, res);
}
@RequestMapping(path = "/getAlbumInfo2")
public void getAlbumInfo2(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request);
int id = ServletRequestUtils.getRequiredIntParameter(request, "id");
Album album = this.albumDao.getAlbum(id);
if (album == null) {
error(request, response, SubsonicRESTController.ErrorCode.NOT_FOUND, "Album not found.");
return;
}
AlbumNotes albumNotes = this.lastFmService.getAlbumNotes(album);
AlbumInfo result = getAlbumInfoInternal(albumNotes);
Response res = createResponse();
res.setAlbumInfo(result);
this.jaxbWriter.writeResponse(request, response, res);
}
private AlbumInfo getAlbumInfoInternal(AlbumNotes albumNotes) {
AlbumInfo result = new AlbumInfo();
if (albumNotes != null) {
result.setNotes(albumNotes.getNotes());
result.setMusicBrainzId(albumNotes.getMusicBrainzId());
result.setLastFmUrl(albumNotes.getLastFmUrl());
result.setSmallImageUrl(albumNotes.getSmallImageUrl());
result.setMediumImageUrl(albumNotes.getMediumImageUrl());
result.setLargeImageUrl(albumNotes.getLargeImageUrl());
}
return result;
}
@RequestMapping("/getVideoInfo")
public ResponseEntity<String> getVideoInfo() {
return ResponseEntity.status(HttpStatus.SC_NOT_IMPLEMENTED).body(NOT_YET_IMPLEMENTED);
}
@RequestMapping("/getCaptions")
public ResponseEntity<String> getCaptions() {
return ResponseEntity.status(HttpStatus.SC_NOT_IMPLEMENTED).body(NOT_YET_IMPLEMENTED);
}
@RequestMapping("/startScan")
public void startScan(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
mediaScannerService.scanLibrary();
getScanStatus(request, response);
}
@RequestMapping("/getScanStatus")
public void getScanStatus(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
ScanStatus scanStatus = new ScanStatus();
scanStatus.setScanning(this.mediaScannerService.isScanning());
scanStatus.setCount((long) this.mediaScannerService.getScanCount());
Response res = createResponse();
res.setScanStatus(scanStatus);
this.jaxbWriter.writeResponse(request, response, res);
}
private HttpServletRequest wrapRequest(HttpServletRequest request) {
return wrapRequest(request, false);
}
private HttpServletRequest wrapRequest(final HttpServletRequest request, boolean jukebox) {
final String playerId = createPlayerIfNecessary(request, jukebox);
return new HttpServletRequestWrapper(request) {
@Override
public String getParameter(String name) {
// Returns the correct player to be used in PlayerService.getPlayer()
if ("player".equals(name)) {
return playerId;
}
// Support old style ID parameters.
if ("id".equals(name)) {
return mapId(request.getParameter("id"));
}
return super.getParameter(name);
}
};
}
private String mapId(String id) {
if (id == null || id.startsWith(CoverArtController.ALBUM_COVERART_PREFIX) ||
id.startsWith(CoverArtController.ARTIST_COVERART_PREFIX) || StringUtils.isNumeric(id)) {
return id;
}
try {
String path = StringUtil.utf8HexDecode(id);
MediaFile mediaFile = mediaFileService.getMediaFile(path);
return String.valueOf(mediaFile.getId());
} catch (Exception x) {
return id;
}
}
private Response createResponse() {
return jaxbWriter.createResponse(true);
}
private void writeEmptyResponse(HttpServletRequest request, HttpServletResponse response) {
jaxbWriter.writeResponse(request, response, createResponse());
}
public void error(HttpServletRequest request, HttpServletResponse response, ErrorCode code, String message) {
jaxbWriter.writeErrorResponse(request, response, code, message);
}
private String createPlayerIfNecessary(HttpServletRequest request, boolean jukebox) {
String username = request.getRemoteUser();
String clientId = request.getParameter("c");
if (jukebox) {
clientId += "-jukebox";
}
List<Player> players = playerService.getPlayersForUserAndClientId(username, clientId);
// If not found, create it.
if (players.isEmpty()) {
Player player = new Player();
player.setIpAddress(request.getRemoteAddr());
player.setUsername(username);
player.setClientId(clientId);
player.setName(clientId);
player.setTechnology(jukebox ? PlayerTechnology.JUKEBOX : PlayerTechnology.EXTERNAL_WITH_PLAYLIST);
playerService.createPlayer(player);
players = playerService.getPlayersForUserAndClientId(username, clientId);
}
// Return the player ID.
return !players.isEmpty() ? String.valueOf(players.get(0).getId()) : null;
}
public enum ErrorCode {
GENERIC(0, "A generic error."),
MISSING_PARAMETER(10, "Required parameter is missing."),
PROTOCOL_MISMATCH_CLIENT_TOO_OLD(20, "Incompatible Airsonic REST protocol version. Client must upgrade."),
PROTOCOL_MISMATCH_SERVER_TOO_OLD(30, "Incompatible Airsonic REST protocol version. Server must upgrade."),
NOT_AUTHENTICATED(40, "Wrong username or password."),
NOT_AUTHORIZED(50, "User is not authorized for the given operation."),
NOT_FOUND(70, "Requested data was not found.");
private final int code;
private final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
}