REST api tweaks

- Moved bookmark caching logic into service layer
- Removed returning "null" when writing to the response directly
- Finish renaming to subsonic rest controller

Signed-off-by: Andrew DeMaria <lostonamountain@gmail.com>
master
Andrew DeMaria 7 years ago
parent 21ff0a1070
commit 51853e53a1
No known key found for this signature in database
GPG Key ID: 0A3F5E91F8364EDF
  1. 6
      airsonic-main/src/main/java/org/airsonic/player/controller/AvatarController.java
  2. 8
      airsonic-main/src/main/java/org/airsonic/player/controller/CoverArtController.java
  3. 7
      airsonic-main/src/main/java/org/airsonic/player/controller/DownloadController.java
  4. 11
      airsonic-main/src/main/java/org/airsonic/player/controller/HLSController.java
  5. 2
      airsonic-main/src/main/java/org/airsonic/player/controller/JAXBWriter.java
  6. 5
      airsonic-main/src/main/java/org/airsonic/player/controller/ProxyController.java
  7. 13
      airsonic-main/src/main/java/org/airsonic/player/controller/StreamController.java
  8. 69
      airsonic-main/src/main/java/org/airsonic/player/controller/SubsonicRESTController.java
  9. 4
      airsonic-main/src/main/java/org/airsonic/player/filter/RESTFilter.java
  10. 26
      airsonic-main/src/main/java/org/airsonic/player/security/RESTRequestParameterProcessingFilter.java
  11. 40
      airsonic-main/src/main/java/org/airsonic/player/service/BookmarkService.java

