package net.flibusta.servlet;
import net.flibusta.persistence.dao.BatchDao;
import net.flibusta.persistence.dao.BookDao;
import net.flibusta.persistence.dao.UrlDao;
import net.flibusta.persistence.dao.UrlInfo;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@Controller
public class MassConvertController {
public static final String PARAM_URL = "url";
public static final String PARAM_URL_TEMPLATE = "urltemplate";
public static final String PARAM_OUT_FORMAT = "out";
public static final String PARAM_SOURCE_FORMAT = "src";
public static final String DEFAULT_OUT_FORMAT = "mobi";
public static final String DEFAULT_URL_TEMPLATE = "http://flibusta.net/b/{0}/download";
private String staticRedirectUrlPrefix = null;
private Boolean useXAccelRerirect = false;
private int convertersPoolSize = 10;
ExecutorService converterExecutor = Executors.newFixedThreadPool(convertersPoolSize);
@Autowired
SingleUrlConverter singleConverterController;
@Autowired
BatchDao batchDao;
@Autowired
UrlDao urlDao;
@Autowired
BookDao bookDao;
@RequestMapping(value = "/batch", method = RequestMethod.GET)
public void convert(@RequestParam(PARAM_URL) String sourceUrlParams,
@RequestParam(value = PARAM_URL_TEMPLATE, required = false, defaultValue = DEFAULT_URL_TEMPLATE) String sourceUrlTemplate,
@RequestParam(value = PARAM_OUT_FORMAT, required = false, defaultValue = DEFAULT_OUT_FORMAT) String outputFormat,
HttpServletResponse response) throws Exception {
if (sourceUrlParams == null || sourceUrlParams.length() == 0) {
throw new Exception("Required parameter missing: " + PARAM_URL);
}
String[] sourceUrls = getSourceUrls(sourceUrlTemplate, sourceUrlParams);
Arrays.sort(sourceUrls);
String batchSignature = calculateBatchSignature(sourceUrls);
String batchFilePath = batchDao.findBatchPath(batchSignature, outputFormat);
if (batchFilePath != null) { // already packed
sendRedirect(response, batchFilePath);
return;
}
List<Callable<Object>> tasks = new ArrayList<Callable<Object>>(sourceUrls.length);
for (final String sourceUrl : sourceUrls) {
tasks.add(createSingleConversionTask(sourceUrl, outputFormat));
}
converterExecutor.invokeAll(tasks); // make sure all files are converted
File batchFile = zipFiles(sourceUrls, outputFormat, batchSignature);
batchDao.addBatch(batchSignature, outputFormat, batchFile);
batchFilePath = batchDao.findBatchPath(batchSignature, outputFormat);
sendRedirect(response, batchFilePath);
}
private void sendRedirect(HttpServletResponse response, String batchFilePath) throws IOException {
if (!useXAccelRerirect) {
response.sendRedirect(staticRedirectUrlPrefix + batchFilePath);
} else {
response.setHeader("X-Accel-Redirect", staticRedirectUrlPrefix + batchFilePath);
String fileName = FilenameUtils.getName(batchFilePath);
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
response.setStatus(HttpStatus.OK.value());
}
}
private String[] getSourceUrls(String sourceUrlTemplate, String sourceUrlParams) {
String[] params = sourceUrlParams.split("_");
String[] urls = new String[params.length];
MessageFormat template = new MessageFormat(sourceUrlTemplate);
for (int i = 0; i < params.length; i++) {
String param = params[i];
String[] subParams = param.split(";");
StringBuffer url = template.format(subParams, new StringBuffer(), null);
urls[i] = url.toString();
}
return urls;
}
private File zipFiles(String[] sourceUrls, String outputFormat, String batchSignature) throws IOException {
File batchFile = File.createTempFile("b_" + batchSignature, ".zip");
ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(batchFile));
zipOutputStream.setLevel(ZipOutputStream.STORED);
try {
byte[] buffer = new byte[8 * 1024];
for (String sourceUrl : sourceUrls) {
UrlInfo urlInfo = urlDao.findUrlInfo(sourceUrl);
if (urlInfo != null) {
File book = bookDao.findBook(urlInfo.getBookId(), outputFormat);
if (book != null) {
ZipEntry entry = new ZipEntry(book.getName());
zipOutputStream.putNextEntry(entry);
FileInputStream fileInputStream = new FileInputStream(book);
int read;
while ((read = fileInputStream.read(buffer)) > 0) {
zipOutputStream.write(buffer, 0, read);
}
IOUtils.closeQuietly(fileInputStream);
}
}
}
} catch (Exception e) {
batchFile.delete();
throw new IOException(e);
} finally {
IOUtils.closeQuietly(zipOutputStream);
}
return batchFile;
}
private String calculateBatchSignature(String[] sourceUrls) {
StringBuilder buffer = new StringBuilder();
for (String sourceUrl : sourceUrls) {
buffer.append(sourceUrl);
}
return DigestUtils.md5Hex(buffer.toString());
}
private Callable<Object> createSingleConversionTask(final String sourceUrl, final String outputFormat) {
return Executors.callable(
new Runnable() {
@Override
public void run() {
try {
singleConverterController.convert(sourceUrl, null, outputFormat, null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
);
}
public void shutdown() {
converterExecutor.shutdown();
}
public void setStaticRedirectUrlPrefix(String staticRedirectUrlPrefix) {
this.staticRedirectUrlPrefix = staticRedirectUrlPrefix;
}
public void setConvertersPoolSize(int convertersPoolSize) {
this.convertersPoolSize = convertersPoolSize;
}
public void setUseXAccelRerirect(Boolean useXAccelRerirect) {
this.useXAccelRerirect = useXAccelRerirect;
}
}