/*
This file is part of Libresonic.
Libresonic 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.
Libresonic 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 Libresonic. If not, see <http://www.gnu.org/licenses/>.
Copyright 2016 (C) Libresonic Authors
Based upon Subsonic, Copyright 2009 (C) Sindre Mehus
*/
package org.libresonic.player.controller;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.libresonic.player.Logger;
import org.libresonic.player.domain.TransferStatus;
import org.libresonic.player.domain.User;
import org.libresonic.player.service.PlayerService;
import org.libresonic.player.service.SecurityService;
import org.libresonic.player.service.SettingsService;
import org.libresonic.player.service.StatusService;
import org.libresonic.player.upload.MonitoredDiskFileItemFactory;
import org.libresonic.player.upload.UploadListener;
import org.libresonic.player.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
/**
* Controller which receives uploaded files.
*
* @author Sindre Mehus
*/
@org.springframework.stereotype.Controller
@RequestMapping("/upload")
public class UploadController {
private static final Logger LOG = Logger.getLogger(UploadController.class);
@Autowired
private SecurityService securityService;
@Autowired
private PlayerService playerService;
@Autowired
private StatusService statusService;
@Autowired
private SettingsService settingsService;
public static final String UPLOAD_STATUS = "uploadStatus";
@RequestMapping(method = { RequestMethod.POST })
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
Map<String, Object> map = new HashMap<>();
List<File> uploadedFiles = new ArrayList<>();
List<File> unzippedFiles = new ArrayList<>();
TransferStatus status = null;
try {
status = statusService.createUploadStatus(playerService.getPlayer(request, response, false, false));
status.setBytesTotal(request.getContentLength());
request.getSession().setAttribute(UPLOAD_STATUS, status);
// Check that we have a file upload request
if (!ServletFileUpload.isMultipartContent(request)) {
throw new Exception("Illegal request.");
}
File dir = null;
boolean unzip = false;
UploadListener listener = new UploadListenerImpl(status);
FileItemFactory factory = new MonitoredDiskFileItemFactory(listener);
ServletFileUpload upload = new ServletFileUpload(factory);
List<?> items = upload.parseRequest(request);
// First, look for "dir" and "unzip" parameters.
for (Object o : items) {
FileItem item = (FileItem) o;
if (item.isFormField() && "dir".equals(item.getFieldName())) {
dir = new File(item.getString());
} else if (item.isFormField() && "unzip".equals(item.getFieldName())) {
unzip = true;
}
}
if (dir == null) {
throw new Exception("Missing 'dir' parameter.");
}
// Look for file items.
for (Object o : items) {
FileItem item = (FileItem) o;
if (!item.isFormField()) {
String fileName = item.getName();
if (fileName.trim().length() > 0) {
File targetFile = new File(dir, new File(fileName).getName());
if (!securityService.isUploadAllowed(targetFile)) {
throw new Exception("Permission denied: " + StringUtil.toHtml(targetFile.getPath()));
}
if (!dir.exists()) {
dir.mkdirs();
}
item.write(targetFile);
uploadedFiles.add(targetFile);
LOG.info("Uploaded " + targetFile);
if (unzip && targetFile.getName().toLowerCase().endsWith(".zip")) {
unzip(targetFile, unzippedFiles);
}
}
}
}
} catch (Exception x) {
LOG.warn("Uploading failed.", x);
map.put("exception", x);
} finally {
if (status != null) {
statusService.removeUploadStatus(status);
request.getSession().removeAttribute(UPLOAD_STATUS);
User user = securityService.getCurrentUser(request);
securityService.updateUserByteCounts(user, 0L, 0L, status.getBytesTransfered());
}
}
map.put("uploadedFiles", uploadedFiles);
map.put("unzippedFiles", unzippedFiles);
return new ModelAndView("upload","model",map);
}
private void unzip(File file, List<File> unzippedFiles) throws Exception {
LOG.info("Unzipping " + file);
ZipFile zipFile = new ZipFile(file);
try {
Enumeration<?> entries = zipFile.getEntries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
File entryFile = new File(file.getParentFile(), entry.getName());
if (!entry.isDirectory()) {
if (!securityService.isUploadAllowed(entryFile)) {
throw new Exception("Permission denied: " + StringUtil.toHtml(entryFile.getPath()));
}
entryFile.getParentFile().mkdirs();
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = zipFile.getInputStream(entry);
outputStream = new FileOutputStream(entryFile);
byte[] buf = new byte[8192];
while (true) {
int n = inputStream.read(buf);
if (n == -1) {
break;
}
outputStream.write(buf, 0, n);
}
LOG.info("Unzipped " + entryFile);
unzippedFiles.add(entryFile);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
}
zipFile.close();
file.delete();
} finally {
zipFile.close();
}
}
/**
* Receives callbacks as the file upload progresses.
*/
private class UploadListenerImpl implements UploadListener {
private TransferStatus status;
private long start;
private UploadListenerImpl(TransferStatus status) {
this.status = status;
start = System.currentTimeMillis();
}
public void start(String fileName) {
status.setFile(new File(fileName));
}
public void bytesRead(long bytesRead) {
// Throttle bitrate.
long byteCount = status.getBytesTransfered() + bytesRead;
long bitCount = byteCount * 8L;
float elapsedMillis = Math.max(1, System.currentTimeMillis() - start);
float elapsedSeconds = elapsedMillis / 1000.0F;
long maxBitsPerSecond = getBitrateLimit();
status.setBytesTransfered(byteCount);
if (maxBitsPerSecond > 0) {
float sleepMillis = 1000.0F * (bitCount / maxBitsPerSecond - elapsedSeconds);
if (sleepMillis > 0) {
try {
Thread.sleep((long) sleepMillis);
} catch (InterruptedException x) {
LOG.warn("Failed to sleep.", x);
}
}
}
}
private long getBitrateLimit() {
return 1024L * settingsService.getUploadBitrateLimit() / Math.max(1, statusService.getAllUploadStatuses().size());
}
}
}