@ -28,7 +28,6 @@ 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.mvc.LastModified; import org.springframework.web.servlet.mvc.LastModified;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -60,17 +59,16 @@ public class AvatarController implements LastModified {
} }
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
Avatar avatar = getAvatar(request); Avatar avatar = getAvatar(request);
if (avatar == null) { if (avatar == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND); response.sendError(HttpServletResponse.SC_NOT_FOUND);
return null; return;
} }
response.setContentType(avatar.getMimeType()); response.setContentType(avatar.getMimeType());
response.getOutputStream().write(avatar.getData()); response.getOutputStream().write(avatar.getData());
return null;
} }
private Avatar getAvatar(HttpServletRequest request) { private Avatar getAvatar(HttpServletRequest request) {

@ -35,7 +35,6 @@ 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.mvc.LastModified; import org.springframework.web.servlet.mvc.LastModified;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -98,7 +97,7 @@ public class CoverArtController implements LastModified {
} }
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
CoverArtRequest coverArtRequest = createCoverArtRequest(request); CoverArtRequest coverArtRequest = createCoverArtRequest(request);
// LOG.info("handleRequest - " + coverArtRequest); // LOG.info("handleRequest - " + coverArtRequest);
@ -107,14 +106,14 @@ public class CoverArtController implements LastModified {
// Send fallback image if no ID is given. (No need to cache it, since it will be cached in browser.) // Send fallback image if no ID is given. (No need to cache it, since it will be cached in browser.)
if (coverArtRequest == null) { if (coverArtRequest == null) {
sendFallback(size, response); sendFallback(size, response);
return null; return;
} }
// Optimize if no scaling is required. // Optimize if no scaling is required.
if (size == null && coverArtRequest.getCoverArt() != null) { if (size == null && coverArtRequest.getCoverArt() != null) {
// LOG.info("sendUnscaled - " + coverArtRequest); // LOG.info("sendUnscaled - " + coverArtRequest);
sendUnscaled(coverArtRequest, response); sendUnscaled(coverArtRequest, response);
return null; return;
} }
// Send cached image, creating it if necessary. // Send cached image, creating it if necessary.
@ -128,7 +127,6 @@ public class CoverArtController implements LastModified {
sendFallback(size, response); sendFallback(size, response);
} }
return null;
} }
private CoverArtRequest createCoverArtRequest(HttpServletRequest request) { private CoverArtRequest createCoverArtRequest(HttpServletRequest request) {

@ -35,7 +35,6 @@ import org.springframework.web.bind.ServletRequestBindingException;
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.mvc.LastModified; import org.springframework.web.servlet.mvc.LastModified;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -89,7 +88,7 @@ public class DownloadController implements LastModified {
} }
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
User user = securityService.getCurrentUser(request); User user = securityService.getCurrentUser(request);
TransferStatus status = null; TransferStatus status = null;
@ -118,7 +117,7 @@ public class DownloadController implements LastModified {
if (!securityService.isFolderAccessAllowed(mediaFile, user.getUsername())) { if (!securityService.isFolderAccessAllowed(mediaFile, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + mediaFile.getId() + " is forbidden for user " + user.getUsername()); "Access to file " + mediaFile.getId() + " is forbidden for user " + user.getUsername());
return null; return;
} }
if (mediaFile.isFile()) { if (mediaFile.isFile()) {
@ -148,8 +147,6 @@ public class DownloadController implements LastModified {
securityService.updateUserByteCounts(user, 0L, status.getBytesTransfered(), 0L); securityService.updateUserByteCounts(user, 0L, status.getBytesTransfered(), 0L);
} }
} }
return null;
} }
private MediaFile getMediaFile(HttpServletRequest request) throws ServletRequestBindingException { private MediaFile getMediaFile(HttpServletRequest request) throws ServletRequestBindingException {

@ -33,7 +33,6 @@ 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.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -68,7 +67,7 @@ public class HLSController {
private JWTSecurityService jwtSecurityService; private JWTSecurityService jwtSecurityService;
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setHeader("Access-Control-Allow-Origin", "*"); response.setHeader("Access-Control-Allow-Origin", "*");
@ -79,19 +78,19 @@ public class HLSController {
if (mediaFile == null) { if (mediaFile == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Media file not found: " + id); response.sendError(HttpServletResponse.SC_NOT_FOUND, "Media file not found: " + id);
return null; return;
} }
if (username != null && !securityService.isFolderAccessAllowed(mediaFile, username)) { if (username != null && !securityService.isFolderAccessAllowed(mediaFile, username)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + mediaFile.getId() + " is forbidden for user " + username); "Access to file " + mediaFile.getId() + " is forbidden for user " + username);
return null; return;
} }
Integer duration = mediaFile.getDurationSeconds(); Integer duration = mediaFile.getDurationSeconds();
if (duration == null || duration == 0) { if (duration == null || duration == 0) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown duration for media file: " + id); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unknown duration for media file: " + id);
return null; return;
} }
response.setContentType("application/vnd.apple.mpegurl"); response.setContentType("application/vnd.apple.mpegurl");
@ -104,7 +103,7 @@ public class HLSController {
generateNormalPlaylist(request, id, player, bitRates.size() == 1 ? bitRates.get(0) : null, duration, writer); generateNormalPlaylist(request, id, player, bitRates.size() == 1 ? bitRates.get(0) : null, duration, writer);
} }
return null; return;
} }
private List<Pair<Integer, Dimension>> parseBitRates(HttpServletRequest request) throws IllegalArgumentException { private List<Pair<Integer, Dimension>> parseBitRates(HttpServletRequest request) throws IllegalArgumentException {

@ -157,7 +157,7 @@ public class JAXBWriter {
} }
public void writeErrorResponse(HttpServletRequest request, HttpServletResponse response, public void writeErrorResponse(HttpServletRequest request, HttpServletResponse response,
RESTController.ErrorCode code, String message) throws Exception { SubsonicRESTController.ErrorCode code, String message) throws Exception {
Response res = createResponse(false); Response res = createResponse(false);
Error error = new Error(); Error error = new Error();
res.setError(error); res.setError(error);

@ -20,7 +20,6 @@
package org.airsonic.player.controller; package org.airsonic.player.controller;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
@ -37,6 +36,8 @@ import javax.servlet.http.HttpServletResponse;
import java.io.InputStream; import java.io.InputStream;
import static org.springframework.http.HttpStatus.*;
/** /**
* A proxy for external HTTP requests. * A proxy for external HTTP requests.
* *
@ -61,7 +62,7 @@ public class ProxyController {
try (CloseableHttpClient client = HttpClients.createDefault()) { try (CloseableHttpClient client = HttpClients.createDefault()) {
try (CloseableHttpResponse resp = client.execute(method)) { try (CloseableHttpResponse resp = client.execute(method)) {
int statusCode = resp.getStatusLine().getStatusCode(); int statusCode = resp.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) { if (statusCode != OK.value()) {
response.sendError(statusCode); response.sendError(statusCode);
} else { } else {
in = resp.getEntity().getContent(); in = resp.getEntity().getContent();

@ -40,7 +40,6 @@ import org.springframework.web.bind.ServletRequestBindingException;
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 javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -84,7 +83,7 @@ public class StreamController {
private SearchService searchService; private SearchService searchService;
@RequestMapping(method = RequestMethod.GET) @RequestMapping(method = RequestMethod.GET)
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
TransferStatus status = null; TransferStatus status = null;
PlayQueueInputStream in = null; PlayQueueInputStream in = null;
@ -96,7 +95,7 @@ public class StreamController {
if (!(authentication instanceof JWTAuthenticationToken) && !user.isStreamRole()) { if (!(authentication instanceof JWTAuthenticationToken) && !user.isStreamRole()) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Streaming is forbidden for user " + user.getUsername()); response.sendError(HttpServletResponse.SC_FORBIDDEN, "Streaming is forbidden for user " + user.getUsername());
return null; return;
} }
// If "playlist" request parameter is set, this is a Podcast request. In that case, create a separate // If "playlist" request parameter is set, this is a Podcast request. In that case, create a separate
@ -136,7 +135,7 @@ public class StreamController {
if (!(authentication instanceof JWTAuthenticationToken) && !securityService.isFolderAccessAllowed(file, user.getUsername())) { if (!(authentication instanceof JWTAuthenticationToken) && !securityService.isFolderAccessAllowed(file, user.getUsername())) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, response.sendError(HttpServletResponse.SC_FORBIDDEN,
"Access to file " + file.getId() + " is forbidden for user " + user.getUsername()); "Access to file " + file.getId() + " is forbidden for user " + user.getUsername());
return null; return;
} }
// Update the index of the currently playing media file. At // Update the index of the currently playing media file. At
@ -189,7 +188,7 @@ public class StreamController {
} }
if (request.getMethod().equals("HEAD")) { if (request.getMethod().equals("HEAD")) {
return null; return;
} }
// Terminate any other streams to this player. // Terminate any other streams to this player.
@ -226,7 +225,7 @@ public class StreamController {
// Check if stream has been terminated. // Check if stream has been terminated.
if (status.terminated()) { if (status.terminated()) {
return null; return;
} }
if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) { if (player.getPlayQueue().getStatus() == PlayQueue.Status.STOPPED) {
@ -257,7 +256,7 @@ public class StreamController {
} }
IOUtils.closeQuietly(in); IOUtils.closeQuietly(in);
} }
return null; return;
} }
private void setContentDuration(HttpServletResponse response, MediaFile file) { private void setContentDuration(HttpServletResponse response, MediaFile file) {

@ -23,8 +23,12 @@ import org.airsonic.player.ajax.LyricsInfo;
import org.airsonic.player.ajax.LyricsService; import org.airsonic.player.ajax.LyricsService;
import org.airsonic.player.ajax.PlayQueueService; import org.airsonic.player.ajax.PlayQueueService;
import org.airsonic.player.command.UserSettingsCommand; import org.airsonic.player.command.UserSettingsCommand;
import org.airsonic.player.dao.*; 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.*;
import org.airsonic.player.domain.Bookmark;
import org.airsonic.player.service.*; import org.airsonic.player.service.*;
import org.airsonic.player.util.Pair; import org.airsonic.player.util.Pair;
import org.airsonic.player.util.StringUtil; import org.airsonic.player.util.StringUtil;
@ -43,7 +47,6 @@ import org.springframework.web.servlet.ModelAndView;
import org.subsonic.restapi.*; import org.subsonic.restapi.*;
import org.subsonic.restapi.PodcastStatus; import org.subsonic.restapi.PodcastStatus;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -65,9 +68,9 @@ import static org.springframework.web.bind.ServletRequestUtils.*;
*/ */
@Controller @Controller
@RequestMapping(value = "/rest", method = {RequestMethod.GET, RequestMethod.POST}) @RequestMapping(value = "/rest", method = {RequestMethod.GET, RequestMethod.POST})
public class RESTController { public class SubsonicRESTController {
private static final Logger LOG = LoggerFactory.getLogger(RESTController.class); private static final Logger LOG = LoggerFactory.getLogger(SubsonicRESTController.class);
@Autowired @Autowired
private SettingsService settingsService; private SettingsService settingsService;
@ -124,7 +127,7 @@ public class RESTController {
@Autowired @Autowired
private AlbumDao albumDao; private AlbumDao albumDao;
@Autowired @Autowired
private BookmarkDao bookmarkDao; private BookmarkService bookmarkService;
@Autowired @Autowired
private PlayQueueDao playQueueDao; private PlayQueueDao playQueueDao;
@Autowired @Autowired
@ -136,18 +139,6 @@ public class RESTController {
private static final String NOT_YET_IMPLEMENTED = "Not yet implemented"; private static final String NOT_YET_IMPLEMENTED = "Not yet implemented";
private static final String NO_LONGER_SUPPORTED = "No longer supported"; private static final String NO_LONGER_SUPPORTED = "No longer supported";
@PostConstruct
public void init() {
refreshBookmarkCache();
}
private void refreshBookmarkCache() {
bookmarkCache.clear();
for (org.airsonic.player.domain.Bookmark bookmark : bookmarkDao.getBookmarks()) {
bookmarkCache.put(BookmarkKey.forBookmark(bookmark), bookmark);
}
}
@RequestMapping(value = "/ping") @RequestMapping(value = "/ping")
public void ping(HttpServletRequest request, HttpServletResponse response) throws Exception { public void ping(HttpServletRequest request, HttpServletResponse response) throws Exception {
Response res = createResponse(); Response res = createResponse();
@ -1337,12 +1328,12 @@ public class RESTController {
} }
@RequestMapping(value = "/download") @RequestMapping(value = "/download")
public ModelAndView download(HttpServletRequest request, HttpServletResponse response) throws Exception { public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request); org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isDownloadRole()) { if (!user.isDownloadRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to download files."); error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to download files.");
return null; return;
} }
long ifModifiedSince = request.getDateHeader("If-Modified-Since"); long ifModifiedSince = request.getDateHeader("If-Modified-Since");
@ -1350,49 +1341,47 @@ public class RESTController {
if (ifModifiedSince != -1 && lastModified != -1 && lastModified <= ifModifiedSince) { if (ifModifiedSince != -1 && lastModified != -1 && lastModified <= ifModifiedSince) {
response.sendError(HttpServletResponse.SC_NOT_MODIFIED); response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
return null; return;
} }
if (lastModified != -1) { if (lastModified != -1) {
response.setDateHeader("Last-Modified", lastModified); response.setDateHeader("Last-Modified", lastModified);
} }
return downloadController.handleRequest(request, response); downloadController.handleRequest(request, response);
} }
@RequestMapping(value = "/stream") @RequestMapping(value = "/stream")
public ModelAndView stream(HttpServletRequest request, HttpServletResponse response) throws Exception { public void stream(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request); org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isStreamRole()) { if (!user.isStreamRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to play files."); error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to play files.");
return null; return;
} }
streamController.handleRequest(request, response); streamController.handleRequest(request, response);
return null;
} }
@RequestMapping(value = "/hls") @RequestMapping(value = "/hls")
public ModelAndView hls(HttpServletRequest request, HttpServletResponse response) throws Exception { public void hls(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
org.airsonic.player.domain.User user = securityService.getCurrentUser(request); org.airsonic.player.domain.User user = securityService.getCurrentUser(request);
if (!user.isStreamRole()) { if (!user.isStreamRole()) {
error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to play files."); error(request, response, ErrorCode.NOT_AUTHORIZED, user.getUsername() + " is not authorized to play files.");
return null; return;
} }
int id = getRequiredIntParameter(request, "id"); int id = getRequiredIntParameter(request, "id");
MediaFile video = mediaFileDao.getMediaFile(id); MediaFile video = mediaFileDao.getMediaFile(id);
if (video == null || video.isDirectory()) { if (video == null || video.isDirectory()) {
error(request, response, ErrorCode.NOT_FOUND, "Video not found."); error(request, response, ErrorCode.NOT_FOUND, "Video not found.");
return null; return;
} }
if (!securityService.isFolderAccessAllowed(video, user.getUsername())) { if (!securityService.isFolderAccessAllowed(video, user.getUsername())) {
error(request, response, ErrorCode.NOT_AUTHORIZED, "Access denied"); error(request, response, ErrorCode.NOT_AUTHORIZED, "Access denied");
return null; return;
} }
hlsController.handleRequest(request, response); hlsController.handleRequest(request, response);
return null;
} }
@RequestMapping(value = "/scrobble") @RequestMapping(value = "/scrobble")
@ -1702,7 +1691,7 @@ public class RESTController {
String username = securityService.getCurrentUsername(request); String username = securityService.getCurrentUsername(request);
Bookmarks result = new Bookmarks(); Bookmarks result = new Bookmarks();
for (org.airsonic.player.domain.Bookmark bookmark : bookmarkDao.getBookmarks(username)) { for (Bookmark bookmark : bookmarkService.getBookmarks(username)) {
org.subsonic.restapi.Bookmark b = new org.subsonic.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());
@ -1729,9 +1718,8 @@ public class RESTController {
String comment = request.getParameter("comment"); String comment = request.getParameter("comment");
Date now = new Date(); Date now = new Date();
org.airsonic.player.domain.Bookmark bookmark = new org.airsonic.player.domain.Bookmark(0, mediaFileId, position, username, comment, now, now); Bookmark bookmark = new Bookmark(0, mediaFileId, position, username, comment, now, now);
bookmarkDao.createOrUpdateBookmark(bookmark); bookmarkService.createOrUpdateBookmark(bookmark);
refreshBookmarkCache();
writeEmptyResponse(request, response); writeEmptyResponse(request, response);
} }
@ -1741,8 +1729,7 @@ public class RESTController {
String username = securityService.getCurrentUsername(request); String username = securityService.getCurrentUsername(request);
int mediaFileId = getRequiredIntParameter(request, "id"); int mediaFileId = getRequiredIntParameter(request, "id");
bookmarkDao.deleteBookmark(username, mediaFileId); bookmarkService.deleteBookmark(username, mediaFileId);
refreshBookmarkCache();
writeEmptyResponse(request, response); writeEmptyResponse(request, response);
} }
@ -1954,15 +1941,15 @@ public class RESTController {
} }
@RequestMapping(value = "/getCoverArt") @RequestMapping(value = "/getCoverArt")
public ModelAndView getCoverArt(HttpServletRequest request, HttpServletResponse response) throws Exception { public void getCoverArt(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
return coverArtController.handleRequest(request, response); coverArtController.handleRequest(request, response);
} }
@RequestMapping(value = "/getAvatar") @RequestMapping(value = "/getAvatar")
public ModelAndView getAvatar(HttpServletRequest request, HttpServletResponse response) throws Exception { public void getAvatar(HttpServletRequest request, HttpServletResponse response) throws Exception {
request = wrapRequest(request); request = wrapRequest(request);
return avatarController.handleRequest(request, response); avatarController.handleRequest(request, response);
} }
@RequestMapping(value = "/changePassword") @RequestMapping(value = "/changePassword")
@ -2237,7 +2224,7 @@ public class RESTController {
MediaFile mediaFile = this.mediaFileService.getMediaFile(id); MediaFile mediaFile = this.mediaFileService.getMediaFile(id);
if (mediaFile == null) { if (mediaFile == null) {
error(request, response, RESTController.ErrorCode.NOT_FOUND, "Media file not found."); error(request, response, SubsonicRESTController.ErrorCode.NOT_FOUND, "Media file not found.");
return; return;
} }
AlbumNotes albumNotes = this.lastFmService.getAlbumNotes(mediaFile); AlbumNotes albumNotes = this.lastFmService.getAlbumNotes(mediaFile);
@ -2256,7 +2243,7 @@ public class RESTController {
Album album = this.albumDao.getAlbum(id); Album album = this.albumDao.getAlbum(id);
if (album == null) { if (album == null) {
error(request, response, RESTController.ErrorCode.NOT_FOUND, "Album not found."); error(request, response, SubsonicRESTController.ErrorCode.NOT_FOUND, "Album not found.");
return; return;
} }
AlbumNotes albumNotes = this.lastFmService.getAlbumNotes(album); AlbumNotes albumNotes = this.lastFmService.getAlbumNotes(album);

@ -20,7 +20,7 @@
package org.airsonic.player.filter; package org.airsonic.player.filter;
import org.airsonic.player.controller.JAXBWriter; import org.airsonic.player.controller.JAXBWriter;
import org.airsonic.player.controller.RESTController; import org.airsonic.player.controller.SubsonicRESTController;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.ServletRequestBindingException;
@ -61,7 +61,7 @@ public class RESTFilter implements Filter {
x = x.getCause(); x = x.getCause();
} }
RESTController.ErrorCode code = (x instanceof ServletRequestBindingException) ? RESTController.ErrorCode.MISSING_PARAMETER : RESTController.ErrorCode.GENERIC; SubsonicRESTController.ErrorCode code = (x instanceof ServletRequestBindingException) ? SubsonicRESTController.ErrorCode.MISSING_PARAMETER : SubsonicRESTController.ErrorCode.GENERIC;
String msg = getErrorMessage(x); String msg = getErrorMessage(x);
LOG.warn("Error in REST API: " + msg, x); LOG.warn("Error in REST API: " + msg, x);

@ -20,7 +20,7 @@
package org.airsonic.player.security; package org.airsonic.player.security;
import org.airsonic.player.controller.JAXBWriter; import org.airsonic.player.controller.JAXBWriter;
import org.airsonic.player.controller.RESTController; import org.airsonic.player.controller.SubsonicRESTController;
import org.airsonic.player.domain.User; import org.airsonic.player.domain.User;
import org.airsonic.player.domain.Version; import org.airsonic.player.domain.Version;
import org.airsonic.player.service.SecurityService; import org.airsonic.player.service.SecurityService;
@ -100,7 +100,7 @@ public class RESTRequestParameterProcessingFilter implements Filter {
String version = StringUtils.trimToNull(httpRequest.getParameter("v")); String version = StringUtils.trimToNull(httpRequest.getParameter("v"));
String client = StringUtils.trimToNull(httpRequest.getParameter("c")); String client = StringUtils.trimToNull(httpRequest.getParameter("c"));
RESTController.ErrorCode errorCode = null; SubsonicRESTController.ErrorCode errorCode = null;
// The username and credentials parameters are not required if the user // The username and credentials parameters are not required if the user
// was previously authenticated, for example using Basic Auth. // was previously authenticated, for example using Basic Auth.
@ -108,7 +108,7 @@ public class RESTRequestParameterProcessingFilter implements Filter {
Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication(); Authentication previousAuth = SecurityContextHolder.getContext().getAuthentication();
boolean missingCredentials = previousAuth == null && (username == null || !passwordOrTokenPresent); boolean missingCredentials = previousAuth == null && (username == null || !passwordOrTokenPresent);
if (missingCredentials || version == null || client == null) { if (missingCredentials || version == null || client == null) {
errorCode = RESTController.ErrorCode.MISSING_PARAMETER; errorCode = SubsonicRESTController.ErrorCode.MISSING_PARAMETER;
} }
if (errorCode == null) { if (errorCode == null) {
@ -127,21 +127,21 @@ public class RESTRequestParameterProcessingFilter implements Filter {
} }
} }
private RESTController.ErrorCode checkAPIVersion(String version) { private SubsonicRESTController.ErrorCode checkAPIVersion(String version) {
Version serverVersion = new Version(jaxbWriter.getRestProtocolVersion()); Version serverVersion = new Version(jaxbWriter.getRestProtocolVersion());
Version clientVersion = new Version(version); Version clientVersion = new Version(version);
if (serverVersion.getMajor() > clientVersion.getMajor()) { if (serverVersion.getMajor() > clientVersion.getMajor()) {
return RESTController.ErrorCode.PROTOCOL_MISMATCH_CLIENT_TOO_OLD; return SubsonicRESTController.ErrorCode.PROTOCOL_MISMATCH_CLIENT_TOO_OLD;
} else if (serverVersion.getMajor() < clientVersion.getMajor()) { } else if (serverVersion.getMajor() < clientVersion.getMajor()) {
return RESTController.ErrorCode.PROTOCOL_MISMATCH_SERVER_TOO_OLD; return SubsonicRESTController.ErrorCode.PROTOCOL_MISMATCH_SERVER_TOO_OLD;
} else if (serverVersion.getMinor() < clientVersion.getMinor()) { } else if (serverVersion.getMinor() < clientVersion.getMinor()) {
return RESTController.ErrorCode.PROTOCOL_MISMATCH_SERVER_TOO_OLD; return SubsonicRESTController.ErrorCode.PROTOCOL_MISMATCH_SERVER_TOO_OLD;
} }
return null; return null;
} }
private RESTController.ErrorCode authenticate(HttpServletRequest httpRequest, String username, String password, String salt, String token, Authentication previousAuth) { private SubsonicRESTController.ErrorCode authenticate(HttpServletRequest httpRequest, String username, String password, String salt, String token, Authentication previousAuth) {
// Previously authenticated and username not overridden? // Previously authenticated and username not overridden?
if (username == null && previousAuth != null) { if (username == null && previousAuth != null) {
@ -151,11 +151,11 @@ public class RESTRequestParameterProcessingFilter implements Filter {
if (salt != null && token != null) { if (salt != null && token != null) {
User user = securityService.getUserByName(username); User user = securityService.getUserByName(username);
if (user == null) { if (user == null) {
return RESTController.ErrorCode.NOT_AUTHENTICATED; return SubsonicRESTController.ErrorCode.NOT_AUTHENTICATED;
} }
String expectedToken = DigestUtils.md5Hex(user.getPassword() + salt); String expectedToken = DigestUtils.md5Hex(user.getPassword() + salt);
if (!expectedToken.equals(token)) { if (!expectedToken.equals(token)) {
return RESTController.ErrorCode.NOT_AUTHENTICATED; return SubsonicRESTController.ErrorCode.NOT_AUTHENTICATED;
} }
password = user.getPassword(); password = user.getPassword();
@ -170,11 +170,11 @@ public class RESTRequestParameterProcessingFilter implements Filter {
return null; return null;
} catch (AuthenticationException x) { } catch (AuthenticationException x) {
eventPublisher.publishEvent(new AuthenticationFailureBadCredentialsEvent(authRequest, x)); eventPublisher.publishEvent(new AuthenticationFailureBadCredentialsEvent(authRequest, x));
return RESTController.ErrorCode.NOT_AUTHENTICATED; return SubsonicRESTController.ErrorCode.NOT_AUTHENTICATED;
} }
} }
return RESTController.ErrorCode.MISSING_PARAMETER; return SubsonicRESTController.ErrorCode.MISSING_PARAMETER;
} }
public static String decrypt(String s) { public static String decrypt(String s) {
@ -191,7 +191,7 @@ public class RESTRequestParameterProcessingFilter implements Filter {
} }
} }
private void sendErrorXml(HttpServletRequest request, HttpServletResponse response, RESTController.ErrorCode errorCode) throws IOException { private void sendErrorXml(HttpServletRequest request, HttpServletResponse response, SubsonicRESTController.ErrorCode errorCode) throws IOException {
try { try {
jaxbWriter.writeErrorResponse(request, response, errorCode, errorCode.getMessage()); jaxbWriter.writeErrorResponse(request, response, errorCode, errorCode.getMessage());
} catch (Exception e) { } catch (Exception e) {

@ -0,0 +1,40 @@
package org.airsonic.player.service;
import org.airsonic.player.dao.BookmarkDao;
import org.airsonic.player.domain.Bookmark;
import org.airsonic.player.domain.MediaFile;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
@Service
public class BookmarkService {
private final BookmarkDao dao;
@Autowired
public BookmarkService(BookmarkDao dao) {
this.dao = dao;
}
public Bookmark getBookmarkForUserAndMediaFile(String username, MediaFile mediaFile) {
return dao.getBookmarks(username)
.stream()
.filter(bookmark -> Objects.equals(mediaFile.getId(), bookmark.getMediaFileId()))
.findFirst().orElse(null);
}
public void createOrUpdateBookmark(Bookmark bookmark) {
dao.createOrUpdateBookmark(bookmark);
}
public void deleteBookmark(String username, int mediaFileId) {
dao.deleteBookmark(username, mediaFileId);
}
public List<Bookmark> getBookmarks(String username) {
return dao.getBookmarks(username);
}
}
Loading…
Cancel
Save