Update to mostly support subsonic api 1.15.0

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
master
Andrew DeMaria 8 years ago
parent bcd330e74d
commit c9931f5906
No known key found for this signature in database
GPG Key ID: 0A3F5E91F8364EDF
  1. 50
      libresonic-main/src/main/java/org/libresonic/player/controller/JAXBWriter.java
  2. 151
      libresonic-main/src/main/java/org/libresonic/player/controller/RESTController.java
  3. 2
      libresonic-main/src/main/java/org/libresonic/player/security/GlobalSecurityConfig.java
  4. 70
      libresonic-rest-api/src/main/resources/libresonic-rest-api.xsd

@ -26,12 +26,12 @@ import org.jdom.Attribute;
import org.jdom.Document; import org.jdom.Document;
import org.jdom.input.SAXBuilder; import org.jdom.input.SAXBuilder;
import org.libresonic.player.util.StringUtil; import org.libresonic.player.util.StringUtil;
import org.libresonic.restapi.Error;
import org.libresonic.restapi.ObjectFactory;
import org.libresonic.restapi.Response;
import org.libresonic.restapi.ResponseStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.subsonic.restapi.Error;
import org.subsonic.restapi.ObjectFactory;
import org.subsonic.restapi.Response;
import org.subsonic.restapi.ResponseStatus;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -40,6 +40,7 @@ import javax.xml.bind.Marshaller;
import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.datatype.XMLGregorianCalendar;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringWriter; import java.io.StringWriter;
import java.util.Date; import java.util.Date;
@ -69,20 +70,30 @@ public class JAXBWriter {
} }
} }
private Marshaller createXmlMarshaller() throws JAXBException { private Marshaller createXmlMarshaller() {
Marshaller marshaller = jaxbContext.createMarshaller(); Marshaller marshaller = null;
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8); try {
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller = jaxbContext.createMarshaller();
return marshaller; marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller;
} catch (JAXBException e) {
throw new RuntimeException(e);
}
} }
private Marshaller createJsonMarshaller() throws JAXBException { private Marshaller createJsonMarshaller() {
Marshaller marshaller = jaxbContext.createMarshaller(); try {
marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8); Marshaller marshaller;
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json"); marshaller.setProperty(Marshaller.JAXB_ENCODING, StringUtil.ENCODING_UTF8);
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
return marshaller; marshaller.setProperty(MarshallerProperties.MEDIA_TYPE, "application/json");
marshaller.setProperty(MarshallerProperties.JSON_INCLUDE_ROOT, true);
return marshaller;
} catch (JAXBException e) {
throw new RuntimeException(e);
}
} }
private String getRESTProtocolVersion() throws Exception { private String getRESTProtocolVersion() throws Exception {
@ -105,11 +116,10 @@ public class JAXBWriter {
Response response = new ObjectFactory().createResponse(); Response response = new ObjectFactory().createResponse();
response.setStatus(ok ? ResponseStatus.OK : ResponseStatus.FAILED); response.setStatus(ok ? ResponseStatus.OK : ResponseStatus.FAILED);
response.setVersion(restProtocolVersion); response.setVersion(restProtocolVersion);
response.setType("Libresonic");
return response; return response;
} }
public void writeResponse(HttpServletRequest request, HttpServletResponse httpResponse, Response jaxbResponse) throws Exception { public void writeResponse(HttpServletRequest request, HttpServletResponse httpResponse, Response jaxbResponse) {
String format = getStringParameter(request, "f", "xml"); String format = getStringParameter(request, "f", "xml");
String jsonpCallback = request.getParameter("callback"); String jsonpCallback = request.getParameter("callback");
@ -140,9 +150,9 @@ public class JAXBWriter {
writer.append(");"); writer.append(");");
} }
httpResponse.getWriter().append(writer.getBuffer()); httpResponse.getWriter().append(writer.getBuffer());
} catch (Exception x) { } catch (JAXBException | IOException x) {
LOG.error("Failed to marshal JAXB", x); LOG.error("Failed to marshal JAXB", x);
throw x; throw new RuntimeException(x);
} }
} }

