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.
2389 lines
106 KiB
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;
|
|
}
|
|
}
|
|
}
|
|
|