/*
* Computoser is a music-composition algorithm and a website to present the results
* Copyright (C) 2012-2014 Bozhidar Bozhanov
*
* Computoser is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Computoser 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Computoser. If not, see <http://www.gnu.org/licenses/>.
*/
package com.music.web;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.feed.AbstractRssFeedView;
import com.music.model.persistent.Piece;
import com.music.model.prefs.UserPreferences;
import com.music.service.PieceService;
import com.music.service.PurchaseService;
import com.music.util.SecurityUtils;
import com.music.util.music.Chance;
import com.sun.syndication.feed.rss.Channel;
import com.sun.syndication.feed.rss.Item;
@Controller
public class MusicController {
private static final Logger logger = LoggerFactory.getLogger(MusicController.class);
@Inject
private PieceService pieceService;
@Inject
private PurchaseService purchaseService;
@Inject
private UserContext userContext;
@Value("${base.url}")
private String baseUrl;
@Value("${hmac.key}")
private String hmacKey;
@RequestMapping("/music/get")
@ResponseBody
public long getMusic(UserPreferences preferences, BindingResult bindingResult) {
// report and ignore errors in binding the UserPreferences object
if (bindingResult.hasErrors()) {
logger.error("Binding errors: " + bindingResult.getAllErrors());
}
if (Chance.test(7)){
return pieceService.getRandomTopPieceId();
} else {
return pieceService.getNextPieceId(preferences);
}
}
@RequestMapping("/music/get/{id}")
public void getPiece(@PathVariable long id,
@RequestParam(required = false, defaultValue = "false") boolean mobileApp,
@RequestParam(required = false, defaultValue = "false") boolean download, OutputStream os,
HttpServletRequest request, HttpServletResponse response) throws IOException {
Long userId = null;
if (userContext.getUser() != null) {
userId = userContext.getUser().getId();
}
if (download) {
pieceService.incrementDownloads(id, false);
} else {
pieceService.storePlay(id, userId, request.getRemoteAddr(), mobileApp);
}
response.setHeader("Content-Disposition", "attachment; filename=\"" + id + ".mp3\"");
setNotCacheable(response);
response.setContentType("audio/mp3");
InputStream is = pieceService.getPieceFile(id);
IOUtils.copy(is, os);
}
@RequestMapping("/music/getMidi/{id}")
public void getPieceMidi(@PathVariable long id, OutputStream os,
HttpServletRequest request, HttpServletResponse response) throws IOException {
pieceService.incrementDownloads(id, true);
response.setHeader("Content-Disposition", "attachment; filename=\"" + id + ".midi\"");
setNotCacheable(response);
response.setContentType("audio/midi");
InputStream is = pieceService.getPieceMidiFile(id);
IOUtils.copy(is, os);
}
@RequestMapping("/music/getXml/{id}")
public void getPieceMusicXml(@PathVariable long id, OutputStream os,
HttpServletRequest request, HttpServletResponse response) {
response.setHeader("Content-Disposition", "attachment; filename=\"" + id + ".xml\"");
response.setHeader("X-Robot-Tag", "noindex");
setNotCacheable(response);
response.setContentType("text/xml");
try {
InputStream is = pieceService.getPieceMusicXml(id);
IOUtils.copy(is, os);
} catch (IOException ex) {
logger.warn("Problem getting musicXML", ex.getMessage());
}
}
@RequestMapping("/music/getTitle/{id}")
@ResponseBody
public String getPieceTitle(@PathVariable long id) {
return pieceService.getPiece(id).getTitle();
}
@RequestMapping("/track/{id}")
public String getTrack(@PathVariable long id, Model model) {
model.addAttribute("pieceId", id);
Piece piece = pieceService.getPiece(id);
if (piece != null) {
model.addAttribute("title", piece.getTitle());
model.addAttribute("likes", piece.getLikes());
return "track";
} else {
logger.warn("No track with id=" + id + " found");
return "redirect:/";
}
}
@RequestMapping("/mytracks")
public String getMyTracks(@RequestParam(required=false, defaultValue="0") int page, Model model) {
if (userContext.getUser() != null) {
List<Piece> pieces = pieceService.getUserPieces(userContext.getUser().getId(), page);
model.addAttribute("title", "Tracks that you have liked");
model.addAttribute("currentPage", "mytracks");
model.addAttribute("pieces", pieces);
return "tracks";
} else {
return "redirect:/";
}
}
@RequestMapping("/toptracks")
public String getTopTracks(@RequestParam(required=false, defaultValue="0") int page, Model model) {
List<Piece> pieces = pieceService.getTopPieces(page);
model.addAttribute("title", "Most liked tracks");
model.addAttribute("currentPage", "toptracks");
model.addAttribute("pieces", pieces);
return "tracks";
}
@RequestMapping("/toprecent")
public String getTopRecentTracks(@RequestParam(required=false, defaultValue="0") int page, Model model) {
List<Piece> pieces = new ArrayList<>();
// this is outside the session, so it doesn't affect the db. Change when switching to DTOs
for (Entry<Piece, Integer> entry : pieceService.getTopRecentPieces(page).entrySet()) {
entry.getKey().setLikes(entry.getValue());
pieces.add(entry.getKey());
}
model.addAttribute("title", "Most liked tracks in the past week");
model.addAttribute("currentPage", "toprecent");
model.addAttribute("pieces", pieces);
return "tracks";
}
private void setNotCacheable(HttpServletResponse response) {
response.setHeader("Pragma", "no-cache");
// HTTP 1.0 header
response.setDateHeader("Expires", 1L);
// HTTP 1.1 header: "no-cache" is the standard value,
// "no-store" is necessary to prevent caching on FireFox.
response.setHeader("Cache-Control", "no-cache");
response.addHeader("Cache-Control", "no-store");
}
@RequestMapping("/music/evaluate/{id}")
@ResponseBody
public void evaluate(@PathVariable long id, @RequestParam boolean positive, HttpServletRequest request) throws IOException {
Long userId = null;
if (userContext.getUser() != null) {
userId = userContext.getUser().getId();
}
pieceService.evaluate(id, userId, positive, request.getRemoteAddr());
}
@RequestMapping("/")
public String index() {
return "index";
}
@ModelAttribute("userContext")
public UserContext getUserContext() {
return userContext;
}
@RequestMapping("/rss")
public View userRss(Model model) {
List<Piece> pieces = pieceService.getRssFeed();
model.addAttribute("pieces", pieces);
PieceRssView view = new PieceRssView();
view.setContentType("application/rss+xml;charset=UTF-8");
return view;
}
@RequestMapping("/purchase/download/{id}/{hmac}")
public void downloadPurchase(@PathVariable String id, @PathVariable String hmac, HttpServletResponse response) throws IOException {
if (SecurityUtils.hmac(StringUtils.leftPad(id, 10, '0'), hmacKey).equals(hmac)) {
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "Content-Disposition: attachment; filename=computoser-tracks-" + id + ".zip;");
purchaseService.download(Long.parseLong(id), response.getOutputStream());
}
}
@RequestMapping("/search")
public String search(UserPreferences preferences, Model model) {
List<Piece> pieces = pieceService.search(preferences);
model.addAttribute("searchEnabled", Boolean.TRUE);
model.addAttribute("pieces", pieces);
model.addAttribute("preferences", preferences);
model.addAttribute("title", "Search");
model.addAttribute("currentPage", "search");
return "tracks";
}
@RequestMapping("/radio")
public void radio(OutputStream os, HttpServletRequest request, HttpServletResponse response) {
}
private class PieceRssView extends AbstractRssFeedView {
@SuppressWarnings("unchecked")
@Override
protected List<Item> buildFeedItems(Map<String, Object> model, HttpServletRequest request,
HttpServletResponse response) throws Exception {
List<Piece> pieces = (List<Piece>) model.get("pieces");
List<Item> items = new ArrayList<Item>(pieces.size());
for (Piece piece : pieces) {
Item item = new Item();
item.setAuthor("Computoser");
item.setPubDate(piece.getGenerationTime().toDate());
item.setLink(baseUrl + "/track/" + piece.getId());
item.setTitle(piece.getTitle());
items.add(item);
}
return items;
}
@Override
protected Channel newFeed() {
Channel channel = super.newFeed();
channel.setTitle("Computoser - computser-generaces music");
channel.setDescription(baseUrl);
channel.setLink(baseUrl + "/rss");
channel.setEncoding("utf-8");
return channel;
}
}
}