@ -20,6 +20,7 @@
package org.libresonic.player.controller; package org.libresonic.player.controller;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpStatus;
import org.libresonic.player.ajax.LyricsInfo; import org.libresonic.player.ajax.LyricsInfo;
import org.libresonic.player.ajax.LyricsService; import org.libresonic.player.ajax.LyricsService;
import org.libresonic.player.ajax.PlayQueueService; import org.libresonic.player.ajax.PlayQueueService;
@ -41,17 +42,17 @@ import org.libresonic.player.service.*;
import org.libresonic.player.util.Pair; import org.libresonic.player.util.Pair;
import org.libresonic.player.util.StringUtil; import org.libresonic.player.util.StringUtil;
import org.libresonic.player.util.Util; import org.libresonic.player.util.Util;
import org.libresonic.restapi.*;
import org.libresonic.restapi.Genres;
import org.libresonic.restapi.PodcastStatus;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import org.subsonic.restapi.*;
import org.subsonic.restapi.PodcastStatus;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -137,10 +138,15 @@ public class RESTController {
private BookmarkDao bookmarkDao; private BookmarkDao bookmarkDao;
@Autowired @Autowired
private PlayQueueDao playQueueDao; private PlayQueueDao playQueueDao;
@Autowired
private MediaScannerService mediaScannerService;
private final Map<BookmarkKey, Bookmark> bookmarkCache = new ConcurrentHashMap<BookmarkKey, Bookmark>(); private final Map<BookmarkKey, Bookmark> bookmarkCache = new ConcurrentHashMap<BookmarkKey, Bookmark>();
private final JAXBWriter jaxbWriter = new JAXBWriter(); 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";
@PostConstruct @PostConstruct
public void init() { public void init() {
refreshBookmarkCache(); refreshBookmarkCache();
@ -192,7 +198,7 @@ public class RESTController {
MusicFolders musicFolders = new MusicFolders(); MusicFolders musicFolders = new MusicFolders();
String username = securityService.getCurrentUsername(request); String username = securityService.getCurrentUsername(request);
for (MusicFolder musicFolder : settingsService.getMusicFoldersForUser(username)) { for (MusicFolder musicFolder : settingsService.getMusicFoldersForUser(username)) {
org.libresonic.restapi.MusicFolder mf = new org.libresonic.restapi.MusicFolder(); org.subsonic.restapi.MusicFolder mf = new org.subsonic.restapi.MusicFolder();
mf.setId(musicFolder.getId()); mf.setId(musicFolder.getId());
mf.setName(musicFolder.getName()); mf.setName(musicFolder.getName());
musicFolders.getMusicFolder().add(mf); musicFolders.getMusicFolder().add(mf);
@ -246,7 +252,7 @@ public class RESTController {
for (MediaFile mediaFile : artist.getMediaFiles()) { for (MediaFile mediaFile : artist.getMediaFiles()) {
if (mediaFile.isDirectory()) { if (mediaFile.isDirectory()) {
Date starredDate = mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username); Date starredDate = mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username);
org.libresonic.restapi.Artist a = new org.libresonic.restapi.Artist(); org.subsonic.restapi.Artist a = new org.subsonic.restapi.Artist();
index.getArtist().add(a); index.getArtist().add(a);
a.setId(String.valueOf(mediaFile.getId())); a.setId(String.valueOf(mediaFile.getId()));
a.setName(artist.getName()); a.setName(artist.getName());
@ -275,10 +281,10 @@ public class RESTController {
@RequestMapping(value = "/getGenres", method = {RequestMethod.GET, RequestMethod.POST}) @RequestMapping(value = "/getGenres", method = {RequestMethod.GET, RequestMethod.POST})
public void getGenres(HttpServletRequest request, HttpServletResponse response) throws Exception { public void getGenres(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
Genres genres = new Genres(); org.subsonic.restapi.Genres genres = new org.subsonic.restapi.Genres();
for (Genre genre : mediaFileDao.getGenres(false)) { for (Genre genre : mediaFileDao.getGenres(false)) {
org.libresonic.restapi.Genre g = new org.libresonic.restapi.Genre(); org.subsonic.restapi.Genre g = new org.subsonic.restapi.Genre();
genres.getGenre().add(g); genres.getGenre().add(g);
g.setContent(genre.getName()); g.setContent(genre.getName());
g.setAlbumCount(genre.getAlbumCount()); g.setAlbumCount(genre.getAlbumCount());
@ -498,8 +504,8 @@ public class RESTController {
return jaxbArtist; return jaxbArtist;
} }
private org.libresonic.restapi.Artist createJaxbArtist(MediaFile artist, String username) { private org.subsonic.restapi.Artist createJaxbArtist(MediaFile artist, String username) {
org.libresonic.restapi.Artist result = new org.libresonic.restapi.Artist(); org.subsonic.restapi.Artist result = new org.subsonic.restapi.Artist();
result.setId(String.valueOf(artist.getId())); result.setId(String.valueOf(artist.getId()));
result.setName(artist.getArtist()); result.setName(artist.getArtist());
Date starred = mediaFileDao.getMediaFileStarredDate(artist.getId(), username); Date starred = mediaFileDao.getMediaFileStarredDate(artist.getId(), username);
@ -552,7 +558,7 @@ public class RESTController {
return jaxbAlbum; return jaxbAlbum;
} }
private <T extends org.libresonic.restapi.Playlist> T createJaxbPlaylist(T jaxbPlaylist, Playlist playlist) { private <T extends org.subsonic.restapi.Playlist> T createJaxbPlaylist(T jaxbPlaylist, Playlist playlist) {
jaxbPlaylist.setId(String.valueOf(playlist.getId())); jaxbPlaylist.setId(String.valueOf(playlist.getId()));
jaxbPlaylist.setName(playlist.getName()); jaxbPlaylist.setName(playlist.getName());
jaxbPlaylist.setComment(playlist.getComment()); jaxbPlaylist.setComment(playlist.getComment());
@ -644,6 +650,7 @@ public class RESTController {
} }
directory.setName(dir.getName()); directory.setName(dir.getName());
directory.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(id, username))); directory.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(id, username)));
directory.setPlayCount((long) dir.getPlayCount());
if (dir.isAlbum()) { if (dir.isAlbum()) {
directory.setAverageRating(ratingService.getAverageRating(dir)); directory.setAverageRating(ratingService.getAverageRating(dir));
@ -691,7 +698,7 @@ public class RESTController {
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username); List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
SearchResult result = searchService.search(criteria, musicFolders, SearchService.IndexType.SONG); SearchResult result = searchService.search(criteria, musicFolders, SearchService.IndexType.SONG);
org.libresonic.restapi.SearchResult searchResult = new org.libresonic.restapi.SearchResult(); org.subsonic.restapi.SearchResult searchResult = new org.subsonic.restapi.SearchResult();
searchResult.setOffset(result.getOffset()); searchResult.setOffset(result.getOffset());
searchResult.setTotalHits(result.getTotalHits()); searchResult.setTotalHits(result.getTotalHits());
@ -799,7 +806,7 @@ public class RESTController {
Playlists result = new Playlists(); Playlists result = new Playlists();
for (Playlist playlist : playlistService.getReadablePlaylistsForUser(requestedUsername)) { for (Playlist playlist : playlistService.getReadablePlaylistsForUser(requestedUsername)) {
result.getPlaylist().add(createJaxbPlaylist(new org.libresonic.restapi.Playlist(), playlist)); result.getPlaylist().add(createJaxbPlaylist(new org.subsonic.restapi.Playlist(), playlist));
} }
Response res = createResponse(); Response res = createResponse();
@ -1246,6 +1253,7 @@ public class RESTController {
child.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username))); child.setStarred(jaxbWriter.convertDate(mediaFileDao.getMediaFileStarredDate(mediaFile.getId(), username)));
child.setUserRating(ratingService.getRatingForUser(username, mediaFile)); child.setUserRating(ratingService.getRatingForUser(username, mediaFile));
child.setAverageRating(ratingService.getAverageRating(mediaFile)); child.setAverageRating(ratingService.getAverageRating(mediaFile));
child.setPlayCount((long) mediaFile.getPlayCount());
if (mediaFile.isFile()) { if (mediaFile.isFile()) {
child.setDuration(mediaFile.getDurationSeconds()); child.setDuration(mediaFile.getDurationSeconds());
@ -1544,7 +1552,7 @@ public class RESTController {
for (PodcastChannel channel : podcastService.getAllChannels()) { for (PodcastChannel channel : podcastService.getAllChannels()) {
if (channelId == null || channelId.equals(channel.getId())) { if (channelId == null || channelId.equals(channel.getId())) {
org.libresonic.restapi.PodcastChannel c = new org.libresonic.restapi.PodcastChannel(); org.subsonic.restapi.PodcastChannel c = new org.subsonic.restapi.PodcastChannel();
result.getChannel().add(c); result.getChannel().add(c);
c.setId(String.valueOf(channel.getId())); c.setId(String.valueOf(channel.getId()));
@ -1587,13 +1595,13 @@ public class RESTController {
jaxbWriter.writeResponse(request, response, res); jaxbWriter.writeResponse(request, response, res);
} }
private org.libresonic.restapi.PodcastEpisode createJaxbPodcastEpisode(Player player, String username, PodcastEpisode episode) { private org.subsonic.restapi.PodcastEpisode createJaxbPodcastEpisode(Player player, String username, PodcastEpisode episode) {
org.libresonic.restapi.PodcastEpisode e = new org.libresonic.restapi.PodcastEpisode(); org.subsonic.restapi.PodcastEpisode e = new org.subsonic.restapi.PodcastEpisode();
String path = episode.getPath(); String path = episode.getPath();
if (path != null) { if (path != null) {
MediaFile mediaFile = mediaFileService.getMediaFile(path); MediaFile mediaFile = mediaFileService.getMediaFile(path);
e = createJaxbChild(new org.libresonic.restapi.PodcastEpisode(), player, mediaFile, username); e = createJaxbChild(new org.subsonic.restapi.PodcastEpisode(), player, mediaFile, username);
e.setStreamId(String.valueOf(mediaFile.getId())); e.setStreamId(String.valueOf(mediaFile.getId()));
} }
@ -1706,7 +1714,7 @@ public class RESTController {
Bookmarks result = new Bookmarks(); Bookmarks result = new Bookmarks();
for (Bookmark bookmark : bookmarkDao.getBookmarks(username)) { for (Bookmark bookmark : bookmarkDao.getBookmarks(username)) {
org.libresonic.restapi.Bookmark b = new org.libresonic.restapi.Bookmark(); org.subsonic.restapi.Bookmark b = new org.subsonic.restapi.Bookmark();
result.getBookmark().add(b); result.getBookmark().add(b);
b.setPosition(bookmark.getPositionMillis()); b.setPosition(bookmark.getPositionMillis());
b.setUsername(bookmark.getUsername()); b.setUsername(bookmark.getUsername());
@ -1762,7 +1770,7 @@ public class RESTController {
return; return;
} }
org.libresonic.restapi.PlayQueue restPlayQueue = new org.libresonic.restapi.PlayQueue(); org.subsonic.restapi.PlayQueue restPlayQueue = new org.subsonic.restapi.PlayQueue();
restPlayQueue.setUsername(playQueue.getUsername()); restPlayQueue.setUsername(playQueue.getUsername());
restPlayQueue.setCurrent(playQueue.getCurrentMediaFileId()); restPlayQueue.setCurrent(playQueue.getCurrentMediaFileId());
restPlayQueue.setPosition(playQueue.getPositionMillis()); restPlayQueue.setPosition(playQueue.getPositionMillis());
@ -1811,7 +1819,7 @@ public class RESTController {
Shares result = new Shares(); Shares result = new Shares();
for (Share share : shareService.getSharesForUser(user)) { for (Share share : shareService.getSharesForUser(user)) {
org.libresonic.restapi.Share s = createJaxbShare(request, share); org.subsonic.restapi.Share s = createJaxbShare(request, share);
result.getShare().add(s); result.getShare().add(s);
for (MediaFile mediaFile : shareService.getSharedFiles(share.getId(), musicFolders)) { for (MediaFile mediaFile : shareService.getSharedFiles(share.getId(), musicFolders)) {
@ -1849,7 +1857,7 @@ public class RESTController {
shareService.updateShare(share); shareService.updateShare(share);
Shares result = new Shares(); Shares result = new Shares();
org.libresonic.restapi.Share s = createJaxbShare(request, share); org.subsonic.restapi.Share s = createJaxbShare(request, share);
result.getShare().add(s); result.getShare().add(s);
List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username); List<MusicFolder> musicFolders = settingsService.getMusicFoldersForUser(username);
@ -1909,8 +1917,8 @@ public class RESTController {
writeEmptyResponse(request, response); writeEmptyResponse(request, response);
} }
private org.libresonic.restapi.Share createJaxbShare(HttpServletRequest request, Share share) { private org.subsonic.restapi.Share createJaxbShare(HttpServletRequest request, Share share) {
org.libresonic.restapi.Share result = new org.libresonic.restapi.Share(); org.subsonic.restapi.Share result = new org.subsonic.restapi.Share();
result.setId(String.valueOf(share.getId())); result.setId(String.valueOf(share.getId()));
result.setUrl(shareService.getShareUrl(request, share)); result.setUrl(shareService.getShareUrl(request, share));
result.setUsername(share.getUsername()); result.setUsername(share.getUsername());
@ -2035,10 +2043,10 @@ public class RESTController {
jaxbWriter.writeResponse(request, response, res); jaxbWriter.writeResponse(request, response, res);
} }
private org.libresonic.restapi.User createJaxbUser(User user) { private org.subsonic.restapi.User createJaxbUser(User user) {
UserSettings userSettings = settingsService.getUserSettings(user.getUsername()); UserSettings userSettings = settingsService.getUserSettings(user.getUsername());
org.libresonic.restapi.User result = new org.libresonic.restapi.User(); org.subsonic.restapi.User result = new org.subsonic.restapi.User();
result.setUsername(user.getUsername()); result.setUsername(user.getUsername());
result.setEmail(user.getEmail()); result.setEmail(user.getEmail());
result.setScrobblingEnabled(userSettings.isLastFmEnabled()); result.setScrobblingEnabled(userSettings.isLastFmEnabled());
@ -2053,6 +2061,10 @@ public class RESTController {
result.setStreamRole(user.isStreamRole()); result.setStreamRole(user.isStreamRole());
result.setJukeboxRole(user.isJukeboxRole()); result.setJukeboxRole(user.isJukeboxRole());
result.setShareRole(user.isShareRole()); result.setShareRole(user.isShareRole());
// currently this role isn't supported by libresonic
result.setVideoConversionRole(false);
// Useless
result.setAvatarLastChanged(null);
TranscodeScheme transcodeScheme = userSettings.getTranscodeScheme(); TranscodeScheme transcodeScheme = userSettings.getTranscodeScheme();
if (transcodeScheme != null && transcodeScheme != TranscodeScheme.OFF) { if (transcodeScheme != null && transcodeScheme != TranscodeScheme.OFF) {
@ -2180,6 +2192,16 @@ public class RESTController {
writeEmptyResponse(request, response); writeEmptyResponse(request, response);
} }
@RequestMapping(value = "/getChatMessages", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseEntity<String> getChatMessages(HttpServletRequest request, HttpServletResponse response) {
return ResponseEntity.status(HttpStatus.SC_GONE).body(NO_LONGER_SUPPORTED);
}
@RequestMapping(value = "/addChatMessage", method = {RequestMethod.GET, RequestMethod.POST})
public ResponseEntity<String> addChatMessage(HttpServletRequest request, HttpServletResponse response) {
return ResponseEntity.status(HttpStatus.SC_GONE).body(NO_LONGER_SUPPORTED);
}
@RequestMapping(value = "/getLyrics", method = {RequestMethod.GET, RequestMethod.POST}) @RequestMapping(value = "/getLyrics", method = {RequestMethod.GET, RequestMethod.POST})
public void getLyrics(HttpServletRequest request, HttpServletResponse response) throws Exception { public void getLyrics(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
@ -2218,6 +2240,87 @@ public class RESTController {
writeEmptyResponse(request, response); writeEmptyResponse(request, response);
} }
@RequestMapping(path = "/getAlbumInfo", method = RequestMethod.GET)
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, RESTController.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", method = RequestMethod.GET)
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, RESTController.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(value = "/getVideoInfo", method = RequestMethod.GET)
public ResponseEntity<String> getVideoInfo() throws Exception {
return ResponseEntity.status(HttpStatus.SC_NOT_IMPLEMENTED).body(NOT_YET_IMPLEMENTED);
}
@RequestMapping(value = "/getCaptions", method = RequestMethod.GET)
public ResponseEntity<String> getCaptions() {
return ResponseEntity.status(HttpStatus.SC_NOT_IMPLEMENTED).body(NOT_YET_IMPLEMENTED);
}
@RequestMapping(value = "/startScan", method = {RequestMethod.PUT})
public void startScan(HttpServletRequest request, HttpServletResponse response) {
request = wrapRequest(request);
mediaScannerService.scanLibrary();
getScanStatus(request, response);
}
@RequestMapping(value = "/getScanStatus", method = {RequestMethod.GET})
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) { private HttpServletRequest wrapRequest(HttpServletRequest request) {
return wrapRequest(request, false); return wrapRequest(request, false);
} }

@ -133,7 +133,7 @@ public class GlobalSecurityConfig extends GlobalAuthenticationConfigurerAdapter
"/playerSettings*", "/shareSettings*", "/passwordSettings*") "/playerSettings*", "/shareSettings*", "/passwordSettings*")
.hasRole("SETTINGS") .hasRole("SETTINGS")
.antMatchers("/generalSettings*", "/advancedSettings*", "/userSettings*", .antMatchers("/generalSettings*", "/advancedSettings*", "/userSettings*",
"/musicFolderSettings*", "/databaseSettings*") "/musicFolderSettings*", "/databaseSettings*", "/rest/startScan*")
.hasRole("ADMIN") .hasRole("ADMIN")
.antMatchers("/deletePlaylist*", "/savePlaylist*", "/db*") .antMatchers("/deletePlaylist*", "/savePlaylist*", "/db*")
.hasRole("PLAYLIST") .hasRole("PLAYLIST")

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:sub="http://libresonic.org/restapi" xmlns:sub="http://subsonic.org/restapi"
targetNamespace="http://libresonic.org/restapi" targetNamespace="http://subsonic.org/restapi"
attributeFormDefault="unqualified" attributeFormDefault="unqualified"
elementFormDefault="qualified" elementFormDefault="qualified"
version="1.14.0"> version="1.15.0">
<xs:element name="subsonic-response" type="sub:Response"/> <xs:element name="subsonic-response" type="sub:Response"/>
@ -19,6 +19,7 @@
<xs:element name="album" type="sub:AlbumWithSongsID3" minOccurs="1" maxOccurs="1"/> <xs:element name="album" type="sub:AlbumWithSongsID3" minOccurs="1" maxOccurs="1"/>
<xs:element name="song" type="sub:Child" minOccurs="1" maxOccurs="1"/> <xs:element name="song" type="sub:Child" minOccurs="1" maxOccurs="1"/>
<xs:element name="videos" type="sub:Videos" minOccurs="1" maxOccurs="1"/> <xs:element name="videos" type="sub:Videos" minOccurs="1" maxOccurs="1"/>
<xs:element name="videoInfo" type="sub:VideoInfo" minOccurs="1" maxOccurs="1"/>
<xs:element name="nowPlaying" type="sub:NowPlaying" minOccurs="1" maxOccurs="1"/> <xs:element name="nowPlaying" type="sub:NowPlaying" minOccurs="1" maxOccurs="1"/>
<xs:element name="searchResult" type="sub:SearchResult" minOccurs="1" maxOccurs="1"/> <xs:element name="searchResult" type="sub:SearchResult" minOccurs="1" maxOccurs="1"/>
<xs:element name="searchResult2" type="sub:SearchResult2" minOccurs="1" maxOccurs="1"/> <xs:element name="searchResult2" type="sub:SearchResult2" minOccurs="1" maxOccurs="1"/>
@ -30,6 +31,7 @@
<xs:element name="license" type="sub:License" minOccurs="1" maxOccurs="1"/> <xs:element name="license" type="sub:License" minOccurs="1" maxOccurs="1"/>
<xs:element name="users" type="sub:Users" minOccurs="1" maxOccurs="1"/> <xs:element name="users" type="sub:Users" minOccurs="1" maxOccurs="1"/>
<xs:element name="user" type="sub:User" minOccurs="1" maxOccurs="1"/> <xs:element name="user" type="sub:User" minOccurs="1" maxOccurs="1"/>
<xs:element name="chatMessages" type="sub:ChatMessages" minOccurs="1" maxOccurs="1"/>
<xs:element name="albumList" type="sub:AlbumList" minOccurs="1" maxOccurs="1"/> <xs:element name="albumList" type="sub:AlbumList" minOccurs="1" maxOccurs="1"/>
<xs:element name="albumList2" type="sub:AlbumList2" minOccurs="1" maxOccurs="1"/> <xs:element name="albumList2" type="sub:AlbumList2" minOccurs="1" maxOccurs="1"/>
<xs:element name="randomSongs" type="sub:Songs" minOccurs="1" maxOccurs="1"/> <xs:element name="randomSongs" type="sub:Songs" minOccurs="1" maxOccurs="1"/>
@ -43,16 +45,17 @@
<xs:element name="shares" type="sub:Shares" minOccurs="1" maxOccurs="1"/> <xs:element name="shares" type="sub:Shares" minOccurs="1" maxOccurs="1"/>
<xs:element name="starred" type="sub:Starred" minOccurs="1" maxOccurs="1"/> <xs:element name="starred" type="sub:Starred" minOccurs="1" maxOccurs="1"/>
<xs:element name="starred2" type="sub:Starred2" minOccurs="1" maxOccurs="1"/> <xs:element name="starred2" type="sub:Starred2" minOccurs="1" maxOccurs="1"/>
<xs:element name="albumInfo" type="sub:AlbumInfo" minOccurs="1" maxOccurs="1"/>
<xs:element name="artistInfo" type="sub:ArtistInfo" minOccurs="1" maxOccurs="1"/> <xs:element name="artistInfo" type="sub:ArtistInfo" minOccurs="1" maxOccurs="1"/>
<xs:element name="artistInfo2" type="sub:ArtistInfo2" minOccurs="1" maxOccurs="1"/> <xs:element name="artistInfo2" type="sub:ArtistInfo2" minOccurs="1" maxOccurs="1"/>
<xs:element name="similarSongs" type="sub:SimilarSongs" minOccurs="1" maxOccurs="1"/> <xs:element name="similarSongs" type="sub:SimilarSongs" minOccurs="1" maxOccurs="1"/>
<xs:element name="similarSongs2" type="sub:SimilarSongs2" minOccurs="1" maxOccurs="1"/> <xs:element name="similarSongs2" type="sub:SimilarSongs2" minOccurs="1" maxOccurs="1"/>
<xs:element name="topSongs" type="sub:TopSongs" minOccurs="1" maxOccurs="1"/> <xs:element name="topSongs" type="sub:TopSongs" minOccurs="1" maxOccurs="1"/>
<xs:element name="scanStatus" type="sub:ScanStatus" minOccurs="1" maxOccurs="1"/>
<xs:element name="error" type="sub:Error" minOccurs="1" maxOccurs="1"/> <xs:element name="error" type="sub:Error" minOccurs="1" maxOccurs="1"/>
</xs:choice> </xs:choice>
<xs:attribute name="status" type="sub:ResponseStatus" use="required"/> <xs:attribute name="status" type="sub:ResponseStatus" use="required"/>
<xs:attribute name="version" type="sub:Version" use="required"/> <xs:attribute name="version" type="sub:Version" use="required"/>
<xs:attribute name="type" type="xs:string" use="required" />
</xs:complexType> </xs:complexType>
<xs:simpleType name="ResponseStatus"> <xs:simpleType name="ResponseStatus">
@ -155,6 +158,7 @@
<xs:attribute name="coverArt" type="xs:string" use="optional"/> <xs:attribute name="coverArt" type="xs:string" use="optional"/>
<xs:attribute name="songCount" type="xs:int" use="required"/> <xs:attribute name="songCount" type="xs:int" use="required"/>
<xs:attribute name="duration" type="xs:int" use="required"/> <xs:attribute name="duration" type="xs:int" use="required"/>
<xs:attribute name="playCount" type="xs:long" use="optional"/> <!-- Added in 1.14.0 -->
<xs:attribute name="created" type="xs:dateTime" use="required"/> <xs:attribute name="created" type="xs:dateTime" use="required"/>
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <xs:attribute name="starred" type="xs:dateTime" use="optional"/>
<xs:attribute name="year" type="xs:int" use="optional"/> <!-- Added in 1.10.1 --> <xs:attribute name="year" type="xs:int" use="optional"/> <!-- Added in 1.10.1 -->
@ -177,6 +181,32 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="VideoInfo">
<xs:sequence>
<xs:element name="captions" type="sub:Captions" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="audioTrack" type="sub:AudioTrack" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="conversion" type="sub:VideoConversion" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="Captions">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="name" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="AudioTrack">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="name" type="xs:string" use="optional"/>
<xs:attribute name="languageCode" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="VideoConversion">
<xs:attribute name="id" type="xs:string" use="required"/>
<xs:attribute name="bitRate" type="xs:int" use="optional"/> <!-- In Kbps -->
<xs:attribute name="audioTrackId" type="xs:int" use="optional"/>
</xs:complexType>
<xs:complexType name="Directory"> <xs:complexType name="Directory">
<xs:sequence> <xs:sequence>
<xs:element name="child" type="sub:Child" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="child" type="sub:Child" minOccurs="0" maxOccurs="unbounded"/>
@ -187,6 +217,7 @@
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.10.1 --> <xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.10.1 -->
<xs:attribute name="userRating" type="sub:UserRating" use="optional"/> <!-- Added in 1.13.0 --> <xs:attribute name="userRating" type="sub:UserRating" use="optional"/> <!-- Added in 1.13.0 -->
<xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.13.0 --> <xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.13.0 -->
<xs:attribute name="playCount" type="xs:long" use="optional"/> <!-- Added in 1.14.0 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="Child"> <xs:complexType name="Child">
@ -211,6 +242,7 @@
<xs:attribute name="isVideo" type="xs:boolean" use="optional"/> <!-- Added in 1.4.1 --> <xs:attribute name="isVideo" type="xs:boolean" use="optional"/> <!-- Added in 1.4.1 -->
<xs:attribute name="userRating" type="sub:UserRating" use="optional"/> <!-- Added in 1.6.0 --> <xs:attribute name="userRating" type="sub:UserRating" use="optional"/> <!-- Added in 1.6.0 -->
<xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.6.0 --> <xs:attribute name="averageRating" type="sub:AverageRating" use="optional"/> <!-- Added in 1.6.0 -->
<xs:attribute name="playCount" type="xs:long" use="optional"/> <!-- Added in 1.14.0 -->
<xs:attribute name="discNumber" type="xs:int" use="optional"/> <!-- Added in 1.8.0 --> <xs:attribute name="discNumber" type="xs:int" use="optional"/> <!-- Added in 1.8.0 -->
<xs:attribute name="created" type="xs:dateTime" use="optional"/> <!-- Added in 1.8.0 --> <xs:attribute name="created" type="xs:dateTime" use="optional"/> <!-- Added in 1.8.0 -->
<xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.8.0 --> <xs:attribute name="starred" type="xs:dateTime" use="optional"/> <!-- Added in 1.8.0 -->
@ -336,6 +368,18 @@
</xs:complexContent> </xs:complexContent>
</xs:complexType> </xs:complexType>
<xs:complexType name="ChatMessages">
<xs:sequence>
<xs:element name="chatMessage" type="sub:ChatMessage" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ChatMessage">
<xs:attribute name="username" type="xs:string" use="required"/>
<xs:attribute name="time" type="xs:long" use="required"/>
<xs:attribute name="message" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="AlbumList"> <xs:complexType name="AlbumList">
<xs:sequence> <xs:sequence>
<xs:element name="album" type="sub:Child" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="album" type="sub:Child" minOccurs="0" maxOccurs="unbounded"/>
@ -477,6 +521,17 @@
</xs:sequence> </xs:sequence>
</xs:complexType> </xs:complexType>
<xs:complexType name="AlbumInfo">
<xs:sequence>
<xs:element name="notes" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="musicBrainzId" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="lastFmUrl" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="smallImageUrl" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="mediumImageUrl" type="xs:string" minOccurs="0" maxOccurs="1"/>
<xs:element name="largeImageUrl" type="xs:string" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ArtistInfoBase"> <xs:complexType name="ArtistInfoBase">
<xs:sequence> <xs:sequence>
<xs:element name="biography" type="xs:string" minOccurs="0" maxOccurs="1"/> <xs:element name="biography" type="xs:string" minOccurs="0" maxOccurs="1"/>
@ -541,6 +596,11 @@
<xs:attribute name="trialExpires" type="xs:dateTime" use="optional"/> <xs:attribute name="trialExpires" type="xs:dateTime" use="optional"/>
</xs:complexType> </xs:complexType>
<xs:complexType name="ScanStatus">
<xs:attribute name="scanning" type="xs:boolean" use="required"/>
<xs:attribute name="count" type="xs:long" use="optional"/>
</xs:complexType>
<xs:complexType name="Users"> <xs:complexType name="Users">
<xs:sequence> <xs:sequence>
<xs:element name="user" type="sub:User" minOccurs="0" maxOccurs="unbounded"/> <xs:element name="user" type="sub:User" minOccurs="0" maxOccurs="unbounded"/>
@ -566,6 +626,8 @@
<xs:attribute name="streamRole" type="xs:boolean" use="required"/> <xs:attribute name="streamRole" type="xs:boolean" use="required"/>
<xs:attribute name="jukeboxRole" type="xs:boolean" use="required"/> <xs:attribute name="jukeboxRole" type="xs:boolean" use="required"/>
<xs:attribute name="shareRole" type="xs:boolean" use="required"/> <!-- Added in 1.7.0 --> <xs:attribute name="shareRole" type="xs:boolean" use="required"/> <!-- Added in 1.7.0 -->
<xs:attribute name="videoConversionRole" type="xs:boolean" use="required"/> <!-- Added in 1.14.0 -->
<xs:attribute name="avatarLastChanged" type="xs:dateTime" use="optional"/> <!-- Added in 1.14.0 -->
</xs:complexType> </xs:complexType>
<xs:complexType name="Error"> <xs:complexType name="Error">

Loading…
Cancel
Save